...
 
Commits (29)
{ {
"presets": ["es2015", "stage-2", "env"], "presets": ["@babel/preset-env"],
"plugins": ["transform-runtime", "lodash", "transform-vue-jsx"], "plugins": ["@babel/plugin-transform-runtime", "lodash", "@vue/babel-plugin-transform-vue-jsx"],
"comments": false "comments": false
} }
...@@ -15,13 +15,13 @@ ...@@ -15,13 +15,13 @@
"lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs" "lint-fix": "eslint --fix --ext .js,.vue src test/unit/specs test/e2e/specs"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.7.6",
"@chenfengyuan/vue-qrcode": "^1.0.0", "@chenfengyuan/vue-qrcode": "^1.0.0",
"babel-plugin-add-module-exports": "^0.2.1",
"babel-plugin-lodash": "^3.2.11",
"body-scroll-lock": "^2.6.4", "body-scroll-lock": "^2.6.4",
"chromatism": "^3.0.0", "chromatism": "^3.0.0",
"cropperjs": "^1.4.3", "cropperjs": "^1.4.3",
"diff": "^3.0.1", "diff": "^3.0.1",
"javascript-detect-element-resize": "^0.5.3",
"karma-mocha-reporter": "^2.2.1", "karma-mocha-reporter": "^2.2.1",
"localforage": "^1.5.0", "localforage": "^1.5.0",
"object-path": "^0.11.3", "object-path": "^0.11.3",
...@@ -40,20 +40,17 @@ ...@@ -40,20 +40,17 @@
"whatwg-fetch": "^2.0.3" "whatwg-fetch": "^2.0.3"
}, },
"devDependencies": { "devDependencies": {
"@babel/polyfill": "^7.0.0", "@babel/core": "^7.7.5",
"@babel/plugin-transform-runtime": "^7.7.6",
"@babel/preset-env": "^7.7.6",
"@babel/register": "^7.7.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.0.0",
"@vue/babel-plugin-transform-vue-jsx": "^1.1.2",
"@vue/test-utils": "^1.0.0-beta.26", "@vue/test-utils": "^1.0.0-beta.26",
"autoprefixer": "^6.4.0", "autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0", "babel-eslint": "^7.0.0",
"babel-helper-vue-jsx-merge-props": "^2.0.3", "babel-loader": "^8.0.6",
"babel-loader": "^7.0.0", "babel-plugin-lodash": "^3.3.4",
"babel-plugin-syntax-jsx": "^6.18.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-plugin-transform-vue-jsx": "3",
"babel-preset-env": "^1.7.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
"chai": "^3.5.0", "chai": "^3.5.0",
"chalk": "^1.1.3", "chalk": "^1.1.3",
"chromedriver": "^2.21.2", "chromedriver": "^2.21.2",
......
...@@ -8,7 +8,7 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan ...@@ -8,7 +8,7 @@ import WhoToFollowPanel from './components/who_to_follow_panel/who_to_follow_pan
import ChatPanel from './components/chat_panel/chat_panel.vue' import ChatPanel from './components/chat_panel/chat_panel.vue'
import MediaModal from './components/media_modal/media_modal.vue' import MediaModal from './components/media_modal/media_modal.vue'
import SideDrawer from './components/side_drawer/side_drawer.vue' import SideDrawer from './components/side_drawer/side_drawer.vue'
import MobilePostStatusButton from './components/mobile_post_status_button/mobile_post_status_button.vue' import FloatingPostButton from './components/floating_post_button/floating_post_button.vue'
import MobileNav from './components/mobile_nav/mobile_nav.vue' import MobileNav from './components/mobile_nav/mobile_nav.vue'
import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue' import UserReportingModal from './components/user_reporting_modal/user_reporting_modal.vue'
import PostStatusModal from './components/post_status_modal/post_status_modal.vue' import PostStatusModal from './components/post_status_modal/post_status_modal.vue'
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
ChatPanel, ChatPanel,
MediaModal, MediaModal,
SideDrawer, SideDrawer,
MobilePostStatusButton, FloatingPostButton,
MobileNav, MobileNav,
UserReportingModal, UserReportingModal,
PostStatusModal PostStatusModal
......
...@@ -118,7 +118,7 @@ ...@@ -118,7 +118,7 @@
:floating="true" :floating="true"
class="floating-chat mobile-hidden" class="floating-chat mobile-hidden"
/> />
<MobilePostStatusButton /> <FloatingPostButton v-if="currentUser && isMobileLayout" />
<UserReportingModal /> <UserReportingModal />
<PostStatusModal /> <PostStatusModal />
<portal-target name="modal" /> <portal-target name="modal" />
......
import { debounce } from 'lodash' import { debounce } from 'lodash'
import 'javascript-detect-element-resize'
const MobilePostStatusButton = { // NOTE: We use the hard-coded boundingClientRect obj of the button at the moment
// since the button moves vertically and it makes difficult to determine the boundingClientRect
const buttonRect = {
bottom: -21, // Can get the actual bottom value by adding window.innerHeight
right: -21, // Can get the actual right value by adding window.innerWidth
height: 70,
width: 70
}
const FloatingPostButton = {
data () { data () {
return { return {
hidden: false,
scrollingDown: false, scrollingDown: false,
inputActive: false, inputActive: false,
oldScrollPos: 0, hoverInlineReply: false,
amountScrolled: 0 oldScrollPos: 0
} }
}, },
created () { created () {
if (this.autohideFloatingPostButton) { if (this.autohideFloatingPostButton) {
this.activateFloatingPostButtonAutohide() this.activateFloatingPostButtonAutohide()
} }
window.addEventListener('resize', this.handleOSK)
}, },
destroyed () { destroyed () {
if (this.autohideFloatingPostButton) { if (this.autohideFloatingPostButton) {
this.deactivateFloatingPostButtonAutohide() this.deactivateFloatingPostButtonAutohide()
} }
window.removeEventListener('resize', this.handleOSK)
}, },
computed: { computed: {
isLoggedIn () {
return !!this.$store.state.users.currentUser
},
isHidden () { isHidden () {
return this.autohideFloatingPostButton && (this.hidden || this.inputActive) return this.autohideFloatingPostButton && (this.scrollingDown || this.inputActive || this.hoverInlineReply)
}, },
autohideFloatingPostButton () { autohideFloatingPostButton () {
return !!this.$store.getters.mergedConfig.autohideFloatingPostButton return !!this.$store.getters.mergedConfig.autohideFloatingPostButton
},
postStatusForms () {
return this.$store.state.interface.postStatusForms
} }
}, },
watch: { watch: {
...@@ -46,10 +53,14 @@ const MobilePostStatusButton = { ...@@ -46,10 +53,14 @@ const MobilePostStatusButton = {
activateFloatingPostButtonAutohide () { activateFloatingPostButtonAutohide () {
window.addEventListener('scroll', this.handleScrollStart) window.addEventListener('scroll', this.handleScrollStart)
window.addEventListener('scroll', this.handleScrollEnd) window.addEventListener('scroll', this.handleScrollEnd)
window.addEventListener('resize', this.handleResize)
window.addResizeListener(document.body, this.checkHoverInlineReply)
}, },
deactivateFloatingPostButtonAutohide () { deactivateFloatingPostButtonAutohide () {
window.removeEventListener('scroll', this.handleScrollStart) window.removeEventListener('scroll', this.handleScrollStart)
window.removeEventListener('scroll', this.handleScrollEnd) window.removeEventListener('scroll', this.handleScrollEnd)
window.removeEventListener('resize', this.handleResize)
window.removeResizeListener(document.body, this.checkHoverInlineReply)
}, },
openPostForm () { openPostForm () {
this.$store.dispatch('openPostStatusModal') this.$store.dispatch('openPostStatusModal')
...@@ -74,20 +85,36 @@ const MobilePostStatusButton = { ...@@ -74,20 +85,36 @@ const MobilePostStatusButton = {
this.inputActive = false this.inputActive = false
} }
}, },
checkHoverInlineReply () {
this.hoverInlineReply = this.postStatusForms.some(form => {
const rect = form.getBoundingClientRect()
return rect.bottom > window.innerHeight + buttonRect.bottom - buttonRect.height &&
rect.right > window.innerWidth + buttonRect.right - buttonRect.width &&
rect.bottom - rect.height < window.innerHeight + buttonRect.bottom &&
rect.right - rect.width < window.innerWidth + buttonRect.right
})
},
handleScrollStart: debounce(function () { handleScrollStart: debounce(function () {
if (window.scrollY > this.oldScrollPos) { if (window.scrollY > this.oldScrollPos) {
this.hidden = true this.scrollingDown = true
} else { } else {
this.hidden = false this.scrollingDown = false
} }
this.oldScrollPos = window.scrollY this.oldScrollPos = window.scrollY
}, 100, { leading: true, trailing: false }), }, 100, { leading: true, trailing: false }),
handleScrollEnd: debounce(function () { handleScrollEnd: debounce(function () {
this.hidden = false this.scrollingDown = false
this.oldScrollPos = window.scrollY this.oldScrollPos = window.scrollY
}, 100, { leading: false, trailing: true })
this.checkHoverInlineReply()
}, 100, { leading: false, trailing: true }),
handleResize: debounce(function () {
this.handleOSK()
this.checkHoverInlineReply()
}, 100)
} }
} }
export default MobilePostStatusButton export default FloatingPostButton
<template> <template>
<div v-if="isLoggedIn"> <button
<button class="floating-post-button"
class="new-status-button" :class="{ 'hidden': isHidden }"
:class="{ 'hidden': isHidden }" @click="openPostForm"
@click="openPostForm" >
> <i class="icon-edit" />
<i class="icon-edit" /> </button>
</button>
</div>
</template> </template>
<script src="./mobile_post_status_button.js"></script> <script src="./floating_post_button.js"></script>
<style lang="scss"> <style lang="scss">
@import '../../_variables.scss'; @import '../../_variables.scss';
.new-status-button { .floating-post-button {
width: 5em; width: 5em;
height: 5em; height: 5em;
border-radius: 100%; border-radius: 100%;
...@@ -46,10 +44,4 @@ ...@@ -46,10 +44,4 @@
} }
} }
@media all and (min-width: 801px) {
.new-status-button {
display: none;
}
}
</style> </style>
...@@ -58,7 +58,7 @@ const LoginForm = { ...@@ -58,7 +58,7 @@ const LoginForm = {
).then((result) => { ).then((result) => {
if (result.error) { if (result.error) {
if (result.error === 'mfa_required') { if (result.error === 'mfa_required') {
this.requireMFA({ app: app, settings: result }) this.requireMFA({ settings: result })
} else if (result.identifier === 'password_reset_required') { } else if (result.identifier === 'password_reset_required') {
this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } }) this.$router.push({ name: 'password-reset', params: { passwordResetRequested: true } })
} else { } else {
......
...@@ -8,18 +8,23 @@ export default { ...@@ -8,18 +8,23 @@ export default {
}), }),
computed: { computed: {
...mapGetters({ ...mapGetters({
authApp: 'authFlow/app',
authSettings: 'authFlow/settings' authSettings: 'authFlow/settings'
}), }),
...mapState({ instance: 'instance' }) ...mapState({
instance: 'instance',
oauth: 'oauth'
})
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireTOTP', 'abortMFA']), ...mapMutations('authFlow', ['requireTOTP', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }), ...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
submit () { submit () {
const { clientId, clientSecret } = this.oauth
const data = { const data = {
app: this.authApp, clientId,
clientSecret,
instance: this.instance.server, instance: this.instance.server,
mfaToken: this.authSettings.mfa_token, mfaToken: this.authSettings.mfa_token,
code: this.code code: this.code
......
...@@ -7,18 +7,23 @@ export default { ...@@ -7,18 +7,23 @@ export default {
}), }),
computed: { computed: {
...mapGetters({ ...mapGetters({
authApp: 'authFlow/app',
authSettings: 'authFlow/settings' authSettings: 'authFlow/settings'
}), }),
...mapState({ instance: 'instance' }) ...mapState({
instance: 'instance',
oauth: 'oauth'
})
}, },
methods: { methods: {
...mapMutations('authFlow', ['requireRecovery', 'abortMFA']), ...mapMutations('authFlow', ['requireRecovery', 'abortMFA']),
...mapActions({ login: 'authFlow/login' }), ...mapActions({ login: 'authFlow/login' }),
clearError () { this.error = false }, clearError () { this.error = false },
submit () { submit () {
const { clientId, clientSecret } = this.oauth
const data = { const data = {
app: this.authApp, clientId,
clientSecret,
instance: this.instance.server, instance: this.instance.server,
mfaToken: this.authSettings.mfa_token, mfaToken: this.authSettings.mfa_token,
code: this.code code: this.code
......
...@@ -48,6 +48,11 @@ const PostStatusForm = { ...@@ -48,6 +48,11 @@ const PostStatusForm = {
if (this.replyTo) { if (this.replyTo) {
this.$refs.textarea.focus() this.$refs.textarea.focus()
} }
this.$store.dispatch('addNewPostStatusForm', this)
},
beforeDestroy () {
this.$store.dispatch('removePostStatusForm', this)
}, },
data () { data () {
const preset = this.$route.query.message const preset = this.$route.query.message
...@@ -374,6 +379,9 @@ const PostStatusForm = { ...@@ -374,6 +379,9 @@ const PostStatusForm = {
}, },
dismissScopeNotice () { dismissScopeNotice () {
this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true }) this.$store.dispatch('setOption', { name: 'hideScopeNotice', value: true })
},
getBoundingClientRect () {
return this.$refs.root.getBoundingClientRect()
} }
} }
} }
......
...@@ -65,7 +65,7 @@ const withLoadMore = ({ ...@@ -65,7 +65,7 @@ const withLoadMore = ({
} }
} }
}, },
render (createElement) { render (h) {
const props = { const props = {
props: { props: {
...this.$props, ...this.$props,
...@@ -74,7 +74,7 @@ const withLoadMore = ({ ...@@ -74,7 +74,7 @@ const withLoadMore = ({
on: this.$listeners, on: this.$listeners,
scopedSlots: this.$scopedSlots scopedSlots: this.$scopedSlots
} }
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value)) const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
return ( return (
<div class="with-load-more"> <div class="with-load-more">
<WrappedComponent {...props}> <WrappedComponent {...props}>
......
...@@ -49,7 +49,7 @@ const withSubscription = ({ ...@@ -49,7 +49,7 @@ const withSubscription = ({
} }
} }
}, },
render (createElement) { render (h) {
if (!this.error && !this.loading) { if (!this.error && !this.loading) {
const props = { const props = {
props: { props: {
...@@ -59,7 +59,7 @@ const withSubscription = ({ ...@@ -59,7 +59,7 @@ const withSubscription = ({
on: this.$listeners, on: this.$listeners,
scopedSlots: this.$scopedSlots scopedSlots: this.$scopedSlots
} }
const children = Object.entries(this.$slots).map(([key, value]) => createElement('template', { slot: key }, value)) const children = Object.entries(this.$slots).map(([key, value]) => h('template', { slot: key }, value))
return ( return (
<div class="with-subscription"> <div class="with-subscription">
<WrappedComponent {...props}> <WrappedComponent {...props}>
......
...@@ -7,7 +7,6 @@ const RECOVERY_STRATEGY = 'recovery' ...@@ -7,7 +7,6 @@ const RECOVERY_STRATEGY = 'recovery'
// initial state // initial state
const state = { const state = {
app: null,
settings: {}, settings: {},
strategy: PASSWORD_STRATEGY, strategy: PASSWORD_STRATEGY,
initStrategy: PASSWORD_STRATEGY // default strategy from config initStrategy: PASSWORD_STRATEGY // default strategy from config
...@@ -16,14 +15,10 @@ const state = { ...@@ -16,14 +15,10 @@ const state = {
const resetState = (state) => { const resetState = (state) => {
state.strategy = state.initStrategy state.strategy = state.initStrategy
state.settings = {} state.settings = {}
state.app = null
} }
// getters // getters
const getters = { const getters = {
app: (state, getters) => {
return state.app
},
settings: (state, getters) => { settings: (state, getters) => {
return state.settings return state.settings
}, },
...@@ -55,9 +50,8 @@ const mutations = { ...@@ -55,9 +50,8 @@ const mutations = {
requireToken (state) { requireToken (state) {
state.strategy = TOKEN_STRATEGY state.strategy = TOKEN_STRATEGY
}, },
requireMFA (state, { app, settings }) { requireMFA (state, { settings }) {
state.settings = settings state.settings = settings
state.app = app
state.strategy = TOTP_STRATEGY // default strategy of MFA state.strategy = TOTP_STRATEGY // default strategy of MFA
}, },
requireRecovery (state) { requireRecovery (state) {
......
...@@ -12,7 +12,8 @@ const defaultState = { ...@@ -12,7 +12,8 @@ const defaultState = {
window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)') window.CSS.supports('-webkit-filter', 'drop-shadow(0 0)')
) )
}, },
mobileLayout: false mobileLayout: false,
postStatusForms: []
} }
const interfaceMod = { const interfaceMod = {
...@@ -35,6 +36,12 @@ const interfaceMod = { ...@@ -35,6 +36,12 @@ const interfaceMod = {
}, },
setMobileLayout (state, value) { setMobileLayout (state, value) {
state.mobileLayout = value state.mobileLayout = value
},
addNewPostStatusForm (state, instance) {
state.postStatusForms.push(instance)
},
removePostStatusForm (state, instance) {
del(state.postStatusForms, state.postStatusForms.indexOf(instance))
} }
}, },
actions: { actions: {
...@@ -49,6 +56,12 @@ const interfaceMod = { ...@@ -49,6 +56,12 @@ const interfaceMod = {
}, },
setMobileLayout ({ commit }, value) { setMobileLayout ({ commit }, value) {
commit('setMobileLayout', value) commit('setMobileLayout', value)
},
addNewPostStatusForm ({ commit }, instance) {
commit('addNewPostStatusForm', instance)
},
removePostStatusForm ({ commit }, instance) {
commit('removePostStatusForm', instance)
} }
} }
} }
......
const verifyOTPCode = ({ app, instance, mfaToken, code }) => { const verifyOTPCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge` const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData() const form = new window.FormData()
form.append('client_id', app.client_id) form.append('client_id', clientId)
form.append('client_secret', app.client_secret) form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken) form.append('mfa_token', mfaToken)
form.append('code', code) form.append('code', code)
form.append('challenge_type', 'totp') form.append('challenge_type', 'totp')
...@@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => { ...@@ -14,12 +14,12 @@ const verifyOTPCode = ({ app, instance, mfaToken, code }) => {
}).then((data) => data.json()) }).then((data) => data.json())
} }
const verifyRecoveryCode = ({ app, instance, mfaToken, code }) => { const verifyRecoveryCode = ({ clientId, clientSecret, instance, mfaToken, code }) => {
const url = `${instance}/oauth/mfa/challenge` const url = `${instance}/oauth/mfa/challenge`
const form = new window.FormData() const form = new window.FormData()
form.append('client_id', app.client_id) form.append('client_id', clientId)
form.append('client_secret', app.client_secret) form.append('client_secret', clientSecret)
form.append('mfa_token', mfaToken) form.append('mfa_token', mfaToken)
form.append('code', code) form.append('code', code)
form.append('challenge_type', 'recovery') form.append('challenge_type', 'recovery')
......
require('babel-register') require('@babel/register')
var config = require('../../config') var config = require('../../config')
// http://nightwatchjs.org/guide#settings-file // http://nightwatchjs.org/guide#settings-file
......
This diff is collapsed.