Merge branch 'develop' into feature/version-info
This commit is contained in:
commit
3c5c09c74f
31 changed files with 706 additions and 400 deletions
|
@ -8,6 +8,7 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan
|
|||
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 MobilePostStatusModal from './components/mobile_post_status_modal/mobile_post_status_modal.vue'
|
||||
import { unseenNotificationsFromStore } from './services/notification_utils/notification_utils'
|
||||
|
||||
export default {
|
||||
|
@ -22,7 +23,8 @@ export default {
|
|||
WhoToFollowPanel,
|
||||
ChatPanel,
|
||||
MediaModal,
|
||||
SideDrawer
|
||||
SideDrawer,
|
||||
MobilePostStatusModal
|
||||
},
|
||||
data: () => ({
|
||||
mobileActivePanel: 'timeline',
|
||||
|
|
25
src/App.scss
25
src/App.scss
|
@ -671,6 +671,31 @@ nav {
|
|||
border-radius: var(--inputRadius, $fallback--inputRadius);
|
||||
}
|
||||
|
||||
@keyframes modal-background-fadein {
|
||||
from {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
to {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-view {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: auto;
|
||||
animation-duration: 0.2s;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
animation-name: modal-background-fadein;
|
||||
}
|
||||
|
||||
.button-icon {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<media-modal></media-modal>
|
||||
</div>
|
||||
<chat-panel :floating="true" v-if="currentUser && chat" class="floating-chat mobile-hidden"></chat-panel>
|
||||
<MobilePostStatusModal />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -4,10 +4,11 @@ import routes from './routes'
|
|||
|
||||
import App from '../App.vue'
|
||||
|
||||
const afterStoreSetup = ({ store, i18n }) => {
|
||||
window.fetch('/api/statusnet/config.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const getStatusnetConfig = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/api/statusnet/config.json')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const { name, closed: registrationClosed, textlimit, uploadlimit, server, vapidPublicKey } = data.site
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'name', value: name })
|
||||
|
@ -28,140 +29,167 @@ const afterStoreSetup = ({ store, i18n }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'vapidPublicKey', value: vapidPublicKey })
|
||||
}
|
||||
|
||||
var apiConfig = data.site.pleromafe
|
||||
return data.site.pleromafe
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Could not load statusnet config, potentially fatal')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
window.fetch('/static/config.json')
|
||||
.then((res) => res.json())
|
||||
.catch((err) => {
|
||||
console.warn('Failed to load static/config.json, continuing without it.')
|
||||
console.warn(err)
|
||||
return {}
|
||||
})
|
||||
.then((staticConfig) => {
|
||||
const overrides = window.___pleromafe_dev_overrides || {}
|
||||
const env = window.___pleromafe_mode.NODE_ENV
|
||||
const getStaticConfig = async () => {
|
||||
try {
|
||||
const res = await window.fetch('/static/config.json')
|
||||
if (res.ok) {
|
||||
return res.json()
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load static/config.json, continuing without it.')
|
||||
console.warn(error)
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
// This takes static config and overrides properties that are present in apiConfig
|
||||
let config = {}
|
||||
if (overrides.staticConfigPreference && env === 'development') {
|
||||
console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG')
|
||||
config = Object.assign({}, apiConfig, staticConfig)
|
||||
} else {
|
||||
config = Object.assign({}, staticConfig, apiConfig)
|
||||
}
|
||||
const setSettings = async ({ apiConfig, staticConfig, store }) => {
|
||||
const overrides = window.___pleromafe_dev_overrides || {}
|
||||
const env = window.___pleromafe_mode.NODE_ENV
|
||||
|
||||
const copyInstanceOption = (name) => {
|
||||
store.dispatch('setInstanceOption', {name, value: config[name]})
|
||||
}
|
||||
// This takes static config and overrides properties that are present in apiConfig
|
||||
let config = {}
|
||||
if (overrides.staticConfigPreference && env === 'development') {
|
||||
console.warn('OVERRIDING API CONFIG WITH STATIC CONFIG')
|
||||
config = Object.assign({}, apiConfig, staticConfig)
|
||||
} else {
|
||||
config = Object.assign({}, staticConfig, apiConfig)
|
||||
}
|
||||
|
||||
copyInstanceOption('nsfwCensorImage')
|
||||
copyInstanceOption('background')
|
||||
copyInstanceOption('hidePostStats')
|
||||
copyInstanceOption('hideUserStats')
|
||||
copyInstanceOption('hideFilteredStatuses')
|
||||
copyInstanceOption('logo')
|
||||
const copyInstanceOption = (name) => {
|
||||
store.dispatch('setInstanceOption', { name, value: config[name] })
|
||||
}
|
||||
|
||||
store.dispatch('setInstanceOption', {
|
||||
name: 'logoMask',
|
||||
value: typeof config.logoMask === 'undefined'
|
||||
? true
|
||||
: config.logoMask
|
||||
})
|
||||
copyInstanceOption('nsfwCensorImage')
|
||||
copyInstanceOption('background')
|
||||
copyInstanceOption('hidePostStats')
|
||||
copyInstanceOption('hideUserStats')
|
||||
copyInstanceOption('hideFilteredStatuses')
|
||||
copyInstanceOption('logo')
|
||||
|
||||
store.dispatch('setInstanceOption', {
|
||||
name: 'logoMargin',
|
||||
value: typeof config.logoMargin === 'undefined'
|
||||
? 0
|
||||
: config.logoMargin
|
||||
})
|
||||
store.dispatch('setInstanceOption', {
|
||||
name: 'logoMask',
|
||||
value: typeof config.logoMask === 'undefined'
|
||||
? true
|
||||
: config.logoMask
|
||||
})
|
||||
|
||||
copyInstanceOption('redirectRootNoLogin')
|
||||
copyInstanceOption('redirectRootLogin')
|
||||
copyInstanceOption('showInstanceSpecificPanel')
|
||||
copyInstanceOption('scopeOptionsEnabled')
|
||||
copyInstanceOption('formattingOptionsEnabled')
|
||||
copyInstanceOption('collapseMessageWithSubject')
|
||||
copyInstanceOption('loginMethod')
|
||||
copyInstanceOption('scopeCopy')
|
||||
copyInstanceOption('subjectLineBehavior')
|
||||
copyInstanceOption('postContentType')
|
||||
copyInstanceOption('alwaysShowSubjectInput')
|
||||
copyInstanceOption('noAttachmentLinks')
|
||||
copyInstanceOption('showFeaturesPanel')
|
||||
store.dispatch('setInstanceOption', {
|
||||
name: 'logoMargin',
|
||||
value: typeof config.logoMargin === 'undefined'
|
||||
? 0
|
||||
: config.logoMargin
|
||||
})
|
||||
|
||||
if ((config.chatDisabled)) {
|
||||
store.dispatch('disableChat')
|
||||
} else {
|
||||
store.dispatch('initializeSocket')
|
||||
}
|
||||
copyInstanceOption('redirectRootNoLogin')
|
||||
copyInstanceOption('redirectRootLogin')
|
||||
copyInstanceOption('showInstanceSpecificPanel')
|
||||
copyInstanceOption('scopeOptionsEnabled')
|
||||
copyInstanceOption('formattingOptionsEnabled')
|
||||
copyInstanceOption('collapseMessageWithSubject')
|
||||
copyInstanceOption('loginMethod')
|
||||
copyInstanceOption('scopeCopy')
|
||||
copyInstanceOption('subjectLineBehavior')
|
||||
copyInstanceOption('postContentType')
|
||||
copyInstanceOption('alwaysShowSubjectInput')
|
||||
copyInstanceOption('noAttachmentLinks')
|
||||
copyInstanceOption('showFeaturesPanel')
|
||||
|
||||
return store.dispatch('setTheme', config['theme'])
|
||||
})
|
||||
.then(() => {
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: routes(store),
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
if (to.matched.some(m => m.meta.dontScroll)) {
|
||||
return false
|
||||
}
|
||||
return savedPosition || { x: 0, y: 0 }
|
||||
}
|
||||
})
|
||||
if ((config.chatDisabled)) {
|
||||
store.dispatch('disableChat')
|
||||
} else {
|
||||
store.dispatch('initializeSocket')
|
||||
}
|
||||
|
||||
/* eslint-disable no-new */
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
||||
})
|
||||
})
|
||||
return store.dispatch('setTheme', config['theme'])
|
||||
}
|
||||
|
||||
window.fetch('/static/terms-of-service.html')
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
const getTOS = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/static/terms-of-service.html')
|
||||
if (res.ok) {
|
||||
const html = await res.text()
|
||||
store.dispatch('setInstanceOption', { name: 'tos', value: html })
|
||||
})
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load TOS")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
window.fetch('/api/pleroma/emoji.json')
|
||||
.then(
|
||||
(res) => res.json()
|
||||
.then(
|
||||
(values) => {
|
||||
const emoji = Object.keys(values).map((key) => {
|
||||
return { shortcode: key, image_url: values[key] }
|
||||
})
|
||||
store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true })
|
||||
},
|
||||
(failure) => {
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false })
|
||||
}
|
||||
),
|
||||
(error) => console.log(error)
|
||||
)
|
||||
const getInstancePanel = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/instance/panel.html')
|
||||
if (res.ok) {
|
||||
const html = await res.text()
|
||||
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load instance panel")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
window.fetch('/static/emoji.json')
|
||||
.then((res) => res.json())
|
||||
.then((values) => {
|
||||
const getStaticEmoji = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/static/emoji.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const emoji = Object.keys(values).map((key) => {
|
||||
return { shortcode: key, image_url: false, 'utf': values[key] }
|
||||
})
|
||||
store.dispatch('setInstanceOption', { name: 'emoji', value: emoji })
|
||||
})
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Can't load static emoji")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
window.fetch('/instance/panel.html')
|
||||
.then((res) => res.text())
|
||||
.then((html) => {
|
||||
store.dispatch('setInstanceOption', { name: 'instanceSpecificPanelContent', value: html })
|
||||
})
|
||||
// This is also used to indicate if we have a 'pleroma backend' or not.
|
||||
// Somewhat weird, should probably be somewhere else.
|
||||
const getCustomEmoji = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/api/pleroma/emoji.json')
|
||||
if (res.ok) {
|
||||
const values = await res.json()
|
||||
const emoji = Object.keys(values).map((key) => {
|
||||
return { shortcode: key, image_url: values[key] }
|
||||
})
|
||||
store.dispatch('setInstanceOption', { name: 'customEmoji', value: emoji })
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: true })
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: false })
|
||||
console.warn("Can't load custom emojis, maybe not a Pleroma instance?")
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
window.fetch('/nodeinfo/2.0.json')
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const getNodeInfo = async ({ store }) => {
|
||||
try {
|
||||
const res = await window.fetch('/nodeinfo/2.0.json')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const metadata = data.metadata
|
||||
|
||||
const features = metadata.features
|
||||
|
@ -169,8 +197,6 @@ const afterStoreSetup = ({ store, i18n }) => {
|
|||
store.dispatch('setInstanceOption', { name: 'chatAvailable', value: features.includes('chat') })
|
||||
store.dispatch('setInstanceOption', { name: 'gopherAvailable', value: features.includes('gopher') })
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'postFormats', value: metadata.postFormats })
|
||||
|
||||
store.dispatch('setInstanceOption', { name: 'restrictedNicknames', value: metadata.restrictedNicknames })
|
||||
|
||||
const suggestions = metadata.suggestions
|
||||
|
@ -182,7 +208,49 @@ const afterStoreSetup = ({ store, i18n }) => {
|
|||
|
||||
const frontendVersion = window.___pleromafe_commit_hash
|
||||
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
|
||||
})
|
||||
} else {
|
||||
throw (res)
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Could not load nodeinfo')
|
||||
console.warn(e)
|
||||
}
|
||||
}
|
||||
|
||||
const afterStoreSetup = async ({ store, i18n }) => {
|
||||
const apiConfig = await getStatusnetConfig({ store })
|
||||
const staticConfig = await getStaticConfig()
|
||||
await setSettings({ store, apiConfig, staticConfig })
|
||||
await getTOS({ store })
|
||||
await getInstancePanel({ store })
|
||||
await getStaticEmoji({ store })
|
||||
await getCustomEmoji({ store })
|
||||
await getNodeInfo({ store })
|
||||
|
||||
// Now we have the server settings and can try logging in
|
||||
if (store.state.oauth.token) {
|
||||
store.dispatch('loginUser', store.state.oauth.token)
|
||||
}
|
||||
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: routes(store),
|
||||
scrollBehavior: (to, _from, savedPosition) => {
|
||||
if (to.matched.some(m => m.meta.dontScroll)) {
|
||||
return false
|
||||
}
|
||||
return savedPosition || { x: 0, y: 0 }
|
||||
}
|
||||
})
|
||||
|
||||
/* eslint-disable no-new */
|
||||
return new Vue({
|
||||
router,
|
||||
store,
|
||||
i18n,
|
||||
el: '#app',
|
||||
render: h => h(App)
|
||||
})
|
||||
}
|
||||
|
||||
export default afterStoreSetup
|
||||
|
|
|
@ -13,7 +13,6 @@ import FollowRequests from 'components/follow_requests/follow_requests.vue'
|
|||
import OAuthCallback from 'components/oauth_callback/oauth_callback.vue'
|
||||
import UserSearch from 'components/user_search/user_search.vue'
|
||||
import Notifications from 'components/notifications/notifications.vue'
|
||||
import UserPanel from 'components/user_panel/user_panel.vue'
|
||||
import LoginForm from 'components/login_form/login_form.vue'
|
||||
import ChatPanel from 'components/chat_panel/chat_panel.vue'
|
||||
import WhoToFollow from 'components/who_to_follow/who_to_follow.vue'
|
||||
|
@ -43,7 +42,6 @@ export default (store) => {
|
|||
{ name: 'friend-requests', path: '/friend-requests', component: FollowRequests },
|
||||
{ name: 'user-settings', path: '/user-settings', component: UserSettings },
|
||||
{ name: 'notifications', path: '/:username/notifications', component: Notifications },
|
||||
{ name: 'new-status', path: '/:username/new-status', component: UserPanel },
|
||||
{ name: 'login', path: '/login', component: LoginForm },
|
||||
{ name: 'chat', path: '/chat', component: ChatPanel, props: () => ({ floating: false }) },
|
||||
{ name: 'oauth-callback', path: '/oauth-callback', component: OAuthCallback, props: (route) => ({ code: route.query.code }) },
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="modal-view" v-if="showing" @click.prevent="hide">
|
||||
<div class="modal-view media-modal-view" v-if="showing" @click.prevent="hide">
|
||||
<img class="modal-image" v-if="type === 'image'" :src="currentMedia.url"></img>
|
||||
<VideoAttachment
|
||||
class="modal-image"
|
||||
|
@ -32,18 +32,7 @@
|
|||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.modal-view {
|
||||
z-index: 1000;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
|
||||
.media-modal-view {
|
||||
&:hover {
|
||||
.modal-view-button-arrow {
|
||||
opacity: 0.75;
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import PostStatusForm from '../post_status_form/post_status_form.vue'
|
||||
import { throttle } from 'lodash'
|
||||
|
||||
const MobilePostStatusModal = {
|
||||
components: {
|
||||
PostStatusForm
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hidden: false,
|
||||
postFormOpen: false,
|
||||
scrollingDown: false,
|
||||
inputActive: false,
|
||||
oldScrollPos: 0,
|
||||
amountScrolled: 0
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('scroll', this.handleScroll)
|
||||
window.addEventListener('resize', this.handleOSK)
|
||||
},
|
||||
destroyed () {
|
||||
window.removeEventListener('scroll', this.handleScroll)
|
||||
window.removeEventListener('resize', this.handleOSK)
|
||||
},
|
||||
computed: {
|
||||
currentUser () {
|
||||
return this.$store.state.users.currentUser
|
||||
},
|
||||
isHidden () {
|
||||
return this.hidden || this.inputActive
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPostForm () {
|
||||
this.postFormOpen = true
|
||||
this.hidden = true
|
||||
|
||||
const el = this.$el.querySelector('textarea')
|
||||
this.$nextTick(function () {
|
||||
el.focus()
|
||||
})
|
||||
},
|
||||
closePostForm () {
|
||||
this.postFormOpen = false
|
||||
this.hidden = false
|
||||
},
|
||||
handleOSK () {
|
||||
// This is a big hack: we're guessing from changed window sizes if the
|
||||
// on-screen keyboard is active or not. This is only really important
|
||||
// for phones in portrait mode and it's more important to show the button
|
||||
// in normal scenarios on all phones, than it is to hide it when the
|
||||
// keyboard is active.
|
||||
// Guesswork based on https://www.mydevice.io/#compare-devices
|
||||
|
||||
// for example, iphone 4 and android phones from the same time period
|
||||
const smallPhone = window.innerWidth < 350
|
||||
const smallPhoneKbOpen = smallPhone && window.innerHeight < 345
|
||||
|
||||
const biggerPhone = !smallPhone && window.innerWidth < 450
|
||||
const biggerPhoneKbOpen = biggerPhone && window.innerHeight < 560
|
||||
if (smallPhoneKbOpen || biggerPhoneKbOpen) {
|
||||
this.inputActive = true
|
||||
} else {
|
||||
this.inputActive = false
|
||||
}
|
||||
},
|
||||
handleScroll: throttle(function () {
|
||||
const scrollAmount = window.scrollY - this.oldScrollPos
|
||||
const scrollingDown = scrollAmount > 0
|
||||
|
||||
if (scrollingDown !== this.scrollingDown) {
|
||||
this.amountScrolled = 0
|
||||
this.scrollingDown = scrollingDown
|
||||
if (!scrollingDown) {
|
||||
this.hidden = false
|
||||
}
|
||||
} else if (scrollingDown) {
|
||||
this.amountScrolled += scrollAmount
|
||||
if (this.amountScrolled > 100 && !this.hidden) {
|
||||
this.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
this.oldScrollPos = window.scrollY
|
||||
this.scrollingDown = scrollingDown
|
||||
}, 100)
|
||||
}
|
||||
}
|
||||
|
||||
export default MobilePostStatusModal
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<div v-if="currentUser">
|
||||
<div
|
||||
class="post-form-modal-view modal-view"
|
||||
v-show="postFormOpen"
|
||||
@click="closePostForm"
|
||||
>
|
||||
<div class="post-form-modal-panel panel" @click.stop="">
|
||||
<div class="panel-heading">{{$t('post_status.new_status')}}</div>
|
||||
<PostStatusForm class="panel-body" @posted="closePostForm"/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="new-status-button"
|
||||
:class="{ 'hidden': isHidden }"
|
||||
@click="openPostForm"
|
||||
>
|
||||
<i class="icon-edit" />
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="./mobile_post_status_modal.js"></script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '../../_variables.scss';
|
||||
|
||||
.post-form-modal-view {
|
||||
max-height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-form-modal-panel {
|
||||
flex-shrink: 0;
|
||||
margin: 25% 0 4em 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.new-status-button {
|
||||
width: 5em;
|
||||
height: 5em;
|
||||
border-radius: 100%;
|
||||
position: fixed;
|
||||
bottom: 1.5em;
|
||||
right: 1.5em;
|
||||
// TODO: this needs its own color, it has to stand out enough and link color
|
||||
// is not very optimal for this particular use.
|
||||
background-color: $fallback--fg;
|
||||
background-color: var(--btn, $fallback--fg);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.3), 0px 4px 6px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
|
||||
transition: 0.35s transform;
|
||||
transition-timing-function: cubic-bezier(0, 1, 0.5, 1);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(150%);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1.5em;
|
||||
color: $fallback--text;
|
||||
color: var(--text, $fallback--text);
|
||||
}
|
||||
}
|
||||
|
||||
@media all and (min-width: 801px) {
|
||||
.new-status-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
|
@ -15,12 +15,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<ul>
|
||||
<li v-if="currentUser" @click="toggleDrawer">
|
||||
<router-link :to="{ name: 'new-status', params: { username: currentUser.screen_name } }">
|
||||
{{ $t("post_status.new_status") }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li v-else @click="toggleDrawer">
|
||||
<li v-if="!currentUser" @click="toggleDrawer">
|
||||
<router-link :to="{ name: 'login' }">
|
||||
{{ $t("login.login") }}
|
||||
</router-link>
|
||||
|
@ -119,14 +114,14 @@
|
|||
}
|
||||
|
||||
.side-drawer-container-open {
|
||||
transition-delay: 0.0s;
|
||||
transition-property: left;
|
||||
transition: 0.35s;
|
||||
transition-property: background-color;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.side-drawer-container-closed {
|
||||
left: -100%;
|
||||
transition-delay: 0.5s;
|
||||
transition-property: left;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.side-drawer-click-outside {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div :title="user.name" class='user-name' v-if="user.name_html" v-html="user.name_html"></div>
|
||||
<div :title="user.name" class='user-name' v-else>{{user.name}}</div>
|
||||
<router-link :to="{ name: 'user-settings' }" v-if="!isOtherUser">
|
||||
<i class="button-icon icon-cog usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
<i class="button-icon icon-pencil usersettings" :title="$t('tool_tip.user_settings')"></i>
|
||||
</router-link>
|
||||
<a :href="user.statusnet_profile_url" target="_blank" v-if="isOtherUser && !user.is_local">
|
||||
<i class="icon-link-ext usersettings"></i>
|
||||
|
@ -159,6 +159,18 @@
|
|||
|
||||
&-bio {
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
max-height: 400px;
|
||||
|
||||
.emoji {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modifiers
|
||||
|
|
|
@ -71,7 +71,9 @@
|
|||
"account_not_locked_warning_link": "uzamčen",
|
||||
"attachments_sensitive": "Označovat přílohy jako citlivé",
|
||||
"content_type": {
|
||||
"plain_text": "Prostý text"
|
||||
"plain_text": "Prostý text",
|
||||
"text/html": "HTML",
|
||||
"text/markdown": "Markdown"
|
||||
},
|
||||
"content_warning": "Předmět (volitelný)",
|
||||
"default": "Právě jsem přistál v L.A.",
|
||||
|
@ -95,7 +97,7 @@
|
|||
"new_captcha": "Kliknutím na obrázek získáte novou CAPTCHA",
|
||||
"username_placeholder": "např. lain",
|
||||
"fullname_placeholder": "např. Lain Iwakura",
|
||||
"bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka a žiji v příměstském Japonsku. Možná mě znáte z Wired.",
|
||||
"bio_placeholder": "např.\nNazdar, jsem Lain\nJsem anime dívka žijící v příměstském Japonsku. Možná mě znáte z Wired.",
|
||||
"validations": {
|
||||
"username_required": "nemůže být prázdné",
|
||||
"fullname_required": "nemůže být prázdné",
|
||||
|
@ -204,7 +206,7 @@
|
|||
"radii_help": "Nastavit zakulacení rohů rozhraní (v pixelech)",
|
||||
"replies_in_timeline": "Odpovědi v časové ose",
|
||||
"reply_link_preview": "Povolit náhledy odkazu pro odpověď při přejetí myši",
|
||||
"reply_visibility_all": "Zobrazit všechny odpovědiShow all replies",
|
||||
"reply_visibility_all": "Zobrazit všechny odpovědi",
|
||||
"reply_visibility_following": "Zobrazit pouze odpovědi směřované na mě nebo uživatele, které sleduji",
|
||||
"reply_visibility_self": "Zobrazit pouze odpovědi směřované na mě",
|
||||
"saving_err": "Chyba při ukládání nastavení",
|
||||
|
@ -221,7 +223,6 @@
|
|||
"subject_line_mastodon": "Jako u Mastodonu: zkopírovat tak, jak je",
|
||||
"subject_line_noop": "Nekopírovat",
|
||||
"post_status_content_type": "Publikovat typ obsahu příspěvku",
|
||||
"status_content_type_plain": "Prostý text",
|
||||
"stop_gifs": "Přehrávat GIFy při přejetí myši",
|
||||
"streaming": "Povolit automatické streamování nových příspěvků při rolování nahoru",
|
||||
"text": "Text",
|
||||
|
@ -339,7 +340,7 @@
|
|||
"button": "Tlačítko",
|
||||
"text": "Spousta dalšího {0} a {1}",
|
||||
"mono": "obsahu",
|
||||
"input": "Just landed in L.A.",
|
||||
"input": "Právě jsem přistál v L.A.",
|
||||
"faint_link": "pomocný manuál",
|
||||
"fine_print": "Přečtěte si náš {0} a nenaučte se nic užitečného!",
|
||||
"header_faint": "Tohle je v pohodě",
|
||||
|
@ -361,7 +362,7 @@
|
|||
"no_statuses": "Žádné příspěvky"
|
||||
},
|
||||
"status": {
|
||||
"reply_to": "Odpovědět uživateli",
|
||||
"reply_to": "Odpověď uživateli",
|
||||
"replies_list": "Odpovědi:"
|
||||
},
|
||||
|
||||
|
@ -413,7 +414,7 @@
|
|||
"upload":{
|
||||
"error": {
|
||||
"base": "Nahrávání selhalo.",
|
||||
"file_too_big": "Soubor je úříliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
||||
"file_too_big": "Soubor je příliš velký [{filesize}{filesizeunit} / {allowedsize}{allowedsizeunit}]",
|
||||
"default": "Zkuste to znovu později"
|
||||
},
|
||||
"file_size_units": {
|
||||
|
|
|
@ -53,9 +53,10 @@ const persistedStateOptions = {
|
|||
'users.lastLoginName',
|
||||
'oauth'
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
createPersistedState(persistedStateOptions).then((persistedState) => {
|
||||
(async () => {
|
||||
const persistedState = await createPersistedState(persistedStateOptions)
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
interface: interfaceModule,
|
||||
|
@ -75,7 +76,7 @@ createPersistedState(persistedStateOptions).then((persistedState) => {
|
|||
})
|
||||
|
||||
afterStoreSetup({ store, i18n })
|
||||
})
|
||||
})()
|
||||
|
||||
// These are inlined by webpack's DefinePlugin
|
||||
/* eslint-disable */
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
const chat = {
|
||||
state: {
|
||||
messages: [],
|
||||
channel: {state: ''}
|
||||
channel: {state: ''},
|
||||
socket: null
|
||||
},
|
||||
mutations: {
|
||||
setChannel (state, channel) {
|
||||
state.channel = channel
|
||||
},
|
||||
setSocket (state, socket) {
|
||||
state.socket = socket
|
||||
},
|
||||
addMessage (state, message) {
|
||||
state.messages.push(message)
|
||||
state.messages = state.messages.slice(-19, 20)
|
||||
|
@ -16,8 +20,12 @@ const chat = {
|
|||
}
|
||||
},
|
||||
actions: {
|
||||
disconnectFromChat (store) {
|
||||
store.state.socket.disconnect()
|
||||
},
|
||||
initializeChat (store, socket) {
|
||||
const channel = socket.channel('chat:public')
|
||||
store.commit('setSocket', socket)
|
||||
channel.on('new_msg', (msg) => {
|
||||
store.commit('addMessage', msg)
|
||||
})
|
||||
|
|
|
@ -333,6 +333,7 @@ export const mutations = {
|
|||
oldTimeline.newStatusCount = 0
|
||||
oldTimeline.visibleStatuses = slice(oldTimeline.statuses, 0, 50)
|
||||
oldTimeline.minVisibleId = last(oldTimeline.visibleStatuses).id
|
||||
oldTimeline.minId = oldTimeline.minVisibleId
|
||||
oldTimeline.visibleStatusesObject = {}
|
||||
each(oldTimeline.visibleStatuses, (status) => { oldTimeline.visibleStatusesObject[status.id] = status })
|
||||
},
|
||||
|
|
|
@ -292,6 +292,7 @@ const users = {
|
|||
|
||||
logout (store) {
|
||||
store.commit('clearCurrentUser')
|
||||
store.dispatch('disconnectFromChat')
|
||||
store.commit('setToken', false)
|
||||
store.dispatch('stopFetching', 'friends')
|
||||
store.commit('setBackendInteractor', backendInteractorService())
|
||||
|
@ -321,6 +322,9 @@ const users = {
|
|||
|
||||
if (user.token) {
|
||||
store.dispatch('setWsToken', user.token)
|
||||
|
||||
// Initialize the chat socket.
|
||||
store.dispatch('initializeSocket')
|
||||
}
|
||||
|
||||
// Start getting fresh posts.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue