Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85804978
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
69 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Oct 10, 7:50 PM (1 d, 8 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785432
Default Alt Text
(69 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline
Log In to Comment