Page MenuHomePhorge

No OneTemporary

Size
69 KB
Referenced Files
None
Subscribers
None
diff --git a/src/App.js b/src/App.js
index 13bd3e05a3..ce6456767b 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,107 +1,107 @@
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import Notifications from './components/notifications/notifications_two.vue'
import UserFinder from './components/user_finder/user_finder.vue'
import InstanceSpecificPanel from './components/instance_specific_panel/instance_specific_panel.vue'
import FeaturesPanel from './components/features_panel/features_panel.vue'
import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_panel.vue'
import ChatPanel from './components/chat_panel/chat_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue'
import { unseenNotificationsFromStore } from './services/notification_utils/notification_utils'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
Notifications,
UserFinder,
InstanceSpecificPanel,
FeaturesPanel,
WhoToFollowPanel,
ChatPanel,
MediaModal,
SideDrawer
},
data: () => ({
mobileActivePanel: 'timeline',
finderHidden: true,
supportsMask: window.CSS && window.CSS.supports && (
window.CSS.supports('mask-size', 'contain') ||
window.CSS.supports('-webkit-mask-size', 'contain') ||
window.CSS.supports('-moz-mask-size', 'contain') ||
window.CSS.supports('-ms-mask-size', 'contain') ||
window.CSS.supports('-o-mask-size', 'contain')
)
}),
created () {
// Load the locale from the storage
this.$i18n.locale = this.$store.state.config.interfaceLanguage
},
computed: {
- currentUser () { return this.$store.state.usersTwo.currentUser },
+ currentUser () { return this.$store.state.users.currentUser },
background () {
return this.currentUser.background_image || this.$store.state.instance.background
},
enableMask () { return this.supportsMask && this.$store.state.instance.logoMask },
logoStyle () {
return {
'visibility': this.enableMask ? 'hidden' : 'visible'
}
},
logoMaskStyle () {
return this.enableMask ? {
'mask-image': `url(${this.$store.state.instance.logo})`
} : {
'background-color': this.enableMask ? '' : 'transparent'
}
},
logoBgStyle () {
return Object.assign({
'margin': `${this.$store.state.instance.logoMargin} 0`,
opacity: this.finderHidden ? 1 : 0
}, this.enableMask ? {} : {
'background-color': this.enableMask ? '' : 'transparent'
})
},
logo () { return this.$store.state.instance.logo },
bgStyle () {
return {
'background-image': `url(${this.background})`
}
},
bgAppStyle () {
return {
'--body-background-image': `url(${this.background})`
}
},
sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { return this.$store.state.instance.showInstanceSpecificPanel },
unseenNotifications () {
// TODO: Fix
return []
},
unseenNotificationsCount () {
return this.unseenNotifications.length
},
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }
},
methods: {
scrollToTop () {
window.scrollTo(0, 0)
},
logout () {
this.$router.replace('/main/public')
this.$store.dispatch('logoutUser')
},
onFinderToggled (hidden) {
this.finderHidden = hidden
},
toggleMobileSidebar () {
this.$refs.sideDrawer.toggleDrawer()
}
}
}
diff --git a/src/components/delete_button/delete_button.js b/src/components/delete_button/delete_button.js
index d2a4f99611..200e2c0f1e 100644
--- a/src/components/delete_button/delete_button.js
+++ b/src/components/delete_button/delete_button.js
@@ -1,17 +1,17 @@
const DeleteButton = {
props: [ 'status' ],
methods: {
deleteStatus () {
const confirmed = window.confirm('Do you really want to delete this status?')
if (confirmed) {
this.$store.dispatch('deleteStatus', { id: this.status.id })
}
}
},
computed: {
- currentUser () { return this.$store.state.usersTwo.currentUser },
+ currentUser () { return this.$store.state.users.currentUser },
canDelete () { return this.currentUser && this.status.account.id === this.currentUser.id }
}
}
export default DeleteButton
diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js
index 847411d161..ea5d7ea4f5 100644
--- a/src/components/nav_panel/nav_panel.js
+++ b/src/components/nav_panel/nav_panel.js
@@ -1,12 +1,12 @@
const NavPanel = {
computed: {
currentUser () {
- return this.$store.state.usersTwo.currentUser
+ return this.$store.state.users.currentUser
},
chat () {
return this.$store.state.chat.channel
}
}
}
export default NavPanel
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index bbb1702617..51f04f991d 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,329 +1,329 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import fileTypeService from '../../services/file_type/file_type.service.js'
import Completion from '../../services/completion/completion.js'
import { take, filter, reject, map, uniqBy } from 'lodash'
const buildMentionsString = ({ user, attentions }, currentUser) => {
let allAttentions = [...attentions]
allAttentions.unshift(user)
allAttentions = uniqBy(allAttentions, 'id')
allAttentions = reject(allAttentions, { id: currentUser.id })
let mentions = map(allAttentions, (attention) => {
return `@${attention.acct}`
})
return mentions.length > 0 ? mentions.join(' ') + ' ' : ''
}
const PostStatusForm = {
props: [
'replyTo',
'repliedUser',
'attentions',
'copyMessageScope',
'subject'
],
components: {
MediaUpload
},
mounted () {
this.resize(this.$refs.textarea)
const textLength = this.$refs.textarea.value.length
this.$refs.textarea.setSelectionRange(textLength, textLength)
if (this.replyTo) {
this.$refs.textarea.focus()
}
},
data () {
const preset = this.$route.query.message
let statusText = preset || ''
const scopeCopy = typeof this.$store.state.config.scopeCopy === 'undefined'
? this.$store.state.instance.scopeCopy
: this.$store.state.config.scopeCopy
if (this.replyTo) {
- const currentUser = this.$store.state.usersTwo.currentUser
+ const currentUser = this.$store.state.users.currentUser
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
const scope = (this.copyMessageScope && scopeCopy || this.copyMessageScope === 'direct')
? this.copyMessageScope
- : this.$store.state.usersTwo.currentUser.default_scope
+ : this.$store.state.users.currentUser.default_scope
const contentType = typeof this.$store.state.config.postContentType === 'undefined'
? this.$store.state.instance.postContentType
: this.$store.state.config.postContentType
return {
dropFiles: [],
submitDisabled: false,
error: null,
posting: false,
highlighted: 0,
newStatus: {
spoilerText: this.subject || '',
status: statusText,
nsfw: false,
files: [],
visibility: scope,
contentType
},
caret: 0
}
},
computed: {
vis () {
return {
public: { selected: this.newStatus.visibility === 'public' },
unlisted: { selected: this.newStatus.visibility === 'unlisted' },
private: { selected: this.newStatus.visibility === 'private' },
direct: { selected: this.newStatus.visibility === 'direct' }
}
},
candidates () {
const firstchar = this.textAtCaret.charAt(0)
if (firstchar === '@') {
const query = this.textAtCaret.slice(1).toUpperCase()
const matchedUsers = filter(this.users, (user) => {
return user.screen_name.toUpperCase().startsWith(query) ||
user.name && user.name.toUpperCase().startsWith(query)
})
if (matchedUsers.length <= 0) {
return false
}
// eslint-disable-next-line camelcase
return map(take(matchedUsers, 5), ({ screen_name, name, profile_image_url_original }, index) => ({
// eslint-disable-next-line camelcase
screen_name: `@${screen_name}`,
name: name,
img: profile_image_url_original,
highlighted: index === this.highlighted
}))
} else if (firstchar === ':') {
if (this.textAtCaret === ':') { return }
const matchedEmoji = filter(this.emoji.concat(this.customEmoji), (emoji) => emoji.shortcode.startsWith(this.textAtCaret.slice(1)))
if (matchedEmoji.length <= 0) {
return false
}
return map(take(matchedEmoji, 5), ({ shortcode, image_url, utf }, index) => ({
screen_name: `:${shortcode}:`,
name: '',
utf: utf || '',
// eslint-disable-next-line camelcase
img: utf ? '' : this.$store.state.instance.server + image_url,
highlighted: index === this.highlighted
}))
} else {
return false
}
},
textAtCaret () {
return (this.wordAtCaret || {}).word || ''
},
wordAtCaret () {
const word = Completion.wordAtPosition(this.newStatus.status, this.caret - 1) || {}
return word
},
users () {
- return this.$store.state.usersTwo.users
+ return this.$store.state.users.users
},
emoji () {
return this.$store.state.instance.emoji || []
},
customEmoji () {
return this.$store.state.instance.customEmoji || []
},
statusLength () {
return this.newStatus.status.length
},
spoilerTextLength () {
return this.newStatus.spoilerText.length
},
statusLengthLimit () {
return this.$store.state.instance.textlimit
},
hasStatusLengthLimit () {
return this.statusLengthLimit > 0
},
charactersLeft () {
return this.statusLengthLimit - (this.statusLength + this.spoilerTextLength)
},
isOverLengthLimit () {
return this.hasStatusLengthLimit && (this.charactersLeft < 0)
},
scopeOptionsEnabled () {
return this.$store.state.instance.scopeOptionsEnabled
},
alwaysShowSubject () {
if (typeof this.$store.state.config.alwaysShowSubjectInput !== 'undefined') {
return this.$store.state.config.alwaysShowSubjectInput
} else if (typeof this.$store.state.instance.alwaysShowSubjectInput !== 'undefined') {
return this.$store.state.instance.alwaysShowSubjectInput
} else {
return this.$store.state.instance.scopeOptionsEnabled
}
},
formattingOptionsEnabled () {
return this.$store.state.instance.formattingOptionsEnabled
}
},
methods: {
replace (replacement) {
this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
const el = this.$el.querySelector('textarea')
el.focus()
this.caret = 0
},
replaceCandidate (e) {
const len = this.candidates.length || 0
if (this.textAtCaret === ':' || e.ctrlKey) { return }
if (len > 0) {
e.preventDefault()
const candidate = this.candidates[this.highlighted]
const replacement = candidate.utf || (candidate.screen_name + ' ')
this.newStatus.status = Completion.replaceWord(this.newStatus.status, this.wordAtCaret, replacement)
const el = this.$el.querySelector('textarea')
el.focus()
this.caret = 0
this.highlighted = 0
}
},
cycleBackward (e) {
const len = this.candidates.length || 0
if (len > 0) {
e.preventDefault()
this.highlighted -= 1
if (this.highlighted < 0) {
this.highlighted = this.candidates.length - 1
}
} else {
this.highlighted = 0
}
},
cycleForward (e) {
const len = this.candidates.length || 0
if (len > 0) {
if (e.shiftKey) { return }
e.preventDefault()
this.highlighted += 1
if (this.highlighted >= len) {
this.highlighted = 0
}
} else {
this.highlighted = 0
}
},
setCaret ({ target: { selectionStart } }) {
this.caret = selectionStart
},
postStatus (newStatus) {
if (this.posting) { return }
if (this.submitDisabled) { return }
if (this.newStatus.status === '') {
if (this.newStatus.files.length > 0) {
this.newStatus.status = '\u200b' // hack
} else {
this.error = 'Cannot post an empty status with no files'
return
}
}
this.posting = true
statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
visibility: newStatus.visibility,
sensitive: newStatus.nsfw,
media: newStatus.files,
store: this.$store,
inReplyToStatusId: this.replyTo,
contentType: newStatus.contentType
}).then((data) => {
if (!data.error) {
this.newStatus = {
status: '',
spoilerText: '',
files: [],
visibility: newStatus.visibility,
contentType: newStatus.contentType
}
this.$refs.mediaUpload.clearFile()
this.$emit('posted')
let el = this.$el.querySelector('textarea')
el.style.height = 'auto'
el.style.height = undefined
this.error = null
} else {
this.error = data.error
}
this.posting = false
})
},
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
this.enableSubmit()
},
removeMediaFile (fileInfo) {
let index = this.newStatus.files.indexOf(fileInfo)
this.newStatus.files.splice(index, 1)
},
uploadFailed (errString, templateArgs) {
templateArgs = templateArgs || {}
this.error = this.$t('upload.error.base') + ' ' + this.$t('upload.error.' + errString, templateArgs)
this.enableSubmit()
},
disableSubmit () {
this.submitDisabled = true
},
enableSubmit () {
this.submitDisabled = false
},
type (fileInfo) {
return fileTypeService.fileType(fileInfo.mimetype)
},
paste (e) {
if (e.clipboardData.files.length > 0) {
// Strangely, files property gets emptied after event propagation
// Trying to wrap it in array doesn't work. Plus I doubt it's possible
// to hold more than one file in clipboard.
this.dropFiles = [e.clipboardData.files[0]]
}
},
fileDrop (e) {
if (e.dataTransfer.files.length > 0) {
e.preventDefault() // allow dropping text like before
this.dropFiles = e.dataTransfer.files
}
},
fileDrag (e) {
e.dataTransfer.dropEffect = 'copy'
},
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))
// Auto is needed to make textbox shrink when removing lines
target.style.height = 'auto'
target.style.height = `${target.scrollHeight - vertPadding}px`
if (target.value === '') {
target.style.height = null
}
},
clearError () {
this.error = null
},
changeVis (visibility) {
this.newStatus.visibility = visibility
}
}
}
export default PostStatusForm
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 5d3e085367..5085570be3 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -1,309 +1,309 @@
<template>
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
<div class="form-group" >
<i18n
- v-if="!this.$store.state.usersTwo.currentUser.locked && this.newStatus.visibility == 'private'"
+ v-if="!this.$store.state.users.currentUser.locked && this.newStatus.visibility == 'private'"
path="post_status.account_not_locked_warning"
tag="p"
class="visibility-notice">
<router-link :to="{ name: 'user-settings' }">{{ $t('post_status.account_not_locked_warning_link') }}</router-link>
</i18n>
<p v-if="this.newStatus.visibility == 'direct'" class="visibility-notice">{{ $t('post_status.direct_warning') }}</p>
<input
v-if="newStatus.spoilerText || alwaysShowSubject"
type="text"
:placeholder="$t('post_status.content_warning')"
v-model="newStatus.spoilerText"
class="form-cw">
<textarea
ref="textarea"
@click="setCaret"
@keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control"
@keydown.down="cycleForward"
@keydown.up="cycleBackward"
@keydown.shift.tab="cycleBackward"
@keydown.tab="cycleForward"
@keydown.enter="replaceCandidate"
@keydown.meta.enter="postStatus(newStatus)"
@keyup.ctrl.enter="postStatus(newStatus)"
@drop="fileDrop"
@dragover.prevent="fileDrag"
@input="resize"
@paste="paste">
</textarea>
<div class="visibility-tray">
<span class="text-format" v-if="formattingOptionsEnabled">
<label for="post-content-type" class="select">
<select id="post-content-type" v-model="newStatus.contentType" class="form-control">
<option value="text/plain">{{$t('post_status.content_type.plain_text')}}</option>
<option value="text/html">HTML</option>
<option value="text/markdown">Markdown</option>
</select>
<i class="icon-down-open"></i>
</label>
</span>
<div v-if="scopeOptionsEnabled">
<i v-on:click="changeVis('direct')" class="icon-mail-alt" :class="vis.direct" :title="$t('post_status.scope.direct')"></i>
<i v-on:click="changeVis('private')" class="icon-lock" :class="vis.private" :title="$t('post_status.scope.private')"></i>
<i v-on:click="changeVis('unlisted')" class="icon-lock-open-alt" :class="vis.unlisted" :title="$t('post_status.scope.unlisted')"></i>
<i v-on:click="changeVis('public')" class="icon-globe" :class="vis.public" :title="$t('post_status.scope.public')"></i>
</div>
</div>
</div>
<div style="position:relative;" v-if="candidates">
<div class="autocomplete-panel">
<div v-for="candidate in candidates" @click="replace(candidate.utf || (candidate.screen_name + ' '))">
<div class="autocomplete" :class="{ highlighted: candidate.highlighted }">
<span v-if="candidate.img"><img :src="candidate.img"></img></span>
<span v-else>{{candidate.utf}}</span>
<span>{{candidate.screen_name}}<small>{{candidate.name}}</small></span>
</div>
</div>
</div>
</div>
<div class='form-bottom'>
<media-upload ref="mediaUpload" @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="uploadFailed" :drop-files="dropFiles"></media-upload>
<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>
<button v-else :disabled="submitDisabled" type="submit" class="btn btn-default">{{$t('general.submit')}}</button>
</div>
<div class='alert error' v-if="error">
Error: {{ error }}
<i class="button-icon icon-cancel" @click="clearError"></i>
</div>
<div class="attachments">
<div class="media-upload-wrapper" v-for="file in newStatus.files">
<i class="fa button-icon icon-cancel" @click="removeMediaFile(file)"></i>
<div class="media-upload-container attachment">
<img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img>
<video v-if="type(file) === 'video'" :src="file.image" controls></video>
<audio v-if="type(file) === 'audio'" :src="file.image" controls></audio>
<a v-if="type(file) === 'unknown'" :href="file.image">{{file.url}}</a>
</div>
</div>
</div>
<div class="upload_settings" v-if="newStatus.files.length > 0">
<input type="checkbox" id="filesSensitive" v-model="newStatus.nsfw">
<label for="filesSensitive">{{$t('post_status.attachments_sensitive')}}</label>
</div>
</form>
</div>
</template>
<script src="./post_status_form.js"></script>
<style lang="scss">
@import '../../_variables.scss';
.tribute-container {
ul {
padding: 0px;
li {
display: flex;
align-items: center;
}
}
img {
padding: 3px;
width: 16px;
height: 16px;
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
}
}
.post-status-form {
.visibility-tray {
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
}
}
.post-status-form, .login {
.form-bottom {
display: flex;
padding: 0.5em;
height: 32px;
button {
width: 10em;
}
p {
margin: 0.35em;
padding: 0.35em;
display: flex;
}
}
.error {
text-align: center;
}
.media-upload-wrapper {
flex: 0 0 auto;
max-width: 100%;
min-width: 50px;
margin-right: .2em;
margin-bottom: .5em;
.icon-cancel {
display: inline-block;
position: static;
margin: 0;
padding-bottom: 0;
margin-left: $fallback--attachmentRadius;
margin-left: var(--attachmentRadius, $fallback--attachmentRadius);
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
}
.attachments {
padding: 0 0.5em;
.attachment {
margin: 0;
position: relative;
flex: 0 0 auto;
border: 1px solid $fallback--border;
border: 1px solid var(--border, $fallback--border);
text-align: center;
audio {
min-width: 300px;
flex: 1 0 auto;
}
a {
display: block;
text-align: left;
line-height: 1.2;
padding: .5em;
}
}
i {
position: absolute;
margin: 10px;
padding: 5px;
background: rgba(230,230,230,0.6);
border-radius: $fallback--attachmentRadius;
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
font-weight: bold;
}
}
.btn {
cursor: pointer;
}
.btn[disabled] {
cursor: not-allowed;
}
form {
display: flex;
flex-direction: column;
padding: 0.6em;
}
.form-group {
display: flex;
flex-direction: column;
padding: 0.3em 0.5em 0.6em;
line-height:24px;
}
form textarea.form-cw {
line-height:16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
min-height: 1px;
}
form textarea.form-control {
line-height:16px;
resize: none;
overflow: hidden;
transition: min-height 200ms 100ms;
min-height: 1px;
box-sizing: content-box;
}
form textarea.form-control:focus {
min-height: 48px;
}
.btn {
cursor: pointer;
}
.btn[disabled] {
cursor: not-allowed;
}
.icon-cancel {
cursor: pointer;
z-index: 4;
}
.autocomplete-panel {
margin: 0 0.5em 0 0.5em;
border-radius: $fallback--tooltipRadius;
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
position: absolute;
z-index: 1;
box-shadow: 1px 2px 4px rgba(0, 0, 0, 0.5);
// this doesn't match original but i don't care, making it uniform.
box-shadow: var(--popupShadow);
min-width: 75%;
background: $fallback--bg;
background: var(--bg, $fallback--bg);
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
.autocomplete {
cursor: pointer;
padding: 0.2em 0.4em 0.2em 0.4em;
border-bottom: 1px solid rgba(0, 0, 0, 0.4);
display: flex;
img {
width: 24px;
height: 24px;
border-radius: $fallback--avatarRadius;
border-radius: var(--avatarRadius, $fallback--avatarRadius);
object-fit: contain;
}
span {
line-height: 24px;
margin: 0 0.1em 0 0.2em;
}
small {
margin-left: .5em;
color: $fallback--faint;
color: var(--faint, $fallback--faint);
}
&.highlighted {
background-color: $fallback--fg;
background-color: var(--lightBg, $fallback--fg);
}
}
}
</style>
diff --git a/src/components/status/status_two.js b/src/components/status/status_two.js
index 89fab1bd5c..2bdf1a3a18 100644
--- a/src/components/status/status_two.js
+++ b/src/components/status/status_two.js
@@ -1,401 +1,401 @@
import Attachment from '../attachment/attachment_two.vue'
import FavoriteButton from '../favorite_button/favorite_button.vue'
import RetweetButton from '../retweet_button/retweet_button.vue'
import DeleteButton from '../delete_button/delete_button.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCardContent from '../user_card_content/user_card_content_two.vue'
import UserAvatar from '../user_avatar/user_avatar.vue'
import Gallery from '../gallery/gallery_two.vue'
import LinkPreview from '../link-preview/link-preview.vue'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
import fileType from 'src/services/file_type/file_type.service'
import { highlightClass, highlightStyle } from '../../services/user_highlighter/user_highlighter_two.js'
import { mentionMatchesUrl, extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
import { unescape } from 'lodash'
import StatusApi from '../../services/new_api/statuses.js'
const Status = {
name: 'Status',
props: [
'statusoid',
'expandable',
'inConversation',
'focused',
'highlight',
'compact',
'replies',
'noReplyLinks',
'noHeading',
'inlineExpanded'
],
data () {
return {
replying: false,
expanded: false,
unmuted: false,
userExpanded: false,
showPreview: false,
previewId: false,
showingTall: this.inConversation && this.focused,
showingLongSubject: false,
expandingSubject: typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
? !this.$store.state.instance.collapseMessageWithSubject
: !this.$store.state.config.collapseMessageWithSubject,
betterShadow: this.$store.state.interface.browserSupport.cssFilter
}
},
computed: {
preview () {
return this.$store.state.statusesTwo.statuses[this.previewId]
},
user () {
return this.userForStatus(this.status)
},
statusoidUser () {
return this.userForStatus(this.statusoid)
},
localCollapseSubjectDefault () {
return typeof this.$store.state.config.collapseMessageWithSubject === 'undefined'
? this.$store.state.instance.collapseMessageWithSubject
: this.$store.state.config.collapseMessageWithSubject
},
muteWords () {
return this.$store.state.config.muteWords
},
repeaterClass () {
const user = this.statusoidUser
return highlightClass(user)
},
userClass () {
const user = this.retweet ? (this.user) : this.statusoidUser
return highlightClass(user)
},
deleted () {
return this.statusoid.deleted
},
repeaterStyle () {
const user = this.statusoidUser
const highlight = this.$store.state.config.highlight
return highlightStyle(highlight[user.screen_name])
},
userStyle () {
if (this.noHeading) return
const user = this.retweet ? this.user : this.statusoidUser
const highlight = this.$store.state.config.highlight
return highlightStyle(highlight[user.screen_name])
},
hideAttachments () {
return (this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)
},
userProfileLink () {
return this.generateUserProfileLink(this.user.id, this.user.acct)
},
replyProfileLink () {
if (this.isReply) {
return this.generateUserProfileLink(this.status.in_reply_to_user_id, this.replyToName)
}
},
retweet () { return !!this.statusoid.reblog },
retweeter () { return this.statusoidUser.display_name || this.statusoidUser.acct },
retweeterHtml () { return this.statusoidUser.name_html },
retweeterProfileLink () { return this.generateUserProfileLink(this.statusoidUser.id, this.statusoidUser.acct) },
status () {
if (this.retweet) {
return this.$store.state.statusesTwo.statuses[this.statusoid.reblog.id]
} else {
return this.statusoid
}
},
loggedIn () {
- return !!this.$store.state.usersTwo.currentUser
+ return !!this.$store.state.users.currentUser
},
muteWordHits () {
// TODO: have some text-only version of the status content or create it.
// const statusText = this.status.text.toLowerCase()
// const hits = filter(this.muteWords, (muteWord) => {
// return statusText.includes(muteWord.toLowerCase())
// })
// return hits
return []
},
muted () { return !this.unmuted && (this.user.muted || this.muteWordHits.length > 0) },
hideFilteredStatuses () {
return typeof this.$store.state.config.hideFilteredStatuses === 'undefined'
? this.$store.state.instance.hideFilteredStatuses
: this.$store.state.config.hideFilteredStatuses
},
hideStatus () {
return (this.hideReply || this.deleted) || (this.muted && this.hideFilteredStatuses)
},
isFocused () {
// retweet or root of an expanded conversation
if (this.focused) {
return true
} else if (!this.inConversation) {
return false
}
// use conversation highlight only when in conversation
return this.status.id === this.highlight
},
// This is a bit hacky, but we want to approximate post height before rendering
// so we count newlines (masto uses <p> for paragraphs, GS uses <br> between them)
// as well as approximate line count by counting characters and approximating ~80
// per line.
//
// Using max-height + overflow: auto for status components resulted in false positives
// very often with japanese characters, and it was very annoying.
tallStatus () {
// TODO: Change how this works.
const lengthScore = this.status.content.split(/<p|<br/).length // + this.status.text.length / 80
return lengthScore > 20
},
longSubject () {
return this.status.spoiler_text.length > 900
},
isReply () {
return !!(this.status.in_reply_to_id && this.status.in_reply_to_account_id)
},
replyToName () {
- const user = this.$store.state.usersTwo.users[this.status.in_reply_to_account_id]
+ const user = this.$store.state.users.users[this.status.in_reply_to_account_id]
if (user) {
return user.acct
} else {
return '...'
}
},
hideReply () {
if (this.$store.state.config.replyVisibility === 'all') {
return false
}
if (this.inlineExpanded || this.expanded || this.inConversation || !this.isReply) {
return false
}
if (this.user.id === this.$store.state.users.currentUser.id) {
return false
}
if (this.status.type === 'retweet') {
return false
}
var checkFollowing = this.$store.state.config.replyVisibility === 'following'
for (var i = 0; i < this.status.attentions.length; ++i) {
if (this.user.id === this.status.attentions[i].id) {
continue
}
if (checkFollowing && this.status.attentions[i].following) {
return false
}
if (this.status.attentions[i].id === this.$store.state.users.currentUser.id) {
return false
}
}
return this.status.attentions.length > 0
},
hideSubjectStatus () {
if (this.tallStatus && !this.localCollapseSubjectDefault) {
return false
}
return !this.expandingSubject && this.status.spoiler_text
},
hideTallStatus () {
if (this.status.spoiler_text && this.localCollapseSubjectDefault) {
return false
}
if (this.showingTall) {
return false
}
return this.tallStatus
},
showingMore () {
return (this.tallStatus && this.showingTall) || (this.status.spoiler_text && this.expandingSubject)
},
nsfwClickthrough () {
if (!this.status.sensitive) {
return false
}
if (this.status.spoiler_text && this.localCollapseSubjectDefault) {
return false
}
return true
},
replySubject () {
if (!this.status.spoiler_text) return ''
const decodedSpoiler_Text = unescape(this.status.spoiler_text)
const behavior = typeof this.$store.state.config.subjectLineBehavior === 'undefined'
? this.$store.state.instance.subjectLineBehavior
: this.$store.state.config.subjectLineBehavior
const startsWithRe = decodedSpoiler_Text.match(/^re[: ]/i)
if (behavior !== 'noop' && startsWithRe || behavior === 'masto') {
return decodedSpoiler_Text
} else if (behavior === 'email') {
return 're: '.concat(decodedSpoiler_Text)
} else if (behavior === 'noop') {
return ''
}
},
attachmentSize () {
if ((this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation) ||
(this.status.media_attachments.length > this.maxThumbnails)) {
return 'hide'
} else if (this.compact) {
return 'small'
}
return 'normal'
},
galleryTypes () {
if (this.attachmentSize === 'hide') {
return []
}
return this.$store.state.config.playVideosInModal
? ['image', 'video']
: ['image']
},
galleryAttachments () {
return this.status.media_attachments.filter(
file => fileType.fileMatchesSomeType(this.galleryTypes, file)
)
},
nonGalleryAttachments () {
return this.status.media_attachments.filter(
file => !fileType.fileMatchesSomeType(this.galleryTypes, file)
)
},
maxThumbnails () {
return this.$store.state.config.maxThumbnails
}
},
components: {
Attachment,
FavoriteButton,
RetweetButton,
DeleteButton,
PostStatusForm,
UserCardContent,
UserAvatar,
Gallery,
LinkPreview
},
methods: {
userForStatus ({ account }) {
- const user = this.$store.state.usersTwo.users[account.id]
+ const user = this.$store.state.users.users[account.id]
return user
},
visibilityIcon (visibility) {
switch (visibility) {
case 'private':
return 'icon-lock'
case 'unlisted':
return 'icon-lock-open-alt'
case 'direct':
return 'icon-mail-alt'
default:
return 'icon-globe'
}
},
linkClicked (event) {
let { target } = event
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
if (target.className.match(/mention/)) {
const href = target.href
const attn = this.status.attentions.find(attn => mentionMatchesUrl(attn, href))
if (attn) {
event.stopPropagation()
event.preventDefault()
const link = this.generateUserProfileLink(attn.id, attn.screen_name)
this.$router.push(link)
return
}
}
if (target.className.match(/hashtag/)) {
// Extract tag name from link url
const tag = extractTagFromUrl(target.href)
if (tag) {
const link = this.generateTagLink(tag)
this.$router.push(link)
return
}
}
window.open(target.href, '_blank')
}
},
toggleReplying () {
this.replying = !this.replying
},
gotoOriginal (id) {
// only handled by conversation, not status_or_conversation
if (this.inConversation) {
this.$emit('goto', id)
}
},
toggleExpanded () {
this.$emit('toggleExpanded')
},
toggleMute () {
this.unmuted = !this.unmuted
},
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
toggleShowMore () {
if (this.showingTall) {
this.showingTall = false
} else if (this.expandingSubject && this.status.spoiler_text) {
this.expandingSubject = false
} else if (this.hideTallStatus) {
this.showingTall = true
} else if (this.hideSubjectStatus && this.status.spoiler_text) {
this.expandingSubject = true
}
},
replyEnter (id, event) {
this.showPreview = true
this.previewId = id
if (!this.preview) {
StatusApi.get({ store: this.$store, params: { id } })
.then(status => this.$store.dispatch('addNewStatus', { status }))
}
},
replyLeave () {
this.showPreview = false
},
generateUserProfileLink (id, name) {
return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
},
generateTagLink (tag) {
return `/tag/${tag}`
},
setMedia () {
const attachments = this.attachmentSize === 'hide' ? this.status.media_attachments : this.galleryAttachments
return () => this.$store.dispatch('setMedia', attachments)
}
},
watch: {
'highlight': function (id) {
if (this.status.id === id) {
let rect = this.$el.getBoundingClientRect()
if (rect.top < 100) {
// Post is above screen, match its top to screen top
window.scrollBy(0, rect.top - 100)
} else if (rect.height >= (window.innerHeight - 50)) {
// Post we want to see is taller than screen so match its top to screen top
window.scrollBy(0, rect.top - 100)
} else if (rect.bottom > window.innerHeight - 50) {
// Post is below screen, match its bottom to screen bottom
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
}
},
filters: {
capitalize: function (str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
}
}
export default Status
diff --git a/src/components/user_card_content/user_card_content_two.js b/src/components/user_card_content/user_card_content_two.js
index 512b3102ef..e2672d7305 100644
--- a/src/components/user_card_content/user_card_content_two.js
+++ b/src/components/user_card_content/user_card_content_two.js
@@ -1,142 +1,142 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { requestFollow, requestUnfollow } from '../../services/follow_manipulate/follow_manipulate'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
export default {
props: [ 'user', 'switcher', 'hideBio' ],
data () {
return {
followRequestInProgress: false,
followRequestSent: false,
hideUserStatsLocal: typeof this.$store.state.config.hideUserStats === 'undefined'
? this.$store.state.instance.hideUserStats
: this.$store.state.config.hideUserStats,
betterShadow: this.$store.state.interface.browserSupport.cssFilter
}
},
computed: {
headingStyle () {
const color = this.$store.state.config.customTheme.colors
? this.$store.state.config.customTheme.colors.bg // v2
: this.$store.state.config.colors.bg // v1
if (color) {
const rgb = (typeof color === 'string') ? hex2rgb(color) : color
const tintColor = `rgba(${Math.floor(rgb.r)}, ${Math.floor(rgb.g)}, ${Math.floor(rgb.b)}, .5)`
const gradient = [
[tintColor, this.hideBio ? '60%' : ''],
this.hideBio ? [
color, '100%'
] : [
tintColor, ''
]
].map(_ => _.join(' ')).join(', ')
return {
backgroundColor: `rgb(${Math.floor(rgb.r * 0.53)}, ${Math.floor(rgb.g * 0.56)}, ${Math.floor(rgb.b * 0.59)})`,
backgroundImage: [
`linear-gradient(to bottom, ${gradient})`,
`url(${this.user.cover_photo})`
].join(', ')
}
}
},
isOtherUser () {
- return this.user.id !== this.$store.state.usersTwo.currentUser.id
+ return this.user.id !== this.$store.state.users.currentUser.id
},
subscribeUrl () {
// eslint-disable-next-line no-undef
const serverUrl = new URL(this.user.statusnet_profile_url)
return `${serverUrl.protocol}//${serverUrl.host}/main/ostatus`
},
loggedIn () {
- return this.$store.state.usersTwo.currentUser
+ return this.$store.state.users.currentUser
},
dailyAvg () {
const days = Math.ceil((new Date() - new Date(this.user.created_at)) / (60 * 60 * 24 * 1000))
return Math.round(this.user.statuses_count / days)
},
userHighlightType: {
get () {
const data = this.$store.state.config.highlight[this.user.acct]
return data && data.type || 'disabled'
},
set (type) {
const data = this.$store.state.config.highlight[this.user.acct]
if (type !== 'disabled') {
this.$store.dispatch('setHighlight', { user: this.user.acct, color: data && data.color || '#FFFFFF', type })
} else {
this.$store.dispatch('setHighlight', { user: this.user.acct, color: undefined })
}
}
},
userHighlightColor: {
get () {
const data = this.$store.state.config.highlight[this.user.acct]
return data && data.color
},
set (color) {
this.$store.dispatch('setHighlight', { user: this.user.acct, color })
}
},
visibleRole () {
const validRole = (this.user.role === 'admin' || this.user.role === 'moderator')
const showRole = this.isOtherUser || this.user.show_role
return validRole && showRole && this.user.role
}
},
components: {
UserAvatar
},
methods: {
followUser () {
this.followRequestInProgress = true
requestFollow(this.user, this.$store).then(({ sent }) => {
this.followRequestInProgress = false
this.followRequestSent = sent
})
},
unfollowUser () {
this.followRequestInProgress = true
requestUnfollow(this.user, this.$store).then(() => {
this.followRequestInProgress = false
})
},
blockUser () {
const store = this.$store
store.state.api.backendInteractor.blockUser(this.user.id)
.then((blockedUser) => store.commit('addNewUsers', [blockedUser]))
},
unblockUser () {
const store = this.$store
store.state.api.backendInteractor.unblockUser(this.user.id)
.then((unblockedUser) => store.commit('addNewUsers', [unblockedUser]))
},
toggleMute () {
const store = this.$store
store.commit('setMuted', { user: this.user, muted: !this.user.muted })
store.state.api.backendInteractor.setUserMute(this.user)
},
setProfileView (v) {
// TODO: What is this?
if (this.switcher) {
const store = this.$store
store.commit('setProfileView', { v })
}
},
linkClicked ({ target }) {
if (target.tagName === 'SPAN') {
target = target.parentNode
}
if (target.tagName === 'A') {
window.open(target.href, '_blank')
}
},
userProfileLink (user) {
return generateProfileLink(user.id, user.acct, this.$store.state.instance.restrictedNicknames)
}
}
}
diff --git a/src/components/user_panel/user_panel.js b/src/components/user_panel/user_panel.js
index edeaefba58..7602ebc84c 100644
--- a/src/components/user_panel/user_panel.js
+++ b/src/components/user_panel/user_panel.js
@@ -1,16 +1,16 @@
import LoginForm from '../login_form/login_form.vue'
import PostStatusForm from '../post_status_form/post_status_form.vue'
import UserCardContent from '../user_card_content/user_card_content_two.vue'
const UserPanel = {
computed: {
- user () { return this.$store.state.usersTwo.currentUser }
+ user () { return this.$store.state.users.currentUser }
},
components: {
LoginForm,
PostStatusForm,
UserCardContent
}
}
export default UserPanel
diff --git a/src/components/user_profile/user_profile_two.js b/src/components/user_profile/user_profile_two.js
index 608dabf3a9..3044183ed8 100644
--- a/src/components/user_profile/user_profile_two.js
+++ b/src/components/user_profile/user_profile_two.js
@@ -1,115 +1,115 @@
import { compose } from 'vue-compose'
import get from 'lodash/get'
import UserCardContent from '../user_card_content/user_card_content_two.vue'
import FollowCard from '../follow_card/follow_card.vue'
import Timeline from '../timeline/timeline_two.vue'
import withLoadMore from '../../hocs/with_load_more/with_load_more'
import withList from '../../hocs/with_list/with_list'
const FollowerList = compose(
withLoadMore({
fetch: (props, $store) => $store.dispatch('addFollowers', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'followers', []),
destory: (props, $store) => $store.dispatch('clearFollowers', props.userId),
childPropName: 'entries',
additionalPropNames: ['userId']
}),
withList({ getEntryProps: user => ({ user }) })
)(FollowCard)
const FriendList = compose(
withLoadMore({
fetch: (props, $store) => $store.dispatch('addFriends', props.userId),
select: (props, $store) => get($store.getters.userById(props.userId), 'friends', []),
destory: (props, $store) => $store.dispatch('clearFriends', props.userId),
childPropName: 'entries',
additionalPropNames: ['userId']
}),
withList({ getEntryProps: user => ({ user }) })
)(FollowCard)
const UserProfile = {
data () {
return {
error: false
}
},
created () {
this.$store.dispatch('fetchUsers', { ids: [this.userId] })
this.$store.dispatch('startFetchingTimelineTwo', { type: 'user', timelineName: this.timelineName, params: { id: this.userId } })
this.$store.dispatch('startFetchingTimelineTwo', { type: 'user', timelineName: this.mediaTimelineName, params: { id: this.userId, only_media: true } })
},
destroyed () {
this.$store.dispatch('stopFetchingTimelineTwo', { timelineName: this.timelineName })
this.$store.dispatch('stopFetchingTimelineTwo', { timelineName: this.mediaTimelineName })
},
computed: {
timelineName () {
if (this.userId) {
return `user_timeline_${this.userId}`
} else {
return false
}
},
mediaTimelineName () {
if (this.timelineName) {
return `${this.timelineName}/media`
} else {
return false
}
},
timeline () {
if (this.timelineName) {
return this.$store.state.statusesTwo.timelines[this.timelineName]
} else {
return false
}
},
favorites () {
return this.$store.state.statuses.timelines.favorites
},
mediaTimeline () {
if (this.mediaTimelineName) {
return this.$store.state.statusesTwo.timelines[this.mediaTimelineName]
} else {
return false
}
},
media () {
return this.$store.state.statuses.timelines.media
},
userId () {
return this.$route.params.id || this.user.id
},
userName () {
return this.$route.params.name || this.user.acct
},
isUs () {
- return this.userId && this.$store.state.usersTwo.currentUser.id &&
- this.userId === this.$store.state.usersTwo.currentUser.id
+ return this.userId && this.$store.state.users.currentUser.id &&
+ this.userId === this.$store.state.users.currentUser.id
},
user () {
- return this.$store.state.usersTwo.users[this.userId]
+ return this.$store.state.users.users[this.userId]
},
fetchBy () {
return this.isExternal ? this.userId : this.userName
},
isExternal () {
return this.$route.name === 'external-user-profile'
},
followsTabVisible () {
return this.isUs || !this.user.hide_follows
},
followersTabVisible () {
return this.isUs || !this.user.hide_followers
}
},
components: {
UserCardContent,
Timeline,
FollowerList,
FriendList
}
}
export default UserProfile
diff --git a/src/main.js b/src/main.js
index 329b8683a3..9eb95cda30 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,92 +1,90 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
import Vuex from 'vuex'
import interfaceModule from './modules/interface.js'
import instanceModule from './modules/instance.js'
// import statusesModule from './modules/statuses.js'
import statusesTwoModule from './modules/statuses_two.js'
import notificationsModule from './modules/notifications.js'
-// import usersModule from './modules/users.js'
-import usersTwoModule from './modules/users_two.js'
+import usersModule from './modules/users.js'
// import apiModule from './modules/api.js'
import apiTwoModule from './modules/api_two.js'
import configModule from './modules/config.js'
import chatModule from './modules/chat.js'
import oauthModule from './modules/oauth.js'
import mediaViewerModule from './modules/media_viewer.js'
import oauthTokensModule from './modules/oauth_tokens.js'
import VueTimeago from 'vue-timeago'
import VueI18n from 'vue-i18n'
import createPersistedState from './lib/persisted_state.js'
import pushNotifications from './lib/push_notifications_plugin.js'
import messages from './i18n/messages.js'
import VueChatScroll from 'vue-chat-scroll'
import afterStoreSetup from './boot/after_store.js'
const currentLocale = (window.navigator.language || 'en').split('-')[0]
Vue.use(Vuex)
Vue.use(VueRouter)
Vue.use(VueTimeago, {
locale: currentLocale === 'ja' ? 'ja' : 'en',
locales: {
'en': require('../static/timeago-en.json'),
'ja': require('../static/timeago-ja.json')
}
})
Vue.use(VueI18n)
Vue.use(VueChatScroll)
const i18n = new VueI18n({
// By default, use the browser locale, we will update it if neccessary
locale: currentLocale,
fallbackLocale: 'en',
messages
})
const persistedStateOptions = {
paths: [
'config',
'users.lastLoginName',
'oauth'
]
};
(async function () {
const persistedState = await createPersistedState(persistedStateOptions)
const store = new Vuex.Store({
modules: {
interface: interfaceModule,
instance: instanceModule,
// statuses: statusesModule,
statusesTwo: statusesTwoModule,
notifications: notificationsModule,
- // users: usersModule,
- usersTwo: usersTwoModule,
+ users: usersModule,
// api: apiModule,
apiTwo: apiTwoModule,
config: configModule,
chat: chatModule,
oauth: oauthModule,
mediaViewer: mediaViewerModule,
oauthTokens: oauthTokensModule
},
plugins: [persistedState, pushNotifications],
strict: false // Socket modifies itself, let's ignore this for now.
// strict: process.env.NODE_ENV !== 'production'
})
afterStoreSetup({ store, i18n })
})()
// These are inlined by webpack's DefinePlugin
/* eslint-disable */
window.___pleromafe_mode = process.env
window.___pleromafe_commit_hash = COMMIT_HASH
window.___pleromafe_dev_overrides = DEV_OVERRIDES
diff --git a/src/modules/users.js b/src/modules/users.js
index 79a5bbed20..09695e26f0 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,355 +1,59 @@
-import { compact, map, each, merge, find } from 'lodash'
-import { set } from 'vue'
-import { registerPushNotifications, unregisterPushNotifications } from '../services/push/push.js'
-import oauthApi from '../services/new_api/oauth'
-import { humanizeErrors } from './errors'
-
-// TODO: Unify with mergeOrAdd in statuses.js
-export const mergeOrAdd = (arr, obj, item) => {
- if (!item) { return false }
- const oldItem = obj[item.id]
- if (oldItem) {
- // We already have this, so only merge the new info.
- merge(oldItem, item)
- return { item: oldItem, new: false }
- } else {
- // This is a new item, prepare it
- arr.push(item)
- obj[item.id] = item
- if (item.screen_name && !item.screen_name.includes('@')) {
- obj[item.screen_name] = item
- }
- return { item, new: true }
- }
-}
-
-// const getNotificationPermission = () => {
-// const Notification = window.Notification
-
-// if (!Notification) return Promise.resolve(null)
-// if (Notification.permission === 'default') return Notification.requestPermission()
-// return Promise.resolve(Notification.permission)
-// }
-
-export const mutations = {
- setMuted (state, { user: { id }, muted }) {
- const user = state.usersObject[id]
- set(user, 'muted', muted)
- },
- setCurrentUser (state, user) {
- state.lastLoginName = user.screen_name
- state.currentUser = merge(state.currentUser || {}, user)
- },
- clearCurrentUser (state) {
- state.currentUser = false
- state.lastLoginName = false
- },
- beginLogin (state) {
- state.loggingIn = true
- },
- endLogin (state) {
- state.loggingIn = false
- },
- // TODO Clean after ourselves?
- addFriends (state, { id, friends, page }) {
- const user = state.usersObject[id]
- each(friends, friend => {
- if (!find(user.friends, { id: friend.id })) {
- user.friends.push(friend)
- }
- })
- user.friendsPage = page + 1
- },
- addFollowers (state, { id, followers, page }) {
- const user = state.usersObject[id]
- each(followers, follower => {
- if (!find(user.followers, { id: follower.id })) {
- user.followers.push(follower)
- }
- })
- user.followersPage = page + 1
- },
- // Because frontend doesn't have a reason to keep these stuff in memory
- // outside of viewing someones user profile.
- clearFriends (state, userId) {
- const user = state.usersObject[userId]
- if (!user) {
- return
- }
- user.friends = []
- user.friendsPage = 0
- },
- clearFollowers (state, userId) {
- const user = state.usersObject[userId]
- if (!user) {
- return
- }
- user.followers = []
- user.followersPage = 0
- },
- addNewUsers (state, users) {
- each(users, (user) => mergeOrAdd(state.users, state.usersObject, user))
- },
- saveBlocks (state, blockIds) {
- state.currentUser.blockIds = blockIds
- },
- saveMutes (state, muteIds) {
- state.currentUser.muteIds = muteIds
- },
- setUserForStatus (state, status) {
- status.user = state.usersObject[status.user.id]
- },
- setUserForNotification (state, notification) {
- notification.action.user = state.usersObject[notification.action.user.id]
- notification.from_profile = state.usersObject[notification.action.user.id]
- },
- setColor (state, { user: { id }, highlighted }) {
- const user = state.usersObject[id]
- set(user, 'highlight', highlighted)
- },
- signUpPending (state) {
- state.signUpPending = true
- state.signUpErrors = []
- },
- signUpSuccess (state) {
- state.signUpPending = false
- },
- signUpFailure (state, errors) {
- state.signUpPending = false
- state.signUpErrors = errors
- }
-}
-
-export const getters = {
- userById: state => id =>
- state.users.find(user => user.id === id),
- userByName: state => name =>
- state.users.find(user => user.screen_name &&
- (user.screen_name.toLowerCase() === name.toLowerCase())
- )
-}
-
-export const defaultState = {
- loggingIn: false,
- lastLoginName: false,
- currentUser: false,
- users: [],
- usersObject: {},
- signUpPending: false,
- signUpErrors: []
+import { each, merge, filter, map, uniq } from 'lodash'
+import Vue from 'vue'
+import UsersApi from '../services/new_api/users.js'
+
+const defaultState = {
+ users: {},
+ nameToId: {},
+ currentUser: false
}
const users = {
state: defaultState,
- mutations,
- getters,
- actions: {
- fetchUser (store, id) {
- return store.rootState.api.backendInteractor.fetchUser({ id })
- .then((user) => store.commit('addNewUsers', [user]))
- },
- fetchBlocks (store) {
- return store.rootState.api.backendInteractor.fetchBlocks()
- .then((blocks) => {
- store.commit('saveBlocks', map(blocks, 'id'))
- store.commit('addNewUsers', blocks)
- return blocks
- })
- },
- blockUser (store, id) {
- return store.rootState.api.backendInteractor.blockUser(id)
- .then((user) => store.commit('addNewUsers', [user]))
- },
- unblockUser (store, id) {
- return store.rootState.api.backendInteractor.unblockUser(id)
- .then((user) => store.commit('addNewUsers', [user]))
- },
- fetchMutes (store) {
- return store.rootState.api.backendInteractor.fetchMutes()
- .then((mutedUsers) => {
- each(mutedUsers, (user) => { user.muted = true })
- store.commit('addNewUsers', mutedUsers)
- store.commit('saveMutes', map(mutedUsers, 'id'))
- })
- },
- muteUser (store, id) {
- return store.state.api.backendInteractor.setUserMute({ id, muted: true })
- .then((user) => store.commit('addNewUsers', [user]))
- },
- unmuteUser (store, id) {
- return store.state.api.backendInteractor.setUserMute({ id, muted: false })
- .then((user) => store.commit('addNewUsers', [user]))
- },
- addFriends ({ rootState, commit }, fetchBy) {
- return new Promise((resolve, reject) => {
- const user = rootState.users.usersObject[fetchBy]
- const page = user.friendsPage || 1
- rootState.api.backendInteractor.fetchFriends({ id: user.id, page })
- .then((friends) => {
- commit('addFriends', { id: user.id, friends, page })
- resolve(friends)
- }).catch(() => {
- reject()
- })
- })
- },
- addFollowers ({ rootState, commit }, fetchBy) {
- const user = rootState.users.usersObject[fetchBy]
- const page = user.followersPage || 1
- return rootState.api.backendInteractor.fetchFollowers({ id: user.id, page })
- .then((followers) => {
- commit('addFollowers', { id: user.id, followers, page })
- return followers
- })
- },
- clearFriends ({ commit }, userId) {
- commit('clearFriends', userId)
- },
- clearFollowers ({ commit }, userId) {
- commit('clearFollowers', userId)
- },
- registerPushNotifications (store) {
- const token = store.state.currentUser.credentials
- const vapidPublicKey = store.rootState.instance.vapidPublicKey
- const isEnabled = store.rootState.config.webPushNotifications
- const notificationVisibility = store.rootState.config.notificationVisibility
-
- registerPushNotifications(isEnabled, vapidPublicKey, token, notificationVisibility)
- },
- unregisterPushNotifications (store) {
- const token = store.state.currentUser.credentials
-
- unregisterPushNotifications(token)
- },
- addNewStatuses (store, { statuses }) {
- const users = map(statuses, 'user')
- const retweetedUsers = compact(map(statuses, 'retweeted_status.user'))
- store.commit('addNewUsers', users)
- store.commit('addNewUsers', retweetedUsers)
-
- // Reconnect users to statuses
- each(statuses, (status) => {
- store.commit('setUserForStatus', status)
- })
- // Reconnect users to retweets
- each(compact(map(statuses, 'retweeted_status')), (status) => {
- store.commit('setUserForStatus', status)
+ mutations: {
+ addNewUsersTwo (state, newUsers) {
+ each(newUsers, (user) => {
+ Vue.set(state.users, user.id, merge(state.users[user.id] || {}, user))
+ Vue.set(state.nameToId, user.acct, user.id)
})
},
- addNewNotifications (store, { notifications }) {
- const users = map(notifications, 'from_profile')
- const notificationIds = notifications.map(_ => _.id)
- store.commit('addNewUsers', users)
-
- const notificationsObject = store.rootState.statuses.notifications.idStore
- const relevantNotifications = Object.entries(notificationsObject)
- .filter(([k, val]) => notificationIds.includes(k))
- .map(([k, val]) => val)
-
- // Reconnect users to notifications
- each(relevantNotifications, (notification) => {
- store.commit('setUserForNotification', notification)
+ setCurrentUserTwo (state, user) {
+ Vue.set(state, 'currentUser', user)
+ }
+ },
+ actions: {
+ fetchUsers ({ commit, state, rootState }, { ids }) {
+ each(uniq(ids), (id) => {
+ UsersApi.get({ store: { state: rootState }, params: { id } })
+ .then((account) => commit('addNewUsersTwo', [account]))
})
},
- async signUp (store, userInfo) {
- store.commit('signUpPending')
-
- let rootState = store.rootState
-
- let response = await rootState.api.backendInteractor.register(userInfo)
- if (response.ok) {
- const data = {
- oauth: rootState.oauth,
- instance: rootState.instance.server
- }
- let app = await oauthApi.getOrCreateApp(data)
- let result = await oauthApi.getTokenWithCredentials({
- app,
- instance: data.instance,
- username: userInfo.username,
- password: userInfo.password
- })
- store.commit('signUpSuccess')
- store.commit('setToken', result.access_token)
- store.dispatch('loginUser', result.access_token)
- } else {
- const data = await response.json()
- let errors = JSON.parse(data.error)
- // replace ap_id with username
- if (errors.ap_id) {
- errors.username = errors.ap_id
- delete errors.ap_id
- }
- errors = humanizeErrors(errors)
- store.commit('signUpFailure', errors)
- throw Error(errors)
+ fetchMissingRepliedUsers ({ state, dispatch }, { statuses }) {
+ const repliedToUsers = map(statuses, 'in_reply_to_account_id')
+ const missingUsers = filter(repliedToUsers, (id) => id && (!state.users[id]))
+ dispatch('fetchUsers', { ids: missingUsers })
+ },
+ async loginUser (store, accessToken) {
+ store.commit('beginLogin')
+ try {
+ const currentUser = await UsersApi.verifyCredentials({ store: { ...store, state: store.rootState }, params: { accessToken } })
+
+ store.commit('addNewUsersTwo', [currentUser])
+ store.commit('setCurrentUserTwo', currentUser)
+ store.dispatch('startFetchingTimelineTwo', { timelineName: 'home', type: 'home' })
+ } catch (error) {
+ console.log('Could not log in')
+ console.log(error)
+ } finally {
+ store.commit('endLogin')
}
},
- async getCaptcha (store) {
- return await store.rootState.api.backendInteractor.getCaptcha()
- },
-
- logout (store) {
- // store.commit('clearCurrentUser')
- // store.commit('setToken', false)
- // store.dispatch('stopFetching', 'friends')
- // store.commit('setBackendInteractor', backendInteractorService())
- },
- loginUser (store, accessToken) {
- // return new Promise((resolve, reject) => {
- // const commit = store.commit
- // commit('beginLogin')
- // store.rootState.api.backendInteractor.verifyCredentials(accessToken)
- // .then((data) => {
- // if (!data.error) {
- // const user = data
- // // user.credentials = userCredentials
- // user.credentials = accessToken
- // user.blockIds = []
- // user.muteIds = []
- // commit('setCurrentUser', user)
- // commit('addNewUsers', [user])
-
- // getNotificationPermission()
- // .then(permission => commit('setNotificationPermission', permission))
-
- // // Set our new backend interactor
- // commit('setBackendInteractor', backendInteractorService(accessToken))
-
- // if (user.token) {
- // store.dispatch('setWsToken', user.token)
- // }
-
- // // Start getting fresh posts.
- // store.dispatch('startFetching', { timeline: 'friends' })
-
- // // Get user mutes
- // store.dispatch('fetchMutes')
-
- // // Fetch our friends
- // store.rootState.api.backendInteractor.fetchFriends({ id: user.id })
- // .then((friends) => commit('addNewUsers', friends))
- // } else {
- // const response = data.error
- // // Authentication failed
- // commit('endLogin')
- // if (response.status === 401) {
- // reject('Wrong username or password')
- // } else {
- // reject('An error occurred, please try again')
- // }
- // }
- // commit('endLogin')
- // resolve()
- // })
- // .catch((error) => {
- // console.log(error)
- // commit('endLogin')
- // reject('Failed to connect to server, try again')
- // })
- // })
+ async logoutUser ({ commit, dispatch }) {
+ commit('setCurrentUserTwo', false)
+ commit('setToken', false)
+ dispatch('stopFetchingTimelineTwo', { timelineName: 'home' })
}
}
}
export default users
diff --git a/src/modules/users_two.js b/src/modules/users_two.js
deleted file mode 100644
index 09695e26f0..0000000000
--- a/src/modules/users_two.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import { each, merge, filter, map, uniq } from 'lodash'
-import Vue from 'vue'
-import UsersApi from '../services/new_api/users.js'
-
-const defaultState = {
- users: {},
- nameToId: {},
- currentUser: false
-}
-
-const users = {
- state: defaultState,
- mutations: {
- addNewUsersTwo (state, newUsers) {
- each(newUsers, (user) => {
- Vue.set(state.users, user.id, merge(state.users[user.id] || {}, user))
- Vue.set(state.nameToId, user.acct, user.id)
- })
- },
- setCurrentUserTwo (state, user) {
- Vue.set(state, 'currentUser', user)
- }
- },
- actions: {
- fetchUsers ({ commit, state, rootState }, { ids }) {
- each(uniq(ids), (id) => {
- UsersApi.get({ store: { state: rootState }, params: { id } })
- .then((account) => commit('addNewUsersTwo', [account]))
- })
- },
- fetchMissingRepliedUsers ({ state, dispatch }, { statuses }) {
- const repliedToUsers = map(statuses, 'in_reply_to_account_id')
- const missingUsers = filter(repliedToUsers, (id) => id && (!state.users[id]))
- dispatch('fetchUsers', { ids: missingUsers })
- },
- async loginUser (store, accessToken) {
- store.commit('beginLogin')
- try {
- const currentUser = await UsersApi.verifyCredentials({ store: { ...store, state: store.rootState }, params: { accessToken } })
-
- store.commit('addNewUsersTwo', [currentUser])
- store.commit('setCurrentUserTwo', currentUser)
- store.dispatch('startFetchingTimelineTwo', { timelineName: 'home', type: 'home' })
- } catch (error) {
- console.log('Could not log in')
- console.log(error)
- } finally {
- store.commit('endLogin')
- }
- },
- async logoutUser ({ commit, dispatch }) {
- commit('setCurrentUserTwo', false)
- commit('setToken', false)
- dispatch('stopFetchingTimelineTwo', { timelineName: 'home' })
- }
- }
-}
-
-export default users

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 10, 7:50 PM (1 d, 10 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785432
Default Alt Text
(69 KB)

Event Timeline