aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/App.scss2
-rw-r--r--src/components/login_form/login_form.js3
-rw-r--r--src/components/oauth_callback/oauth_callback.js3
-rw-r--r--src/components/poll/poll.js39
-rw-r--r--src/components/poll/poll.vue16
-rw-r--r--src/components/post_status_form/post_status_form.js7
-rw-r--r--src/components/post_status_form/post_status_form.vue32
-rw-r--r--src/components/status/status.vue2
-rw-r--r--src/lib/persisted_state.js3
-rw-r--r--src/main.js4
-rw-r--r--src/modules/chat.js2
-rw-r--r--src/modules/oauth.js8
-rw-r--r--src/modules/polls.js70
-rw-r--r--src/modules/statuses.js12
-rw-r--r--src/modules/users.js2
-rw-r--r--src/services/new_api/user_search.js3
16 files changed, 159 insertions, 49 deletions
diff --git a/src/App.scss b/src/App.scss
index db9f62f0..e4c764bf 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -200,6 +200,7 @@ input, textarea, .select {
}
}
+ label::before {
+ flex-shrink: 0;
display: inline-block;
content: '';
transition: box-shadow 200ms;
@@ -236,6 +237,7 @@ input, textarea, .select {
}
}
+ label::before {
+ flex-shrink: 0;
display: inline-block;
content: '✔';
transition: color 200ms;
diff --git a/src/components/login_form/login_form.js b/src/components/login_form/login_form.js
index 93214646..4a5b1965 100644
--- a/src/components/login_form/login_form.js
+++ b/src/components/login_form/login_form.js
@@ -26,9 +26,10 @@ const LoginForm = {
this.isTokenAuth ? this.submitToken() : this.submitPassword()
},
submitToken () {
- const { clientId } = this.oauth
+ const { clientId, clientSecret } = this.oauth
const data = {
clientId,
+ clientSecret,
instance: this.instance.server,
commit: this.$store.commit
}
diff --git a/src/components/oauth_callback/oauth_callback.js b/src/components/oauth_callback/oauth_callback.js
index 2c6ca235..a3c7b7f9 100644
--- a/src/components/oauth_callback/oauth_callback.js
+++ b/src/components/oauth_callback/oauth_callback.js
@@ -4,10 +4,11 @@ const oac = {
props: ['code'],
mounted () {
if (this.code) {
- const { clientId } = this.$store.state.oauth
+ const { clientId, clientSecret } = this.$store.state.oauth
oauth.getToken({
clientId,
+ clientSecret,
instance: this.$store.state.instance.server,
code: this.code
}).then((result) => {
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
index ecacbc35..98db5582 100644
--- a/src/components/poll/poll.js
+++ b/src/components/poll/poll.js
@@ -3,26 +3,39 @@ import { forEach, map } from 'lodash'
export default {
name: 'Poll',
- props: ['poll', 'statusId'],
+ props: ['basePoll'],
components: { Timeago },
data () {
return {
loading: false,
- choices: [],
- refreshInterval: null
+ choices: []
}
},
created () {
- this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
- // Initialize choices to booleans and set its length to match options
- this.choices = this.poll.options.map(_ => false)
+ if (!this.$store.state.polls.pollsObject[this.pollId]) {
+ this.$store.dispatch('mergeOrAddPoll', this.basePoll)
+ }
+ this.$store.dispatch('trackPoll', this.pollId)
},
destroyed () {
- clearTimeout(this.refreshInterval)
+ this.$store.dispatch('untrackPoll', this.pollId)
},
computed: {
+ pollId () {
+ return this.basePoll.id
+ },
+ poll () {
+ const storePoll = this.$store.state.polls.pollsObject[this.pollId]
+ return storePoll || {}
+ },
+ options () {
+ return (this.poll && this.poll.options) || []
+ },
+ expiresAt () {
+ return (this.poll && this.poll.expires_at) || 0
+ },
expired () {
- return Date.now() > Date.parse(this.poll.expires_at)
+ return (this.poll && this.poll.expired) || false
},
loggedIn () {
return this.$store.state.users.currentUser
@@ -33,9 +46,6 @@ export default {
totalVotesCount () {
return this.poll.votes_count
},
- expiresAt () {
- return Date.parse(this.poll.expires_at).toLocaleString()
- },
containerClass () {
return {
loading: this.loading
@@ -55,11 +65,6 @@ export default {
}
},
methods: {
- refreshPoll () {
- if (this.expired) return
- this.fetchPoll()
- this.refreshInterval = setTimeout(this.refreshPoll, 30 * 1000)
- },
percentageForOption (count) {
return this.totalVotesCount === 0 ? 0 : Math.round(count / this.totalVotesCount * 100)
},
@@ -104,4 +109,4 @@ export default {
})
}
}
-} \ No newline at end of file
+}
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index 28e9f4a8..bb67101a 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -2,7 +2,7 @@
<div class="poll" v-bind:class="containerClass">
<div
class="poll-option"
- v-for="(option, index) in poll.options"
+ v-for="(option, index) in options"
:key="index"
>
<div v-if="showResults" :title="resultTitle(option)" class="option-result">
@@ -31,8 +31,8 @@
:disabled="loading"
:value="index"
>
- <label>
- {{option.title}}
+ <label class="option-vote">
+ <div>{{option.title}}</div>
</label>
</div>
</div>
@@ -50,7 +50,7 @@
{{totalVotesCount}} {{ $t("polls.votes") }}&nbsp;·&nbsp;
</div>
<i18n :path="expired ? 'polls.expired' : 'polls.expires_in'">
- <Timeago :time="this.poll.expires_at" :auto-update="60" :now-threshold="0" />
+ <Timeago :time="this.expiresAt" :auto-update="60" :now-threshold="0" />
</i18n>
</div>
</div>
@@ -68,8 +68,7 @@
margin: 0 0 0.5em;
}
.poll-option {
- margin: 0.5em 0;
- height: 1.5em;
+ margin: 0.75em 0.5em;
}
.option-result {
height: 100%;
@@ -87,6 +86,7 @@
}
.result-percentage {
width: 3.5em;
+ flex-shrink: 0;
}
.result-fill {
height: 100%;
@@ -99,6 +99,10 @@
left: 0;
transition: width 0.5s;
}
+ .option-vote {
+ display: flex;
+ align-items: center;
+ }
input {
width: 3.5em;
}
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index 5dbb1c9d..ef6b0fce 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -269,8 +269,11 @@ const PostStatusForm = {
resize (e) {
const target = e.target || e
if (!(target instanceof window.Element)) { return }
- const vertPadding = Number(window.getComputedStyle(target)['padding-top'].substr(0, 1)) +
- Number(window.getComputedStyle(target)['padding-bottom'].substr(0, 1))
+ const topPaddingStr = window.getComputedStyle(target)['padding-top']
+ const bottomPaddingStr = window.getComputedStyle(target)['padding-bottom']
+ // Remove "px" at the end of the values
+ const vertPadding = Number(topPaddingStr.substr(0, topPaddingStr.length - 2)) +
+ Number(bottomPaddingStr.substr(0, bottomPaddingStr.length - 2))
// Auto is needed to make textbox shrink when removing lines
target.style.height = 'auto'
target.style.height = `${target.scrollHeight - vertPadding}px`
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index fbeaf39b..67cdc721 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -48,7 +48,7 @@
<EmojiInput
:suggest="emojiUserSuggestor"
v-model="newStatus.status"
- class="form-control"
+ class="form-control main-input"
>
<textarea
ref="textarea"
@@ -65,6 +65,13 @@
class="form-post-body"
>
</textarea>
+ <p
+ v-if="hasStatusLengthLimit"
+ class="character-counter faint"
+ :class="{ error: isOverLengthLimit }"
+ >
+ {{ charactersLeft }}
+ </p>
</EmojiInput>
<div class="visibility-tray">
<div class="text-format" v-if="postFormats.length > 1">
@@ -109,8 +116,6 @@
/>
</div>
</div>
- <p v-if="isOverLengthLimit" class="error">{{ charactersLeft }}</p>
- <p class="faint" v-else-if="hasStatusLengthLimit">{{ charactersLeft }}</p>
<button v-if="posting" disabled class="btn btn-default">{{$t('post_status.posting')}}</button>
<button v-else-if="isOverLengthLimit" disabled class="btn btn-default">{{$t('general.submit')}}</button>
@@ -304,10 +309,12 @@
}
.form-post-body {
- line-height:16px;
+ height: 16px; // Only affects the empty-height
+ line-height: 16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
+ padding-bottom: 1.75em;
min-height: 1px;
box-sizing: content-box;
}
@@ -316,6 +323,23 @@
min-height: 48px;
}
+ .main-input {
+ position: relative;
+ }
+
+ .character-counter {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ padding: 0;
+ margin: 0 0.5em;
+
+ &.error {
+ color: $fallback--cRed;
+ color: var(--cRed, $fallback--cRed);
+ }
+ }
+
.btn {
cursor: pointer;
}
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 58402f7e..440e1957 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -124,7 +124,7 @@
</div>
<div v-if="status.poll && status.poll.options">
- <poll :poll="status.poll" :status-id="status.id" />
+ <poll :base-poll="status.poll" />
</div>
<div v-if="status.attachments && (!hideSubjectStatus || showingLongSubject)" class="attachments media-body">
diff --git a/src/lib/persisted_state.js b/src/lib/persisted_state.js
index 7ab89c12..cad7ea25 100644
--- a/src/lib/persisted_state.js
+++ b/src/lib/persisted_state.js
@@ -19,7 +19,8 @@ const saveImmedeatelyActions = [
'setHighlight',
'setOption',
'setClientData',
- 'setToken'
+ 'setToken',
+ 'clearToken'
]
const defaultStorage = (() => {
diff --git a/src/main.js b/src/main.js
index d0f2674b..3287fa2b 100644
--- a/src/main.js
+++ b/src/main.js
@@ -14,6 +14,7 @@ import authFlowModule from './modules/auth_flow.js'
import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
import reportsModule from './modules/reports.js'
+import pollsModule from './modules/polls.js'
import VueI18n from 'vue-i18n'
@@ -72,7 +73,8 @@ const persistedStateOptions = {
authFlow: authFlowModule,
mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule,
- reports: reportsModule
+ reports: reportsModule,
+ polls: pollsModule
},
plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now.
diff --git a/src/modules/chat.js b/src/modules/chat.js
index 2804e577..4d8d6699 100644
--- a/src/modules/chat.js
+++ b/src/modules/chat.js
@@ -21,7 +21,7 @@ const chat = {
},
actions: {
disconnectFromChat (store) {
- store.state.socket.disconnect()
+ store.state.socket && store.state.socket.disconnect()
},
initializeChat (store, socket) {
const channel = socket.channel('chat:public')
diff --git a/src/modules/oauth.js b/src/modules/oauth.js
index 11cb10fe..a2a83450 100644
--- a/src/modules/oauth.js
+++ b/src/modules/oauth.js
@@ -1,3 +1,5 @@
+import { delete as del } from 'vue'
+
const oauth = {
state: {
clientId: false,
@@ -22,6 +24,12 @@ const oauth = {
},
setToken (state, token) {
state.userToken = token
+ },
+ clearToken (state) {
+ state.userToken = false
+ // state.token is userToken with older name, coming from persistent state
+ // let's clear it as well, since it is being used as a fallback of state.userToken
+ del(state, 'token')
}
},
getters: {
diff --git a/src/modules/polls.js b/src/modules/polls.js
new file mode 100644
index 00000000..e6158b63
--- /dev/null
+++ b/src/modules/polls.js
@@ -0,0 +1,70 @@
+import { merge } from 'lodash'
+import { set } from 'vue'
+
+const polls = {
+ state: {
+ // Contains key = id, value = number of trackers for this poll
+ trackedPolls: {},
+ pollsObject: {}
+ },
+ mutations: {
+ mergeOrAddPoll (state, poll) {
+ const existingPoll = state.pollsObject[poll.id]
+ // Make expired-state change trigger re-renders properly
+ poll.expired = Date.now() > Date.parse(poll.expires_at)
+ if (existingPoll) {
+ set(state.pollsObject, poll.id, merge(existingPoll, poll))
+ } else {
+ set(state.pollsObject, poll.id, poll)
+ }
+ },
+ trackPoll (state, pollId) {
+ const currentValue = state.trackedPolls[pollId]
+ if (currentValue) {
+ set(state.trackedPolls, pollId, currentValue + 1)
+ } else {
+ set(state.trackedPolls, pollId, 1)
+ }
+ },
+ untrackPoll (state, pollId) {
+ const currentValue = state.trackedPolls[pollId]
+ if (currentValue) {
+ set(state.trackedPolls, pollId, currentValue - 1)
+ } else {
+ set(state.trackedPolls, pollId, 0)
+ }
+ }
+ },
+ actions: {
+ mergeOrAddPoll ({ commit }, poll) {
+ commit('mergeOrAddPoll', poll)
+ },
+ updateTrackedPoll ({ rootState, dispatch, commit }, pollId) {
+ rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
+ setTimeout(() => {
+ if (rootState.polls.trackedPolls[pollId]) {
+ dispatch('updateTrackedPoll', pollId)
+ }
+ }, 30 * 1000)
+ commit('mergeOrAddPoll', poll)
+ })
+ },
+ trackPoll ({ rootState, commit, dispatch }, pollId) {
+ if (!rootState.polls.trackedPolls[pollId]) {
+ setTimeout(() => dispatch('updateTrackedPoll', pollId), 30 * 1000)
+ }
+ commit('trackPoll', pollId)
+ },
+ untrackPoll ({ commit }, pollId) {
+ commit('untrackPoll', pollId)
+ },
+ votePoll ({ rootState, commit }, { id, pollId, choices }) {
+ return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
+ commit('mergeOrAddPoll', poll)
+ return poll
+ })
+ }
+ }
+}
+
+export default polls
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index 262ff80a..9b11a13e 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -583,18 +583,6 @@ const statuses = {
]).then(([favoritedByUsers, rebloggedByUsers]) =>
commit('addFavsAndRepeats', { id, favoritedByUsers, rebloggedByUsers })
)
- },
- votePoll ({ rootState, commit }, { id, pollId, choices }) {
- return rootState.api.backendInteractor.vote(pollId, choices).then(poll => {
- commit('updateStatusWithPoll', { id, poll })
- return poll
- })
- },
- refreshPoll ({ rootState, commit }, { id, pollId }) {
- return rootState.api.backendInteractor.fetchPoll(pollId).then(poll => {
- commit('updateStatusWithPoll', { id, poll })
- return poll
- })
}
},
mutations
diff --git a/src/modules/users.js b/src/modules/users.js
index 22340271..1e0b16f5 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -399,7 +399,7 @@ const users = {
logout (store) {
store.commit('clearCurrentUser')
store.dispatch('disconnectFromChat')
- store.commit('setToken', false)
+ store.commit('clearToken')
store.dispatch('stopFetching', 'friends')
store.commit('setBackendInteractor', backendInteractorService(store.getters.getToken()))
store.dispatch('stopFetching', 'notifications')
diff --git a/src/services/new_api/user_search.js b/src/services/new_api/user_search.js
index 869afa9c..c5a87cce 100644
--- a/src/services/new_api/user_search.js
+++ b/src/services/new_api/user_search.js
@@ -6,7 +6,8 @@ const search = ({query, store}) => {
store,
url: '/api/v1/accounts/search',
params: {
- q: query
+ q: query,
+ resolve: true
}
})
.then((data) => data.json())