forked from AkkomaGang/akkoma-fe
Merge branch 'develop' into feature/normal-emoji-completion
This commit is contained in:
commit
b67c506062
36 changed files with 457 additions and 196 deletions
|
@ -23,9 +23,15 @@ module.exports = {
|
||||||
assetsPublicPath: '/',
|
assetsPublicPath: '/',
|
||||||
proxyTable: {
|
proxyTable: {
|
||||||
'/api': {
|
'/api': {
|
||||||
target: 'https://social.heldscal.la/',
|
target: 'htts://localhost:4000/',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
cookieDomainRewrite: 'localhost'
|
cookieDomainRewrite: 'localhost'
|
||||||
|
},
|
||||||
|
'/socket': {
|
||||||
|
target: 'htts://localhost:4000/',
|
||||||
|
changeOrigin: true,
|
||||||
|
cookieDomainRewrite: 'localhost',
|
||||||
|
ws: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// CSS Sourcemaps off by default because relative paths are "buggy"
|
// CSS Sourcemaps off by default because relative paths are "buggy"
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
"localforage": "^1.5.0",
|
"localforage": "^1.5.0",
|
||||||
"node-sass": "^3.10.1",
|
"node-sass": "^3.10.1",
|
||||||
"object-path": "^0.11.3",
|
"object-path": "^0.11.3",
|
||||||
|
"phoenix": "^1.3.0",
|
||||||
"sanitize-html": "^1.13.0",
|
"sanitize-html": "^1.13.0",
|
||||||
"sass-loader": "^4.0.2",
|
"sass-loader": "^4.0.2",
|
||||||
"vue": "^2.3.4",
|
"vue": "^2.3.4",
|
||||||
|
|
18
src/App.scss
18
src/App.scss
|
@ -16,7 +16,13 @@ h4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
|
box-sizing: border-box;
|
||||||
padding-top: 60px;
|
padding-top: 60px;
|
||||||
|
margin: auto;
|
||||||
|
min-height: 100vh;
|
||||||
|
max-width: 980px;
|
||||||
|
background-color: rgba(0,0,0,0.15);
|
||||||
|
align-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.text-center {
|
||||||
|
@ -157,15 +163,6 @@ main-router {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#content {
|
|
||||||
margin: auto;
|
|
||||||
min-height: 100vh;
|
|
||||||
max-width: 980px;
|
|
||||||
padding-bottom: 1em;
|
|
||||||
background-color: rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container > * {
|
.container > * {
|
||||||
min-width: 0px;
|
min-width: 0px;
|
||||||
}
|
}
|
||||||
|
@ -210,10 +207,11 @@ nav {
|
||||||
.panel-switcher {
|
.panel-switcher {
|
||||||
display: none;
|
display: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 46px;
|
||||||
button {
|
button {
|
||||||
display: block;
|
display: block;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-height: 32px;
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,6 +34,13 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-right: -0.7em;
|
margin-right: -0.7em;
|
||||||
|
|
||||||
|
.attachment.media-upload-container {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
max-height: 300px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
flex: 1 0 30%;
|
flex: 1 0 30%;
|
||||||
margin: 0.5em 0.7em 0.6em 0.0em;
|
margin: 0.5em 0.7em 0.6em 0.0em;
|
||||||
|
@ -82,9 +89,7 @@
|
||||||
img.media-upload {
|
img.media-upload {
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
width: 100%;
|
max-width: 100%;
|
||||||
height: 100%;
|
|
||||||
flex: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.oembed {
|
.oembed {
|
||||||
|
@ -126,6 +131,7 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%; /* If this isn't here, chrome will stretch the images */
|
height: 100%; /* If this isn't here, chrome will stretch the images */
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
|
image-orientation: from-image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
src/components/chat/chat.js
Normal file
21
src/components/chat/chat.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const chat = {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
currentMessage: '',
|
||||||
|
channel: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
messages () {
|
||||||
|
return this.$store.state.chat.messages
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit (message) {
|
||||||
|
this.$store.state.chat.channel.push('new_msg', {text: message}, 10000)
|
||||||
|
this.currentMessage = ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default chat
|
59
src/components/chat/chat.vue
Normal file
59
src/components/chat/chat.vue
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<template>
|
||||||
|
<div class="chat-panel panel panel-default">
|
||||||
|
<div class="panel-heading timeline-heading base02-background base04">
|
||||||
|
<div class="title">
|
||||||
|
{{$t('chat.title')}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body base01-background">
|
||||||
|
<div class="chat-window">
|
||||||
|
<div class="chat-message" v-for="message in messages" :key="message.id">
|
||||||
|
<span class="chat-avatar">
|
||||||
|
<img :src="message.author.avatar" />
|
||||||
|
{{message.author.username}}:
|
||||||
|
</span>
|
||||||
|
<span class="chat-text">
|
||||||
|
{{message.text}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="chat-input">
|
||||||
|
<form @submit.prevent="submit(currentMessage)">
|
||||||
|
<input v-model="currentMessage" type="text" >
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="./chat.js"></script>
|
||||||
|
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.chat-window {
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
.chat-message {
|
||||||
|
padding: 0.2em 0.5em
|
||||||
|
}
|
||||||
|
.chat-avatar {
|
||||||
|
img {
|
||||||
|
height: 32px;
|
||||||
|
width: 32px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chat-input {
|
||||||
|
display: flex;
|
||||||
|
form {
|
||||||
|
flex: auto;
|
||||||
|
input {
|
||||||
|
margin: 0.5em;
|
||||||
|
width: fill-available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="canDelete">
|
<div v-if="canDelete">
|
||||||
<a href="#" v-on:click.prevent="deleteStatus()">
|
<a href="#" v-on:click.prevent="deleteStatus()">
|
||||||
<i class='fa icon-cancel delete-status'></i>
|
<i class='base09 icon-cancel delete-status'></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<i :class='classes' class='favorite-button fa' @click.prevent='favorite()'/>
|
<i :class='classes' class='favorite-button base09' @click.prevent='favorite()'/>
|
||||||
<span v-if='status.fave_num > 0'>{{status.fave_num}}</span>
|
<span v-if='status.fave_num > 0'>{{status.fave_num}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.icon-star {
|
.favorite-button.icon-star {
|
||||||
color: orange;
|
color: orange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop">
|
<div class="media-upload" @drop.prevent @dragover.prevent="fileDrag" @drop="fileDrop">
|
||||||
<label class="btn btn-default">
|
<label class="btn btn-default">
|
||||||
<i class="fa icon-spin4 animate-spin" v-if="uploading"></i>
|
<i class="base09 icon-spin4 animate-spin" v-if="uploading"></i>
|
||||||
<i class="fa icon-upload" v-if="!uploading"></i>
|
<i class="base09 icon-upload" v-if="!uploading"></i>
|
||||||
<input type=file style="position: fixed; top: -100em"></input>
|
<input type=file style="position: fixed; top: -100em"></input>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,9 @@ const NavPanel = {
|
||||||
computed: {
|
computed: {
|
||||||
currentUser () {
|
currentUser () {
|
||||||
return this.$store.state.users.currentUser
|
return this.$store.state.users.currentUser
|
||||||
|
},
|
||||||
|
chat () {
|
||||||
|
return this.$store.state.chat.channel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,11 @@
|
||||||
{{ $t("nav.timeline") }}
|
{{ $t("nav.timeline") }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if='chat && currentUser'>
|
||||||
|
<router-link class="base00-background" to='/chat'>
|
||||||
|
{{ $t("nav.chat") }}
|
||||||
|
</router-link>
|
||||||
|
</li>
|
||||||
<li v-if='currentUser'>
|
<li v-if='currentUser'>
|
||||||
<router-link class="base00-background" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
<router-link class="base00-background" :to="{ name: 'mentions', params: { username: currentUser.screen_name } }">
|
||||||
{{ $t("nav.mentions") }}
|
{{ $t("nav.mentions") }}
|
||||||
|
|
|
@ -59,6 +59,10 @@
|
||||||
color: $blue;
|
color: $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-star.lit {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
.status-content {
|
.status-content {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-height: 300px;
|
max-height: 300px;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<div class="panel-heading base02-background base04">
|
<div class="panel-heading base02-background base04">
|
||||||
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
|
<span class="unseen-count" v-if="unseenCount">{{unseenCount}}</span>
|
||||||
{{$t('notifications.notifications')}}
|
{{$t('notifications.notifications')}}
|
||||||
<button @click.prevent="markAsSeen" class="base04 base02-background read-button">{{$t('notifications.read')}}</button>
|
<button v-if="unseenCount" @click.prevent="markAsSeen" class="base04 base02-background read-button">{{$t('notifications.read')}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body base03-border">
|
<div class="panel-body base03-border">
|
||||||
<div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'>
|
<div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
<div v-if="notification.type === 'favorite'">
|
<div v-if="notification.type === 'favorite'">
|
||||||
<h1>
|
<h1>
|
||||||
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
|
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
|
||||||
<i class="fa icon-star"></i>
|
<i class="fa icon-star lit"></i>
|
||||||
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
|
<small><router-link :to="{ name: 'conversation', params: { id: notification.status.id } }"><timeago :since="notification.action.created_at" :auto-update="240"></timeago></router-link></small>
|
||||||
</h1>
|
</h1>
|
||||||
<div class="notification-gradient" :style="hiderStyle"></div>
|
<div class="notification-gradient" :style="hiderStyle"></div>
|
||||||
|
|
|
@ -157,6 +157,14 @@ const PostStatusForm = {
|
||||||
type (fileInfo) {
|
type (fileInfo) {
|
||||||
return fileTypeService.fileType(fileInfo.mimetype)
|
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) {
|
fileDrop (e) {
|
||||||
if (e.dataTransfer.files.length > 0) {
|
if (e.dataTransfer.files.length > 0) {
|
||||||
e.preventDefault() // allow dropping text like before
|
e.preventDefault() // allow dropping text like before
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="post-status-form">
|
<div class="post-status-form">
|
||||||
<form @submit.prevent="postStatus(newStatus)">
|
<form @submit.prevent="postStatus(newStatus)">
|
||||||
<div class="form-group base03-border" >
|
<div class="form-group base03-border" >
|
||||||
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
|
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" :placeholder="$t('post_status.default')" rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize" @paste="paste"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div style="position:relative;" v-if="candidates">
|
<div style="position:relative;" v-if="candidates">
|
||||||
<div class="autocomplete-panel base05-background">
|
<div class="autocomplete-panel base05-background">
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
<i class="icon-cancel" @click="clearError"></i>
|
<i class="icon-cancel" @click="clearError"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="attachments">
|
<div class="attachments">
|
||||||
<div class="attachment base03-border" v-for="file in newStatus.files">
|
<div class="media-upload-container attachment base03-border" v-for="file in newStatus.files">
|
||||||
<i class="fa icon-cancel" @click="removeMediaFile(file)"></i>
|
<i class="fa icon-cancel" @click="removeMediaFile(file)"></i>
|
||||||
<img class="thumbnail media-upload" :src="file.image" v-if="type(file) === 'image'"></img>
|
<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>
|
<video v-if="type(file) === 'video'" :src="file.image" controls></video>
|
||||||
|
@ -41,6 +41,7 @@
|
||||||
<script src="./post_status_form.js"></script>
|
<script src="./post_status_form.js"></script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
||||||
.tribute-container {
|
.tribute-container {
|
||||||
ul {
|
ul {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<i :class='classes' class='icon-retweet fa' v-on:click.prevent='retweet()'></i>
|
<i :class='classes' class='icon-retweet base09' v-on:click.prevent='retweet()'></i>
|
||||||
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
|
<span v-if='status.repeat_num > 0'>{{status.repeat_num}}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
color: $green;
|
color: $green;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.retweeted {
|
.icon-retweet.retweeted {
|
||||||
color: $green;
|
color: $green;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class='status-actions'>
|
<div class='status-actions'>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" v-on:click.prevent="toggleReplying">
|
<a href="#" v-on:click.prevent="toggleReplying">
|
||||||
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
|
<i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<retweet-button :status=status></retweet-button>
|
<retweet-button :status=status></retweet-button>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
<div class="media status container muted">
|
<div class="media status container muted">
|
||||||
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
|
||||||
<small class="muteWords">{{muteWordHits.join(', ')}}</small>
|
<small class="muteWords">{{muteWordHits.join(', ')}}</small>
|
||||||
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="fa icon-eye-off"></i></a>
|
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="base09 icon-eye-off"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="!muted">
|
<template v-if="!muted">
|
||||||
|
@ -75,10 +75,10 @@
|
||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="heading-icons">
|
<div class="heading-icons">
|
||||||
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="fa icon-eye-off"></i></a>
|
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="base09 icon-eye-off"></i></a>
|
||||||
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="fa icon-binoculars"></i></a>
|
<a :href="status.external_url" target="_blank" v-if="!status.is_local" class="source_url"><i class="base09 icon-binoculars"></i></a>
|
||||||
<template v-if="expandable">
|
<template v-if="expandable">
|
||||||
<a href="#" @click.prevent="toggleExpanded" class="expand"><i class="fa icon-plus-squared"></i></a>
|
<a href="#" @click.prevent="toggleExpanded" class="expand"><i class="base09 icon-plus-squared"></i></a>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-preview status-preview-loading base00-background base03-border" v-else-if="showPreview">
|
<div class="status-preview status-preview-loading base00-background base03-border" v-else-if="showPreview">
|
||||||
<i class="fa icon-spin4 animate-spin"></i>
|
<i class="base09 icon-spin4 animate-spin"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
|
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
|
||||||
|
@ -109,7 +109,7 @@
|
||||||
<div class='status-actions'>
|
<div class='status-actions'>
|
||||||
<div>
|
<div>
|
||||||
<a href="#" v-on:click.prevent="toggleReplying">
|
<a href="#" v-on:click.prevent="toggleReplying">
|
||||||
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
|
<i class="base09 icon-reply" :class="{'icon-reply-active': replying}"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<retweet-button :status=status></retweet-button>
|
<retweet-button :status=status></retweet-button>
|
||||||
|
@ -324,7 +324,7 @@
|
||||||
color: $blue;
|
color: $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-reply-active {
|
.icon-reply.icon-reply-active {
|
||||||
color: $blue;
|
color: $blue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { rgbstr2hex } from '../../services/color_convert/color_convert.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -19,13 +21,6 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
const rgbstr2hex = (rgb) => {
|
|
||||||
if (rgb[0] === '#') {
|
|
||||||
return rgb
|
|
||||||
}
|
|
||||||
rgb = rgb.match(/\d+/g)
|
|
||||||
return `#${((Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16)}`
|
|
||||||
}
|
|
||||||
this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors['base00'])
|
this.bgColorLocal = rgbstr2hex(this.$store.state.config.colors['base00'])
|
||||||
this.fgColorLocal = rgbstr2hex(this.$store.state.config.colors['base02'])
|
this.fgColorLocal = rgbstr2hex(this.$store.state.config.colors['base02'])
|
||||||
this.textColorLocal = rgbstr2hex(this.$store.state.config.colors['base05'])
|
this.textColorLocal = rgbstr2hex(this.$store.state.config.colors['base05'])
|
||||||
|
|
|
@ -29,6 +29,13 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
newStatusCount () {
|
newStatusCount () {
|
||||||
return this.timeline.newStatusCount
|
return this.timeline.newStatusCount
|
||||||
|
},
|
||||||
|
newStatusCountStr () {
|
||||||
|
if (this.timeline.flushMarker !== 0) {
|
||||||
|
return ''
|
||||||
|
} else {
|
||||||
|
return ` (${this.newStatusCount})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
@ -64,8 +71,14 @@ const Timeline = {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
showNewStatuses () {
|
showNewStatuses () {
|
||||||
|
if (this.timeline.flushMarker !== 0) {
|
||||||
|
this.$store.commit('clearTimeline', { timeline: this.timelineName })
|
||||||
|
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
|
||||||
|
this.fetchOlderStatuses()
|
||||||
|
} else {
|
||||||
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
|
this.$store.commit('showNewStatuses', { timeline: this.timelineName })
|
||||||
this.paused = false
|
this.paused = false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
fetchOlderStatuses () {
|
fetchOlderStatuses () {
|
||||||
const store = this.$store
|
const store = this.$store
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
{{title}}
|
{{title}}
|
||||||
</div>
|
</div>
|
||||||
<button @click.prevent="showNewStatuses" class="base05 base02-background loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
|
<button @click.prevent="showNewStatuses" class="base05 base02-background loadmore-button" v-if="timeline.newStatusCount > 0 && !timelineError">
|
||||||
{{$t('timeline.show_new')}} ({{timeline.newStatusCount}})
|
{{$t('timeline.show_new')}}{{newStatusCountStr}}
|
||||||
</button>
|
</button>
|
||||||
<div @click.prevent class="base06 error loadmore-text" v-if="timelineError">
|
<div @click.prevent class="base06 error loadmore-text" v-if="timelineError">
|
||||||
{{$t('timeline.error_fetching')}}
|
{{$t('timeline.error_fetching')}}
|
||||||
|
|
|
@ -59,13 +59,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.usercard {
|
.usercard {
|
||||||
width: -webkit-fill-available;
|
width: fill-available;
|
||||||
width: -moz-webkit-fill-available;
|
|
||||||
stretch: fill;
|
|
||||||
margin: 0.2em 0 0.7em 0;
|
margin: 0.2em 0 0.7em 0;
|
||||||
border-radius: 5px;
|
border-radius: 10px;
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-color: inherit;
|
border-color: inherit;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
64
src/components/user_card_content/user_card_content.js
Normal file
64
src/components/user_card_content/user_card_content.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { hex2rgb } from '../../services/color_convert/color_convert.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: [ 'user', 'switcher' ],
|
||||||
|
computed: {
|
||||||
|
headingStyle () {
|
||||||
|
const color = this.$store.state.config.colors['base00']
|
||||||
|
if (color) {
|
||||||
|
const rgb = hex2rgb(color)
|
||||||
|
console.log(rgb)
|
||||||
|
return {
|
||||||
|
backgroundColor: `rgb(${Math.floor(rgb[0] * 0.53)}, ${Math.floor(rgb[1] * 0.56)}, ${Math.floor(rgb[2] * 0.59)})`,
|
||||||
|
backgroundImage: `url(${this.user.cover_photo})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bodyStyle () {
|
||||||
|
return {
|
||||||
|
background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isOtherUser () {
|
||||||
|
return this.user.id !== this.$store.state.users.currentUser.id
|
||||||
|
},
|
||||||
|
loggedIn () {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
followUser () {
|
||||||
|
const store = this.$store
|
||||||
|
store.state.api.backendInteractor.followUser(this.user.id)
|
||||||
|
.then((followedUser) => store.commit('addNewUsers', [followedUser]))
|
||||||
|
},
|
||||||
|
unfollowUser () {
|
||||||
|
const store = this.$store
|
||||||
|
store.state.api.backendInteractor.unfollowUser(this.user.id)
|
||||||
|
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
|
||||||
|
},
|
||||||
|
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) {
|
||||||
|
const store = this.$store
|
||||||
|
store.commit('setProfileView', { v })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -84,69 +84,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script src="./user_card_content.js"></script>
|
||||||
export default {
|
|
||||||
props: [ 'user', 'switcher' ],
|
|
||||||
computed: {
|
|
||||||
headingStyle () {
|
|
||||||
let color = this.$store.state.config.colors['base00']
|
|
||||||
if (color) {
|
|
||||||
let rgb = this.$store.state.config.colors['base00'].match(/\d+/g)
|
|
||||||
return {
|
|
||||||
backgroundColor: `rgb(${Math.floor(rgb[0] * 0.53)}, ${Math.floor(rgb[1] * 0.56)}, ${Math.floor(rgb[2] * 0.59)})`,
|
|
||||||
backgroundImage: `url(${this.user.cover_photo})`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
bodyStyle () {
|
|
||||||
return {
|
|
||||||
background: `linear-gradient(to bottom, rgba(0, 0, 0, 0), ${this.$store.state.config.colors['base00']} 80%)`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isOtherUser () {
|
|
||||||
return this.user.id !== this.$store.state.users.currentUser.id
|
|
||||||
},
|
|
||||||
loggedIn () {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
followUser () {
|
|
||||||
const store = this.$store
|
|
||||||
store.state.api.backendInteractor.followUser(this.user.id)
|
|
||||||
.then((followedUser) => store.commit('addNewUsers', [followedUser]))
|
|
||||||
},
|
|
||||||
unfollowUser () {
|
|
||||||
const store = this.$store
|
|
||||||
store.state.api.backendInteractor.unfollowUser(this.user.id)
|
|
||||||
.then((unfollowedUser) => store.commit('addNewUsers', [unfollowedUser]))
|
|
||||||
},
|
|
||||||
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) {
|
|
||||||
const store = this.$store
|
|
||||||
store.commit('setProfileView', { v })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
@ -164,7 +102,6 @@
|
||||||
.profile-panel-body {
|
.profile-panel-body {
|
||||||
top: -0em;
|
top: -0em;
|
||||||
padding-top: 4em;
|
padding-top: 4em;
|
||||||
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
<div>
|
<div>
|
||||||
<input type="file" @change="uploadFile(0, $event)" ></input>
|
<input type="file" @change="uploadFile(0, $event)" ></input>
|
||||||
</div>
|
</div>
|
||||||
<i class="fa icon-spin4 animate-spin" v-if="uploading[0]"></i>
|
<i class="base09 icon-spin4 animate-spin" v-if="uploading[0]"></i>
|
||||||
<button class="btn btn-default base05 base02-background" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
|
<button class="btn btn-default base05 base02-background" v-else-if="previews[0]" @click="submitAvatar">{{$t('general.submit')}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<div>
|
<div>
|
||||||
<input type="file" @change="uploadFile(1, $event)" ></input>
|
<input type="file" @change="uploadFile(1, $event)" ></input>
|
||||||
</div>
|
</div>
|
||||||
<i class="fa icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
|
<i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[1]"></i>
|
||||||
<button class="btn btn-default base05 base02-background" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
|
<button class="btn btn-default base05 base02-background" v-else-if="previews[1]" @click="submitBanner">{{$t('general.submit')}}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
<div>
|
<div>
|
||||||
<input type="file" @change="uploadFile(2, $event)" ></input>
|
<input type="file" @change="uploadFile(2, $event)" ></input>
|
||||||
</div>
|
</div>
|
||||||
<i class="fa icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
|
<i class="base09 icon-spin4 animate-spin uploading" v-if="uploading[2]"></i>
|
||||||
<button class="btn btn-default base05 base02-background" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
|
<button class="btn btn-default base05 base02-background" v-else-if="previews[2]" @click="submitBg">{{$t('general.submit')}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
const de = {
|
const de = {
|
||||||
|
chat: {
|
||||||
|
title: 'Chat'
|
||||||
|
},
|
||||||
nav: {
|
nav: {
|
||||||
|
chat: 'Lokaler Chat',
|
||||||
timeline: 'Zeitleiste',
|
timeline: 'Zeitleiste',
|
||||||
mentions: 'Erwähnungen',
|
mentions: 'Erwähnungen',
|
||||||
public_tl: 'Lokale Zeitleiste',
|
public_tl: 'Lokale Zeitleiste',
|
||||||
|
@ -179,7 +183,11 @@ const fi = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const en = {
|
const en = {
|
||||||
|
chat: {
|
||||||
|
title: 'Chat'
|
||||||
|
},
|
||||||
nav: {
|
nav: {
|
||||||
|
chat: 'Local Chat',
|
||||||
timeline: 'Timeline',
|
timeline: 'Timeline',
|
||||||
mentions: 'Mentions',
|
mentions: 'Mentions',
|
||||||
public_tl: 'Public Timeline',
|
public_tl: 'Public Timeline',
|
||||||
|
|
75
src/main.js
75
src/main.js
|
@ -12,11 +12,13 @@ import UserProfile from './components/user_profile/user_profile.vue'
|
||||||
import Settings from './components/settings/settings.vue'
|
import Settings from './components/settings/settings.vue'
|
||||||
import Registration from './components/registration/registration.vue'
|
import Registration from './components/registration/registration.vue'
|
||||||
import UserSettings from './components/user_settings/user_settings.vue'
|
import UserSettings from './components/user_settings/user_settings.vue'
|
||||||
|
import Chat from './components/chat/chat.vue'
|
||||||
|
|
||||||
import statusesModule from './modules/statuses.js'
|
import statusesModule from './modules/statuses.js'
|
||||||
import usersModule from './modules/users.js'
|
import usersModule from './modules/users.js'
|
||||||
import apiModule from './modules/api.js'
|
import apiModule from './modules/api.js'
|
||||||
import configModule from './modules/config.js'
|
import configModule from './modules/config.js'
|
||||||
|
import chatModule from './modules/chat.js'
|
||||||
|
|
||||||
import VueTimeago from 'vue-timeago'
|
import VueTimeago from 'vue-timeago'
|
||||||
import VueI18n from 'vue-i18n'
|
import VueI18n from 'vue-i18n'
|
||||||
|
@ -57,14 +59,35 @@ const store = new Vuex.Store({
|
||||||
statuses: statusesModule,
|
statuses: statusesModule,
|
||||||
users: usersModule,
|
users: usersModule,
|
||||||
api: apiModule,
|
api: apiModule,
|
||||||
config: configModule
|
config: configModule,
|
||||||
|
chat: chatModule
|
||||||
},
|
},
|
||||||
plugins: [createPersistedState(persistedStateOptions)],
|
plugins: [createPersistedState(persistedStateOptions)],
|
||||||
strict: process.env.NODE_ENV !== 'production'
|
strict: false // Socket modifies itself, let's ignore this for now.
|
||||||
|
// strict: process.env.NODE_ENV !== 'production'
|
||||||
})
|
})
|
||||||
|
|
||||||
const routes = [
|
const i18n = new VueI18n({
|
||||||
{ name: 'root', path: '/', redirect: '/main/all' },
|
locale: currentLocale,
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages
|
||||||
|
})
|
||||||
|
|
||||||
|
window.fetch('/static/config.json')
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then((data) => {
|
||||||
|
const {name, theme, background, logo, registrationOpen} = data
|
||||||
|
store.dispatch('setOption', { name: 'name', value: name })
|
||||||
|
store.dispatch('setOption', { name: 'theme', value: theme })
|
||||||
|
store.dispatch('setOption', { name: 'background', value: background })
|
||||||
|
store.dispatch('setOption', { name: 'logo', value: logo })
|
||||||
|
store.dispatch('setOption', { name: 'registrationOpen', value: registrationOpen })
|
||||||
|
if (data['chatDisabled']) {
|
||||||
|
store.dispatch('disableChat')
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{ name: 'root', path: '/', redirect: data['defaultPath'] || '/main/all' },
|
||||||
{ path: '/main/all', component: PublicAndExternalTimeline },
|
{ path: '/main/all', component: PublicAndExternalTimeline },
|
||||||
{ path: '/main/public', component: PublicTimeline },
|
{ path: '/main/public', component: PublicTimeline },
|
||||||
{ path: '/main/friends', component: FriendsTimeline },
|
{ path: '/main/friends', component: FriendsTimeline },
|
||||||
|
@ -74,10 +97,11 @@ const routes = [
|
||||||
{ name: 'mentions', path: '/:username/mentions', component: Mentions },
|
{ name: 'mentions', path: '/:username/mentions', component: Mentions },
|
||||||
{ name: 'settings', path: '/settings', component: Settings },
|
{ name: 'settings', path: '/settings', component: Settings },
|
||||||
{ name: 'registration', path: '/registration', component: Registration },
|
{ name: 'registration', path: '/registration', component: Registration },
|
||||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings }
|
{ name: 'user-settings', path: '/user-settings', component: UserSettings },
|
||||||
]
|
{ name: 'chat', path: '/chat', component: Chat }
|
||||||
|
]
|
||||||
|
|
||||||
const router = new VueRouter({
|
const router = new VueRouter({
|
||||||
mode: 'history',
|
mode: 'history',
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior: (to, from, savedPosition) => {
|
scrollBehavior: (to, from, savedPosition) => {
|
||||||
|
@ -86,31 +110,16 @@ const router = new VueRouter({
|
||||||
}
|
}
|
||||||
return savedPosition || { x: 0, y: 0 }
|
return savedPosition || { x: 0, y: 0 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const i18n = new VueI18n({
|
/* eslint-disable no-new */
|
||||||
locale: currentLocale,
|
new Vue({
|
||||||
fallbackLocale: 'en',
|
|
||||||
messages
|
|
||||||
})
|
|
||||||
|
|
||||||
/* eslint-disable no-new */
|
|
||||||
new Vue({
|
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
i18n,
|
i18n,
|
||||||
el: '#app',
|
el: '#app',
|
||||||
render: h => h(App)
|
render: h => h(App)
|
||||||
})
|
})
|
||||||
|
|
||||||
window.fetch('/static/config.json')
|
|
||||||
.then((res) => res.json())
|
|
||||||
.then(({name, theme, background, logo, registrationOpen}) => {
|
|
||||||
store.dispatch('setOption', { name: 'name', value: name })
|
|
||||||
store.dispatch('setOption', { name: 'theme', value: theme })
|
|
||||||
store.dispatch('setOption', { name: 'background', value: background })
|
|
||||||
store.dispatch('setOption', { name: 'logo', value: logo })
|
|
||||||
store.dispatch('setOption', { name: 'registrationOpen', value: registrationOpen })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.fetch('/static/terms-of-service.html')
|
window.fetch('/static/terms-of-service.html')
|
||||||
|
@ -120,13 +129,19 @@ window.fetch('/static/terms-of-service.html')
|
||||||
})
|
})
|
||||||
|
|
||||||
window.fetch('/api/pleroma/emoji.json')
|
window.fetch('/api/pleroma/emoji.json')
|
||||||
.then((res) => res.json())
|
.then(
|
||||||
.then((values) => {
|
(res) => res.json()
|
||||||
|
.then(
|
||||||
|
(values) => {
|
||||||
const emoji = Object.keys(values).map((key) => {
|
const emoji = Object.keys(values).map((key) => {
|
||||||
return { shortcode: key, image_url: values[key] }
|
return { shortcode: key, image_url: values[key] }
|
||||||
})
|
})
|
||||||
store.dispatch('setOption', { name: 'customEmoji', value: emoji })
|
store.dispatch('setOption', { name: 'emoji', value: emoji })
|
||||||
})
|
},
|
||||||
|
(failure) => {}
|
||||||
|
),
|
||||||
|
(error) => console.log(error)
|
||||||
|
)
|
||||||
|
|
||||||
window.fetch('/static/emoji.json')
|
window.fetch('/static/emoji.json')
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import {isArray} from 'lodash'
|
import {isArray} from 'lodash'
|
||||||
|
import { Socket } from 'phoenix'
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
state: {
|
state: {
|
||||||
backendInteractor: backendInteractorService(),
|
backendInteractor: backendInteractorService(),
|
||||||
fetchers: {}
|
fetchers: {},
|
||||||
|
socket: null,
|
||||||
|
chatDisabled: false
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setBackendInteractor (state, backendInteractor) {
|
setBackendInteractor (state, backendInteractor) {
|
||||||
|
@ -15,6 +18,12 @@ const api = {
|
||||||
},
|
},
|
||||||
removeFetcher (state, {timeline}) {
|
removeFetcher (state, {timeline}) {
|
||||||
delete state.fetchers[timeline]
|
delete state.fetchers[timeline]
|
||||||
|
},
|
||||||
|
setSocket (state, socket) {
|
||||||
|
state.socket = socket
|
||||||
|
},
|
||||||
|
setChatDisabled (state, value) {
|
||||||
|
state.chatDisabled = value
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -37,6 +46,17 @@ const api = {
|
||||||
const fetcher = store.state.fetchers[timeline]
|
const fetcher = store.state.fetchers[timeline]
|
||||||
window.clearInterval(fetcher)
|
window.clearInterval(fetcher)
|
||||||
store.commit('removeFetcher', {timeline})
|
store.commit('removeFetcher', {timeline})
|
||||||
|
},
|
||||||
|
initializeSocket (store, token) {
|
||||||
|
// Set up websocket connection
|
||||||
|
if (!store.state.chatDisabled) {
|
||||||
|
let socket = new Socket('/socket', {params: {token: token}})
|
||||||
|
socket.connect()
|
||||||
|
store.dispatch('initializeChat', socket)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
disableChat (store) {
|
||||||
|
store.commit('setChatDisabled', true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
src/modules/chat.js
Normal file
33
src/modules/chat.js
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
const chat = {
|
||||||
|
state: {
|
||||||
|
messages: [],
|
||||||
|
channel: null
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setChannel (state, channel) {
|
||||||
|
state.channel = channel
|
||||||
|
},
|
||||||
|
addMessage (state, message) {
|
||||||
|
state.messages.push(message)
|
||||||
|
state.messages = state.messages.slice(-19, 20)
|
||||||
|
},
|
||||||
|
setMessages (state, messages) {
|
||||||
|
state.messages = messages.slice(-19, 20)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
initializeChat (store, socket) {
|
||||||
|
const channel = socket.channel('chat:public')
|
||||||
|
channel.on('new_msg', (msg) => {
|
||||||
|
store.commit('addMessage', msg)
|
||||||
|
})
|
||||||
|
channel.on('messages', ({messages}) => {
|
||||||
|
store.commit('setMessages', messages)
|
||||||
|
})
|
||||||
|
channel.join()
|
||||||
|
store.commit('setChannel', channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default chat
|
|
@ -22,7 +22,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
},
|
},
|
||||||
public: {
|
public: {
|
||||||
statuses: [],
|
statuses: [],
|
||||||
|
@ -36,7 +37,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
statuses: [],
|
statuses: [],
|
||||||
|
@ -50,7 +52,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
},
|
},
|
||||||
publicAndExternal: {
|
publicAndExternal: {
|
||||||
statuses: [],
|
statuses: [],
|
||||||
|
@ -64,7 +67,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
},
|
},
|
||||||
friends: {
|
friends: {
|
||||||
statuses: [],
|
statuses: [],
|
||||||
|
@ -78,7 +82,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
},
|
},
|
||||||
tag: {
|
tag: {
|
||||||
statuses: [],
|
statuses: [],
|
||||||
|
@ -92,7 +97,8 @@ export const defaultState = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +387,8 @@ export const mutations = {
|
||||||
loading: false,
|
loading: false,
|
||||||
followers: [],
|
followers: [],
|
||||||
friends: [],
|
friends: [],
|
||||||
viewing: 'statuses'
|
viewing: 'statuses',
|
||||||
|
flushMarker: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
state.timelines[timeline] = emptyTimeline
|
state.timelines[timeline] = emptyTimeline
|
||||||
|
@ -422,6 +429,9 @@ export const mutations = {
|
||||||
each(notifications, (notification) => {
|
each(notifications, (notification) => {
|
||||||
notification.seen = true
|
notification.seen = true
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
queueFlush (state, { timeline, id }) {
|
||||||
|
state.timelines[timeline].flushMarker = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +468,9 @@ const statuses = {
|
||||||
// Optimistic retweeting...
|
// Optimistic retweeting...
|
||||||
commit('setRetweeted', { status, value: true })
|
commit('setRetweeted', { status, value: true })
|
||||||
apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
apiService.retweet({ id: status.id, credentials: rootState.users.currentUser.credentials })
|
||||||
|
},
|
||||||
|
queueFlush ({ rootState, commit }, { timeline, id }) {
|
||||||
|
commit('queueFlush', { timeline, id })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations
|
mutations
|
||||||
|
|
|
@ -97,6 +97,10 @@ const users = {
|
||||||
// Set our new backend interactor
|
// Set our new backend interactor
|
||||||
commit('setBackendInteractor', backendInteractorService(userCredentials))
|
commit('setBackendInteractor', backendInteractorService(userCredentials))
|
||||||
|
|
||||||
|
if (user.token) {
|
||||||
|
store.dispatch('initializeSocket', user.token)
|
||||||
|
}
|
||||||
|
|
||||||
// Start getting fresh tweets.
|
// Start getting fresh tweets.
|
||||||
store.dispatch('startFetching', 'friends')
|
store.dispatch('startFetching', 'friends')
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,16 @@ let fetch = (url, options) => {
|
||||||
return oldfetch(fullUrl, options)
|
return oldfetch(fullUrl, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// from https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
|
||||||
|
let utoa = (str) => {
|
||||||
|
// first we use encodeURIComponent to get percent-encoded UTF-8,
|
||||||
|
// then we convert the percent encodings into raw bytes which
|
||||||
|
// can be fed into btoa.
|
||||||
|
return btoa(encodeURIComponent(str)
|
||||||
|
.replace(/%([0-9A-F]{2})/g,
|
||||||
|
(match, p1) => { return String.fromCharCode('0x' + p1) }))
|
||||||
|
}
|
||||||
|
|
||||||
// Params
|
// Params
|
||||||
// cropH
|
// cropH
|
||||||
// cropW
|
// cropW
|
||||||
|
@ -156,7 +166,7 @@ const register = (params) => {
|
||||||
|
|
||||||
const authHeaders = (user) => {
|
const authHeaders = (user) => {
|
||||||
if (user && user.username && user.password) {
|
if (user && user.username && user.password) {
|
||||||
return { 'Authorization': `Basic ${btoa(`${user.username}:${user.password}`)}` }
|
return { 'Authorization': `Basic ${utoa(`${user.username}:${user.password}`)}` }
|
||||||
} else {
|
} else {
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
@ -281,6 +291,8 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
|
||||||
url += `/${tag}.json`
|
url += `/${tag}.json`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params.push(['count', 20])
|
||||||
|
|
||||||
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
|
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
|
||||||
url += `?${queryString}`
|
url += `?${queryString}`
|
||||||
|
|
||||||
|
|
34
src/services/color_convert/color_convert.js
Normal file
34
src/services/color_convert/color_convert.js
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { map } from 'lodash'
|
||||||
|
|
||||||
|
const rgb2hex = (r, g, b) => {
|
||||||
|
[r, g, b] = map([r, g, b], (val) => {
|
||||||
|
val = Math.ceil(val)
|
||||||
|
val = val < 0 ? 0 : val
|
||||||
|
val = val > 255 ? 255 : val
|
||||||
|
return val
|
||||||
|
})
|
||||||
|
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const hex2rgb = (hex) => {
|
||||||
|
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
||||||
|
return result ? {
|
||||||
|
r: parseInt(result[1], 16),
|
||||||
|
g: parseInt(result[2], 16),
|
||||||
|
b: parseInt(result[3], 16)
|
||||||
|
} : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbstr2hex = (rgb) => {
|
||||||
|
if (rgb[0] === '#') {
|
||||||
|
return rgb
|
||||||
|
}
|
||||||
|
rgb = rgb.match(/\d+/g)
|
||||||
|
return `#${((Number(rgb[0]) << 16) + (Number(rgb[1]) << 8) + Number(rgb[2])).toString(16)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
rgb2hex,
|
||||||
|
hex2rgb,
|
||||||
|
rgbstr2hex
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { times, map } from 'lodash'
|
import { times } from 'lodash'
|
||||||
|
import { rgb2hex, hex2rgb } from '../color_convert/color_convert.js'
|
||||||
|
|
||||||
// While this is not used anymore right now, I left it in if we want to do custom
|
// While this is not used anymore right now, I left it in if we want to do custom
|
||||||
// styles that aren't just colors, so user can pick from a few different distinct
|
// styles that aren't just colors, so user can pick from a few different distinct
|
||||||
|
@ -56,16 +57,6 @@ const setStyle = (href, commit) => {
|
||||||
cssEl.addEventListener('load', setDynamic)
|
cssEl.addEventListener('load', setDynamic)
|
||||||
}
|
}
|
||||||
|
|
||||||
const rgb2hex = (r, g, b) => {
|
|
||||||
[r, g, b] = map([r, g, b], (val) => {
|
|
||||||
val = Math.ceil(val)
|
|
||||||
val = val < 0 ? 0 : val
|
|
||||||
val = val > 255 ? 255 : val
|
|
||||||
return val
|
|
||||||
})
|
|
||||||
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const setColors = (col, commit) => {
|
const setColors = (col, commit) => {
|
||||||
const head = document.head
|
const head = document.head
|
||||||
const body = document.body
|
const body = document.body
|
||||||
|
@ -82,6 +73,7 @@ const setColors = (col, commit) => {
|
||||||
if (isDark) {
|
if (isDark) {
|
||||||
mod = mod * -1
|
mod = mod * -1
|
||||||
}
|
}
|
||||||
|
|
||||||
colors['base00'] = rgb2hex(col.bg.r, col.bg.g, col.bg.b) // background
|
colors['base00'] = rgb2hex(col.bg.r, col.bg.g, col.bg.b) // background
|
||||||
colors['base01'] = rgb2hex((col.bg.r + col.fg.r) / 2, (col.bg.g + col.fg.g) / 2, (col.bg.b + col.fg.b) / 2) // hilighted bg
|
colors['base01'] = rgb2hex((col.bg.r + col.fg.r) / 2, (col.bg.g + col.fg.g) / 2, (col.bg.b + col.fg.b) / 2) // hilighted bg
|
||||||
colors['base02'] = rgb2hex(col.fg.r, col.fg.g, col.fg.b) // panels & buttons
|
colors['base02'] = rgb2hex(col.fg.r, col.fg.g, col.fg.b) // panels & buttons
|
||||||
|
@ -91,11 +83,13 @@ const setColors = (col, commit) => {
|
||||||
colors['base06'] = rgb2hex(col.text.r - mod, col.text.g - mod, col.text.b - mod) // strong text
|
colors['base06'] = rgb2hex(col.text.r - mod, col.text.g - mod, col.text.b - mod) // strong text
|
||||||
colors['base07'] = rgb2hex(col.text.r - mod * 2, col.text.g - mod * 2, col.text.b - mod * 2)
|
colors['base07'] = rgb2hex(col.text.r - mod * 2, col.text.g - mod * 2, col.text.b - mod * 2)
|
||||||
colors['base08'] = rgb2hex(col.link.r, col.link.g, col.link.b) // links
|
colors['base08'] = rgb2hex(col.link.r, col.link.g, col.link.b) // links
|
||||||
|
colors['base09'] = rgb2hex((col.bg.r + col.text.r) / 2, (col.bg.g + col.text.g) / 2, (col.bg.b + col.text.b) / 2) // icons
|
||||||
|
|
||||||
times(9, (n) => {
|
const num = 10
|
||||||
const color = colors[`base0${8 - n}`]
|
times(num, (n) => {
|
||||||
styleSheet.insertRule(`.base0${8 - n} { color: ${color}`, 'index-max')
|
const color = colors[`base0${num - 1 - n}`]
|
||||||
styleSheet.insertRule(`.base0${8 - n}-background { background-color: ${color}`, 'index-max')
|
styleSheet.insertRule(`.base0${num - 1 - n} { color: ${color}`, 'index-max')
|
||||||
|
styleSheet.insertRule(`.base0${num - 1 - n}-background { background-color: ${color}`, 'index-max')
|
||||||
})
|
})
|
||||||
|
|
||||||
styleSheet.insertRule(`a { color: ${colors['base08']}`, 'index-max')
|
styleSheet.insertRule(`a { color: ${colors['base08']}`, 'index-max')
|
||||||
|
@ -108,15 +102,6 @@ const setColors = (col, commit) => {
|
||||||
commit('setOption', { name: 'customTheme', value: col })
|
commit('setOption', { name: 'customTheme', value: col })
|
||||||
}
|
}
|
||||||
|
|
||||||
const hex2rgb = (hex) => {
|
|
||||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
||||||
return result ? {
|
|
||||||
r: parseInt(result[1], 16),
|
|
||||||
g: parseInt(result[2], 16),
|
|
||||||
b: parseInt(result[3], 16)
|
|
||||||
} : null
|
|
||||||
}
|
|
||||||
|
|
||||||
const setPreset = (val, commit) => {
|
const setPreset = (val, commit) => {
|
||||||
window.fetch('/static/styles.json')
|
window.fetch('/static/styles.json')
|
||||||
.then((data) => data.json())
|
.then((data) => data.json())
|
||||||
|
|
|
@ -29,12 +29,19 @@ const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false
|
||||||
args['tag'] = tag
|
args['tag'] = tag
|
||||||
|
|
||||||
return apiService.fetchTimeline(args)
|
return apiService.fetchTimeline(args)
|
||||||
.then((statuses) => update({store, statuses, timeline, showImmediately}),
|
.then((statuses) => {
|
||||||
() => store.dispatch('setError', { value: true }))
|
if (!older && statuses.length >= 20) {
|
||||||
|
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
|
||||||
|
}
|
||||||
|
update({store, statuses, timeline, showImmediately})
|
||||||
|
}, () => store.dispatch('setError', { value: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
const startFetching = ({timeline = 'friends', credentials, store, userId = false, tag = false}) => {
|
const startFetching = ({timeline = 'friends', credentials, store, userId = false, tag = false}) => {
|
||||||
fetchAndUpdate({timeline, credentials, store, showImmediately: true, userId, tag})
|
const rootState = store.rootState || store.state
|
||||||
|
const timelineData = rootState.statuses.timelines[camelCase(timeline)]
|
||||||
|
const showImmediately = timelineData.visibleStatuses.length === 0
|
||||||
|
fetchAndUpdate({timeline, credentials, store, showImmediately, userId, tag})
|
||||||
const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store, userId, tag })
|
const boundFetchAndUpdate = () => fetchAndUpdate({ timeline, credentials, store, userId, tag })
|
||||||
return setInterval(boundFetchAndUpdate, 10000)
|
return setInterval(boundFetchAndUpdate, 10000)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,5 +3,7 @@
|
||||||
"theme": "pleroma-dark",
|
"theme": "pleroma-dark",
|
||||||
"background": "/static/bg.jpg",
|
"background": "/static/bg.jpg",
|
||||||
"logo": "/static/logo.png",
|
"logo": "/static/logo.png",
|
||||||
"registrationOpen": false
|
"registrationOpen": false,
|
||||||
|
"defaultPath": "/main/all",
|
||||||
|
"chatDisabled": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -4226,6 +4226,10 @@ phantomjs-prebuilt@^2.1.3, phantomjs-prebuilt@^2.1.7:
|
||||||
request-progress "~2.0.1"
|
request-progress "~2.0.1"
|
||||||
which "~1.2.10"
|
which "~1.2.10"
|
||||||
|
|
||||||
|
phoenix@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/phoenix/-/phoenix-1.3.0.tgz#1df2c27f986ee295e37c9983ec28ebac1d7f4a3e"
|
||||||
|
|
||||||
pify@^2.0.0:
|
pify@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
|
|
Loading…
Reference in a new issue