Skip to content
Snippets Groups Projects
Commit e0e8965c authored by Shpuld Shpuldson's avatar Shpuld Shpuldson
Browse files

update branch and fix merge conflicts

parents 44923afb 7d46e396
No related branches found
No related tags found
No related merge requests found
Showing
with 502 additions and 158 deletions
......@@ -2,7 +2,7 @@
> A Qvitter-style frontend for certain GS servers.
![screenshot](http://i.imgur.com/3q30Zxt.jpg)
![screenshot](https://my.mixtape.moe/kjzioz.PNG)
# FOR ADMINS
......
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
......
......@@ -23,11 +23,11 @@
"object-path": "^0.11.3",
"sanitize-html": "^1.13.0",
"sass-loader": "^4.0.2",
"vue": "^2.1.0",
"vue-router": "^2.2.0",
"vue-template-compiler": "^2.1.10",
"vue": "^2.3.4",
"vue-router": "^2.5.3",
"vue-template-compiler": "^2.3.4",
"vue-timeago": "^3.1.2",
"vuex": "^2.1.0"
"vuex": "^2.3.1"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
......
import UserPanel from './components/user_panel/user_panel.vue'
import NavPanel from './components/nav_panel/nav_panel.vue'
import Notifications from './components/notifications/notifications.vue'
import UserFinder from './components/user_finder/user_finder.vue'
export default {
name: 'app',
components: {
UserPanel,
NavPanel,
Notifications
Notifications,
UserFinder
},
data: () => ({
mobileActivePanel: 'timeline'
......
......@@ -52,6 +52,8 @@ button{
.item {
flex: 1;
line-height: 21px;
height: 21px;
}
.gaps > .item {
......@@ -134,11 +136,6 @@ main-router {
background-color: rgba(0,0,0,0.1);
}
.media-body {
flex: 1;
padding-left: 0.5em;
}
.container > * {
min-width: 0px;
}
......@@ -147,60 +144,6 @@ main-router {
color: grey;
}
.status-actions {
width: 50%;
display: flex;
div, favorite-button {
flex: 1;
}
}
status-text-container {
display: block;
}
.status-el {
line-height: 18px;
.notify {
.avatar {
border-width: 3px;
border-style: solid;
}
}
.media-left {
img {
margin-top: 0.2em;
float: right;
margin-right: 0.3em;
border-radius: 5px;
}
}
.retweet-info {
padding: 0.7em 0 0 0.6em;
.media-left {
display: flex;
i {
align-self: center;
text-align: right;
flex: 1;
padding-right: 0.3em;
}
}
}
.media-heading {
small {
font-weight: lighter;
}
margin-bottom: 0.3em;
}
}
nav {
z-index: 1000;
}
......@@ -213,13 +156,20 @@ nav {
}
.main {
flex: 1;
flex-basis: 65%;
flex-basis: 60%;
flex-grow: 1;
flex-shrink: 1;
}
.sidebar {
flex: 1;
flex-basis: 35%;
flex: 0;
flex-basis: 35%;
}
.sidebar-flexer {
flex: 1;
flex-basis: 345px;
width: 365px;
}
.mobile-shown {
......@@ -238,6 +188,30 @@ nav {
}
}
@media all and (min-width: 960px) {
.sidebar {
overflow: hidden;
max-height: 100vh;
width: 350px;
position: fixed;
margin-top: -10px;
.sidebar-container {
height: 96vh;
width: 362px;
padding-top: 10px;
padding-right: 20px;
overflow-x: hidden;
overflow-y: scroll;
}
}
.sidebar-flexer {
max-height: 96vh;
flex-shrink: 0;
flex-grow: 0;
}
}
@media all and (max-width: 959px) {
.mobile-hidden {
display: none;
......
......@@ -6,6 +6,7 @@
<router-link :to="{ name: 'root'}">{{sitename}}</router-link>
</div>
<div class='item right'>
<user-finder></user-finder>
<router-link :to="{ name: 'settings'}"><i class="icon-cog"></i></router-link>
</div>
</div>
......@@ -15,10 +16,14 @@
<button @click="activatePanel('sidebar')">Sidebar</button>
<button @click="activatePanel('timeline')">Timeline</button>
</div>
<div class="sidebar" :class="{ 'mobile-hidden': mobileActivePanel != 'sidebar' }">
<user-panel></user-panel>
<nav-panel></nav-panel>
<notifications v-if="currentUser"></notifications>
<div class="sidebar-flexer" :class="{ 'mobile-hidden': mobileActivePanel != 'sidebar'}">
<div class="sidebar" :class="{ 'mobile-hidden': mobileActivePanel != 'sidebar' }">
<div class="sidebar-container">
<user-panel></user-panel>
<nav-panel></nav-panel>
<notifications v-if="currentUser"></notifications>
</div>
</div>
</div>
<div class="main" :class="{ 'mobile-hidden': mobileActivePanel != 'timeline' }">
<transition name="fade">
......
......@@ -26,7 +26,7 @@ const Attachment = {
autoHeight () {
if (this.type === 'image' && this.nsfw) {
return {
'min-height': '311px'
'min-height': '109px'
}
}
}
......
......@@ -33,10 +33,10 @@
.attachments {
display: flex;
flex-wrap: wrap;
margin-right: -0.8em;
margin-right: -0.7em;
.attachment {
flex: 1 0 30%;
margin: 0.5em 0.8em 0.6em 0.0em;
margin: 0.5em 0.7em 0.6em 0.0em;
align-self: flex-start;
&.html {
......@@ -116,8 +116,10 @@
border-style: solid;
border-width: 1px;
border-radius: 5px;
object-fit: contain;
width: 100%;
height: 100%; /* If this isn't here, chrome will stretch the images */
max-height: 500px;
}
}
}
......
import { filter, sortBy } from 'lodash'
import { find, filter, sortBy } from 'lodash'
import { statusType } from '../../modules/statuses.js'
import Status from '../status/status.vue'
......@@ -8,6 +8,16 @@ const sortAndFilterConversation = (conversation) => {
}
const conversation = {
data () {
return {
highlight: null,
preview: {
x: 0,
y: 0,
status: null
}
}
},
props: [
'statusoid',
'collapsable'
......@@ -22,7 +32,6 @@ const conversation = {
const conversationId = this.status.statusnet_conversation_id
const statuses = this.$store.state.statuses.allStatuses
const conversation = filter(statuses, { statusnet_conversation_id: conversationId })
return sortAndFilterConversation(conversation)
}
},
......@@ -41,6 +50,7 @@ const conversation = {
const conversationId = this.status.statusnet_conversation_id
this.$store.state.api.backendInteractor.fetchConversation({id: conversationId})
.then((statuses) => this.$store.dispatch('addNewStatuses', { statuses }))
.then(() => this.setHighlight(this.statusoid.id))
} else {
const id = this.$route.params.id
this.$store.state.api.backendInteractor.fetchStatus({id})
......@@ -48,12 +58,38 @@ const conversation = {
.then(() => this.fetchConversation())
}
},
focused: function (id) {
getReplies (id) {
let res = []
id = Number(id)
let i
for (i = 0; i < this.conversation.length; i++) {
if (Number(this.conversation[i].in_reply_to_status_id) === id) {
res.push({
name: `#${i}`,
id: this.conversation[i].id
})
}
}
return res
},
focused (id) {
if (this.statusoid.retweeted_status) {
return (id === this.statusoid.retweeted_status.id)
} else {
return (id === this.statusoid.id)
}
},
setHighlight (id) {
this.highlight = Number(id)
},
setPreview (id, x, y) {
if (id) {
this.preview.x = x
this.preview.y = y
this.preview.status = find(this.conversation, { id: id })
} else {
this.preview.status = null
}
}
}
}
......
......@@ -8,7 +8,17 @@
</div>
<div class="panel-body">
<div class="timeline">
<status v-for="status in conversation" :key="status.id" :statusoid="status" :expandable='false' :focused="focused(status.id)" :inConversation='true'></status>
<status v-for="status in conversation" @goto="setHighlight" :key="status.id" @preview="setPreview" :statusoid="status" :expandable='false' :focused="focused(status.id)" :inConversation='true' :highlight="highlight" :replies="getReplies(status.id)"></status>
</div>
</div>
<div class="status-preview base00-background base03-border" :style="{ left: preview.x + 'px', top: preview.y + 'px'}" v-if="preview.status">
<img class="avatar" :src="preview.status.user.profile_image_url_original">
<div class="text">
<h4>
{{ preview.status.user.name }}
<small><a>{{ preview.status.user.screen_name}}</a></small>
</h4>
<div @click.prevent="linkClicked" class="status-content" v-html="preview.status.statusnet_html"></div>
</div>
</div>
</div>
......@@ -21,4 +31,30 @@
border-bottom-style: solid;
border-bottom-width: 1px;
}
.status-preview {
position: absolute;
max-width: 35em;
padding: 0.5em;
display: flex;
border-color: inherit;
border-style: solid;
border-width: 1px;
border-radius: 4px;
box-shadow: 2px 2px 3px rgba(0, 0, 0, 0.5);
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
}
.text {
h4 {
margin-bottom: 0.4em;
small {
font-weight: lighter;
}
}
padding: 0 0.5em 0.5em 0.5em;
}
}
</style>
import Status from '../status/status.vue'
import { sortBy, take, filter } from 'lodash'
const Notifications = {
......@@ -23,6 +25,9 @@ const Notifications = {
return this.unseenNotifications.length
}
},
components: {
Status
},
watch: {
unseenCount (count) {
if (count > 0) {
......
@import '../../_variables.scss';
.notifications {
// a bit of a hack to allow scrolling below notifications
padding-bottom: 15em;
.panel-heading {
// force the text to stay centered, while keeping
......@@ -43,19 +45,23 @@
word-wrap: break-word;
line-height:18px;
.icon-retweet {
.icon-retweet.lit {
color: $green;
}
.icon-reply {
.icon-reply.lit {
color: $blue;
}
h1 {
word-break: break-all;
margin: 0 0 0.3em;
padding: 0;
font-size: 1em;
line-height:20px;
small {
font-weight: lighter;
}
}
padding: 0.3em 0.8em 0.5em;
......
......@@ -7,23 +7,34 @@
<button @click.prevent="markAsSeen" class="base06 base02-background read-button">Read!</button>
</div>
<div class="panel-body base03-border">
<div v-for="notification in visibleNotifications" class="notification" :class='{"unseen": !notification.seen}'>
<div v-for="notification in visibleNotifications" :key="notification" class="notification" :class='{"unseen": !notification.seen}'>
<a :href="notification.action.user.statusnet_profile_url">
<img class='avatar' :src="notification.action.user.profile_image_url_original">
</a>
<div class='text'>
<timeago :since="notification.action.created_at" :auto-update="240"></timeago>
<div class='text' style="width: 100%;">
<div v-if="notification.type === 'favorite'">
<h1>{{ notification.action.user.name }}<br><i class="fa icon-star"></i> favorited your <router-link :to="{ name: 'conversation', params: { id: notification.status.id } }">status</h1>
<p>{{ notification.status.text }}</p>
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-star"></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>
</h1>
<div v-html="notification.status.statusnet_html"></div>
</div>
<div v-if="notification.type === 'repeat'">
<h1>{{ notification.action.user.name }}<br><i class="fa icon-retweet"></i> repeated your <router-link :to="{ name: 'conversation', params: { id: notification.status.id } }">status</h1>
<p>{{ notification.status.text }}</p>
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-retweet 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>
</h1>
<div v-html="notification.status.statusnet_html"></div>
</div>
<div v-if="notification.type === 'mention'">
<h1>{{ notification.action.user.name }}<br><i class="fa icon-reply"></i> <router-link :to="{ name: 'conversation', params: { id: notification.status.id } }">mentioned</router-link> you</h1>
<p>{{ notification.status.text }}</p>
<h1>
<span :title="'@'+notification.action.user.screen_name">{{ notification.action.user.name }}</span>
<i class="fa icon-reply 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>
</h1>
<status :compact="true" :statusoid="notification.status"></status>
</div>
</div>
</div>
......
......@@ -5,6 +5,7 @@ import Completion from '../../services/completion/completion.js'
import { take, filter, reject, map, uniqBy } from 'lodash'
const buildMentionsString = ({user, attentions}, currentUser) => {
let allAttentions = [...attentions]
......@@ -87,6 +88,8 @@ const PostStatusForm = {
files: []
}
this.$emit('posted')
let el = this.$el.querySelector('textarea')
el.style.height = '16px'
},
addMediaFile (fileInfo) {
this.newStatus.files.push(fileInfo)
......@@ -113,6 +116,13 @@ const PostStatusForm = {
},
fileDrag (e) {
e.dataTransfer.dropEffect = 'copy'
},
resize (e) {
e.target.style.height = 'auto'
e.target.style.height = `${e.target.scrollHeight - 10}px`
if (e.target.value === '') {
e.target.style.height = '16px'
}
}
}
}
......
<template>
<div class="post-status-form">
<form @submit.prevent="postStatus(newStatus)">
<div class="form-group" >
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" placeholder="Just landed in L.A." rows="3" class="form-control" @keyup.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag"></textarea>
</div>
<div class="attachments">
<div class="attachment" v-for="file in newStatus.files">
<i class="fa icon-cancel" @click="removeMediaFile(file)"></i>
<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 class="form-group base03-border" >
<textarea @click="setCaret" @keyup="setCaret" v-model="newStatus.status" placeholder="Just landed in L.A." rows="1" class="form-control" @keydown.meta.enter="postStatus(newStatus)" @keyup.ctrl.enter="postStatus(newStatus)" @drop="fileDrop" @dragover.prevent="fileDrag" @input="resize"></textarea>
</div>
<div>
<h1>Word</h1>
......@@ -24,6 +15,15 @@
<media-upload @uploading="disableSubmit" @uploaded="addMediaFile" @upload-failed="enableSubmit" :drop-files="dropFiles"></media-upload>
<button :disabled="submitDisabled" type="submit" class="btn btn-default base05 base01-background">Submit</button>
</div>
<div class="attachments">
<div class="attachment" v-for="file in newStatus.files">
<i class="fa icon-cancel" @click="removeMediaFile(file)"></i>
<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>
</form>
</div>
</template>
......@@ -51,14 +51,20 @@
.form-bottom {
display: flex;
padding: 0.5em;
height: 32px;
button {
flex: 2;
width: 10em;
}
}
.attachments {
padding: 0.5em;
padding: 0 0.5em;
.attachment {
position: relative;
margin: 0.5em 0.8em 0.2em 0;
}
i {
position: absolute;
......@@ -86,11 +92,16 @@
form textarea {
border: solid;
border-width: 1px;
border-color: silver;
border-color: inherit;
border-radius: 5px;
line-height:16px;
padding: 5px;
resize: vertical;
resize: none;
overflow: hidden;
}
form textarea:focus {
min-height: 48px;
}
.btn {
......
import StyleSwitcher from '../style_switcher/style_switcher.vue'
import { filter, trim } from 'lodash'
const settings = {
data () {
return {
hideAttachmentsLocal: this.$store.state.config.hideAttachments,
hideAttachmentsInConvLocal: this.$store.state.config.hideAttachmentsInConv,
hideNsfwLocal: this.$store.state.config.hideNsfw
hideNsfwLocal: this.$store.state.config.hideNsfw,
autoLoadLocal: this.$store.state.config.autoLoad,
hoverPreviewLocal: this.$store.state.config.hoverPreview,
muteWordsString: this.$store.state.config.muteWords.join('\n')
}
},
components: {
......@@ -20,6 +24,16 @@ const settings = {
},
hideNsfwLocal (value) {
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
},
autoLoadLocal (value) {
this.$store.dispatch('setOption', { name: 'autoLoad', value })
},
hoverPreviewLocal (value) {
this.$store.dispatch('setOption', { name: 'hoverPreview', value })
},
muteWordsString (value) {
value = filter(value.split('\n'), (word) => trim(word).length > 0)
this.$store.dispatch('setOption', { name: 'muteWords', value })
}
}
}
......
......@@ -8,6 +8,11 @@
<h2>Theme</h2>
<style-switcher></style-switcher>
</div>
<div class="setting-item">
<h2>Filtering</h2>
<p>All notices containing these words will be muted, one per line</p>
<textarea id="muteWords" v-model="muteWordsString"></textarea>
</div>
<div class="setting-item">
<h2>Attachments</h2>
<ul class="setting-list">
......@@ -23,6 +28,14 @@
<input type="checkbox" id="hideNsfw" v-model="hideNsfwLocal">
<label for="hideNsfw">Enable clickthrough NSFW attachment hiding</label>
</li>
<li>
<input type="checkbox" id="autoLoad" v-model="autoLoadLocal">
<label for="autoLoad">Enable automatic loading when scrolled to the bottom</label>
</li>
<li>
<input type="checkbox" id="hoverPreview" v-model="hoverPreviewLocal">
<label for="hoverPreview">Enable reply-link preview on mouse hover</label>
</li>
</ul>
</div>
</div>
......@@ -32,9 +45,13 @@
<script src="./settings.js">
</script>
<style>
<style lang="scss">
.setting-item {
margin: 1em 1em 1.4em;
textarea {
width: 100%;
height: 100px;
}
}
.setting-list {
list-style-type: none;
......
......@@ -4,13 +4,17 @@ 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.vue'
import { filter } from 'lodash'
const Status = {
props: [
'statusoid',
'expandable',
'inConversation',
'focused'
'focused',
'highlight',
'compact',
'replies'
],
data: () => ({
replying: false,
......@@ -19,6 +23,9 @@ const Status = {
userExpanded: false
}),
computed: {
muteWords () {
return this.$store.state.config.muteWords
},
hideAttachments () {
return (this.$store.state.config.hideAttachments && !this.inConversation) ||
(this.$store.state.config.hideAttachmentsInConv && this.inConversation)
......@@ -35,12 +42,30 @@ const Status = {
loggedIn () {
return !!this.$store.state.users.currentUser
},
muted () { return !this.unmuted && this.status.user.muted },
muteWordHits () {
const statusText = this.status.text.toLowerCase()
const hits = filter(this.muteWords, (muteWord) => {
return statusText.includes(muteWord.toLowerCase())
})
return hits
},
muted () { return !this.unmuted && (this.status.user.muted || this.muteWordHits.length > 0) },
isReply () { return !!this.status.in_reply_to_status_id },
borderColor () {
return {
borderBottomColor: this.$store.state.config.colors['base02']
}
},
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
}
},
components: {
......@@ -63,6 +88,10 @@ const Status = {
toggleReplying () {
this.replying = !this.replying
},
gotoOriginal (id) {
// only handled by conversation, not status_or_conversation
this.$emit('goto', id)
},
toggleExpanded () {
this.$emit('toggleExpanded')
},
......@@ -71,6 +100,28 @@ const Status = {
},
toggleUserExpanded () {
this.userExpanded = !this.userExpanded
},
replyEnter (id, event) {
if (this.$store.state.config.hoverPreview) {
let rect = event.target.getBoundingClientRect()
this.$emit('preview', Number(id), rect.left + 20, rect.top + 20 + window.pageYOffset)
}
},
replyLeave () {
this.$emit('preview', 0, 0, 0)
}
},
watch: {
'highlight': function (id) {
id = Number(id)
if (this.status.id === id) {
let rect = this.$el.getBoundingClientRect()
if (rect.top < 100) {
window.scrollBy(0, rect.top - 200)
} else if (rect.bottom > window.innerHeight - 50) {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
}
}
}
......
<template>
<div class="status-el base00-background base03-border" v-if="!status.deleted" v-bind:class="[{ 'base01-background': focused }, { 'status-conversation': inConversation }]" >
<div class="status-el base00-background" v-if="compact">
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
<div v-if="loggedIn">
<div class='status-actions'>
<div>
<a href="#" v-on:click.prevent="toggleReplying">
<i class="fa icon-reply" :class="{'icon-reply-active': replying}"></i>
</a>
</div>
<retweet-button :status=status></retweet-button>
<favorite-button :status=status></favorite-button>
</div>
</div>
<post-status-form class="reply-body" :reply-to="status.id" :attentions="status.attentions" :repliedUser="status.user" v-on:posted="toggleReplying" v-if="replying"/>
</div>
<div class="status-el base00-background base03-border" v-else-if="!status.deleted" v-bind:class="[{ 'base01-background': isFocused }, { 'status-conversation': inConversation }]" >
<template v-if="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>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="icon-eye-off"></i></a>
<small class="muteWords">{{muteWordHits.join(', ')}}</small>
<a href="#" class="unmute" @click.prevent="toggleMute"><i class="fa icon-eye-off"></i></a>
</div>
</template>
<template v-if="!muted">
......@@ -12,13 +28,14 @@
<i class='fa icon-retweet retweeted'></i>
</div>
<div class="media-body">
Retweeted by {{retweeter}}
Repeated by <a :href="statusoid.user.statusnet_profile_url" style="font-weight: bold;" :title="'@'+statusoid.user.screen_name">{{retweeter}}</a>
</div>
</div>
<div class="media status container">
<div class="media-left">
<a :href="status.user.statusnet_profile_url">
<img @click.prevent="toggleUserExpanded" class='avatar' :src="status.user.profile_image_url_original">
<img @click.prevent="toggleUserExpanded" :class="{retweeted: retweet}" class='avatar' :src="status.user.profile_image_url_original">
<img v-if="retweet" class='avatar-retweeter' :src="statusoid.user.profile_image_url_original"></img>
</a>
</div>
<div class="media-body">
......@@ -26,40 +43,45 @@
<user-card-content :user="status.user"></user-card-content>
</div>
<div class="user-content">
<h4 class="media-heading">
{{status.user.name}}
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
<small v-if="status.in_reply_to_screen_name"> &gt;
<router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }">
{{status.in_reply_to_screen_name}}
</router-link>
</small>
<template v-if="isReply">
<div class="media-heading">
<div class="name-and-links">
<h4 class="user-name">{{status.user.name}}</h4>
<div class="links">
<h4>
<small><router-link :to="{ name: 'user-profile', params: { id: status.user.id } }">{{status.user.screen_name}}</router-link></small>
<small v-if="status.in_reply_to_screen_name"> &gt;
<router-link :to="{ name: 'user-profile', params: { id: status.in_reply_to_user_id } }">
{{status.in_reply_to_screen_name}}
</router-link>
</small>
<template v-if="isReply && !expandable">
<small>
<a href="#" @click.prevent="gotoOriginal(status.in_reply_to_status_id)"><i class="icon-reply" @mouseenter="replyEnter(status.in_reply_to_status_id, $event)" @mouseout="replyLeave()"></i></a>
</small>
</template>
-
<small>
<router-link :to="{ name: 'conversation', params: { id: status.in_reply_to_status_id } }">
<i class="icon-reply"></i>
</router-link>
<router-link :to="{ name: 'conversation', params: { id: status.id } }">
<timeago :since="status.created_at" :auto-update="60"></timeago>
</router-link>
</small>
</h4>
</div>
<h4 class="replies" v-if="inConversation">
<small v-if="replies.length">Replies:</small>
<small v-for="reply in replies">
<a href="#" @click.prevent="gotoOriginal(reply.id)" @mouseenter="replyEnter(reply.id, $event)" @mouseout="replyLeave()">{{reply.name}}&nbsp;</a>
</small>
</template>
-
<small>
<router-link :to="{ name: 'conversation', params: { id: status.id } }">
<timeago :since="status.created_at" :auto-update="60"></timeago>
</router-link>
</small>
<template v-if="expandable">
-
<small>
<a href="#" @click.prevent="toggleExpanded" ><i class="icon-plus-squared"></i></a>
</small>
<small v-if="status.user.muted">
<a href="#" @click.prevent="toggleMute" ><i class="icon-eye-off"></i></a>
</small>
</template>
<small v-if="!status.is_local" class="source_url">
<a :href="status.external_url" target="_blank" ><i class="icon-binoculars"></i></a>
</small>
</h4>
</h4>
</div>
<div class="heading-icons">
<a href="#" @click.prevent="toggleMute" v-if="unmuted"><i class="fa 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>
<template v-if="expandable">
<a href="#" @click.prevent="toggleExpanded" class="expand"><i class="fa icon-plus-squared"></i></a>
</template>
</div>
</div>
<div @click.prevent="linkClicked" class="status-content" v-html="status.statusnet_html"></div>
......@@ -95,24 +117,65 @@
<style lang="scss">
@import '../../_variables.scss';
status-text-container {
display: block;
}
.status-el {
hyphens: auto;
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-word;
border-left-width: 0px;
line-height: 18px;
.notify {
.avatar {
border-width: 3px;
border-style: solid;
}
}
.media-body {
flex: 1;
padding-left: 0.5em;
}
.user-content {
min-height: 52px;
padding-top: 1px;
}
.media-heading {
display: flex;
min-height: 1.4em;
margin-bottom: 0.3em;
small {
font-weight: lighter;
}
h4 {
margin-right: 0.4em;
}
.name-and-links {
flex: 1 0;
display: flex;
flex-wrap: wrap;
}
.replies {
flex-basis: 100%;
}
}
.source_url {
float: right;
}
.greentext {
color: green;
.expand {
margin-right: -0.3em;
}
a {
......@@ -129,6 +192,34 @@
margin-top: 0.2em;
margin-bottom: 0.5em;
}
.media-left {
img {
margin-top: 0.2em;
float: right;
margin-right: 0.3em;
border-radius: 5px;
}
}
.retweet-info {
padding: 0.7em 0 0 0.6em;
.media-left {
display: flex;
i {
align-self: center;
text-align: right;
flex: 1;
padding-right: 0.3em;
}
}
}
}
.greentext {
color: green;
}
.status-conversation {
......@@ -136,7 +227,14 @@
}
.status-actions {
padding-top: 5px;
padding-top: 0.15em;
width: 100%;
display: flex;
div, favorite-button {
max-width: 6em;
flex: 1;
}
}
.icon-reply:hover {
......@@ -148,7 +246,23 @@
}
.status .avatar {
width: 48px;
width: 48px;
height: 48px;
&.retweeted {
width: 40px;
height: 40px;
margin-right: 8px;
margin-bottom: 8px;
}
}
.status img.avatar-retweeter {
width: 24px;
height: 24px;
position: absolute;
margin-left: 24px;
margin-top: 24px;
}
.status.compact .avatar {
......@@ -156,14 +270,22 @@
}
.status {
padding: 0.65em 0.7em 0.8em 0.8em;
padding: 0.4em 0.7em 0.45em 0.7em;
border-bottom: 1px solid;
border-bottom-color: inherit;
border-left: 4px rgba(255, 48, 16, 0.65);
border-left-style: inherit;
}
.muted button {
margin-left: auto;
.muted {
padding: 0.1em 0.4em 0.1em 0.8em;
button {
margin-left: auto;
}
.muteWords {
margin-left: 10px;
}
}
a.unmute {
......@@ -188,4 +310,35 @@
flex: 1;
}
@media all and (max-width: 960px) {
.status-el {
.name-and-links {
margin-left: -0.25em;
}
}
.status {
max-width: 100%;
}
.status .avatar {
width: 40px;
height: 40px;
&.retweeted {
width: 34px;
height: 34px;
margin-right: 8px;
margin-bottom: 8px;
}
}
.status img.avatar-retweeter {
width: 22px;
height: 22px;
position: absolute;
margin-left: 18px;
margin-top: 18px;
}
}
</style>
......@@ -6,7 +6,8 @@ const Timeline = {
props: [
'timeline',
'timelineName',
'title'
'title',
'userId'
],
computed: {
timelineError () { return this.$store.state.statuses.error }
......@@ -20,11 +21,14 @@ const Timeline = {
const credentials = store.state.users.currentUser.credentials
const showImmediately = this.timeline.visibleStatuses.length === 0
window.onscroll = this.scrollLoad
timelineFetcher.fetchAndUpdate({
store,
credentials,
timeline: this.timelineName,
showImmediately
showImmediately,
userId: this.userId
})
},
methods: {
......@@ -40,8 +44,15 @@ const Timeline = {
credentials,
timeline: this.timelineName,
older: true,
showImmediately: true
showImmediately: true,
userId: this.userId
}).then(() => store.commit('setLoading', { timeline: this.timelineName, value: false }))
},
scrollLoad (e) {
let height = Math.max(document.body.offsetHeight, document.body.scrollHeight)
if (this.timeline.loading === false && this.$store.state.config.autoLoad && (window.innerHeight + window.pageYOffset) >= (height - 750)) {
this.fetchOlderStatuses()
}
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment