Merge remote-tracking branch 'upstream/develop' into streaming

* upstream/develop: (51 commits)
  toggle_activation api is also deprecated
  use vuex action
  refactor toggleActivationStatus
  replace setActivationStatus api with new one
  use flex for stickers
  i18n/update-ja_easy
  Use a centralized fallback for missing values and use instance.federating instead of instance.federation.enabled
  Add fallback in case BE does not report federating status in nodeinfo
  The value we are looking for is federationPolicy.enabled, not federationPolicy.federating
  Logic should be to hide TWKN if not federating OR if instance is not public
  Finally trust eslint
  More lint
  More lint
  Lint
  mfa: removed unused code
  increase icon width a little bit in the nav panel
  add icons to nav panel
  Revert "Merge branch 'revert-96cab6d8' into 'develop'"
  Support "native" captcha
  Revert "Merge branch 'oauth-extra-scopes' into 'develop'"
  ...
This commit is contained in:
Henry Jameson 2019-12-26 12:47:51 +02:00
commit b619477573
35 changed files with 1206 additions and 691 deletions

View file

@ -1,5 +1,5 @@
{ {
"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
} }

View file

@ -77,6 +77,9 @@ Use custom image for NSFW'd images
### `showFeaturesPanel` ### `showFeaturesPanel`
Show panel showcasing instance features/settings to logged-out visitors Show panel showcasing instance features/settings to logged-out visitors
### `hideSitename`
Hide instance name in header
## Indirect configuration ## Indirect configuration
Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it. Some features are configured depending on how backend is configured. In general the approach is "if backend allows it there's no need to hide it, if backend doesn't allow it there's no need to show it.
@ -96,3 +99,6 @@ Setting this will change the warning text that is displayed for direct messages.
ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that. ATTENTION: If you actually want the behavior to change. You will need to set the appropriate option at the backend. See the backend documentation for information about that.
DO NOT activate this without checking the backend configuration first! DO NOT activate this without checking the backend configuration first!
### Private Mode
If the `private` instance setting is enabled in the backend, features that are not accessible without authentication, such as the timelines and search will be disabled for unauthenticated users.

View file

@ -15,9 +15,8 @@
"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",
@ -40,20 +39,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",

View file

@ -90,6 +90,7 @@ export default {
}, },
sitename () { return this.$store.state.instance.name }, sitename () { return this.$store.state.instance.name },
chat () { return this.$store.state.chat.channel.state === 'joined' }, chat () { return this.$store.state.chat.channel.state === 'joined' },
hideSitename () { return this.$store.state.instance.hideSitename },
suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled }, suggestionsEnabled () { return this.$store.state.instance.suggestionsEnabled },
showInstanceSpecificPanel () { showInstanceSpecificPanel () {
return this.$store.state.instance.showInstanceSpecificPanel && return this.$store.state.instance.showInstanceSpecificPanel &&
@ -97,7 +98,8 @@ export default {
this.$store.state.instance.instanceSpecificPanelContent this.$store.state.instance.instanceSpecificPanelContent
}, },
showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel }, showFeaturesPanel () { return this.$store.state.instance.showFeaturesPanel },
isMobileLayout () { return this.$store.state.interface.mobileLayout } isMobileLayout () { return this.$store.state.interface.mobileLayout },
privateMode () { return this.$store.state.instance.private }
}, },
methods: { methods: {
scrollToTop () { scrollToTop () {

View file

@ -870,3 +870,16 @@ nav {
transform: rotate(359deg); transform: rotate(359deg);
} }
} }
.new-status-notification {
position:relative;
margin-top: -1px;
font-size: 1.1em;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
padding: 10px;
z-index: 1;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}

View file

@ -31,6 +31,7 @@
</div> </div>
<div class="item"> <div class="item">
<router-link <router-link
v-if="!hideSitename"
class="site-name" class="site-name"
:to="{ name: 'root' }" :to="{ name: 'root' }"
active-class="home" active-class="home"
@ -40,6 +41,7 @@
</div> </div>
<div class="item right"> <div class="item right">
<search-bar <search-bar
v-if="currentUser || !privateMode"
class="nav-icon mobile-hidden" class="nav-icon mobile-hidden"
@toggled="onSearchBarToggled" @toggled="onSearchBarToggled"
@click.stop.native @click.stop.native

View file

@ -108,6 +108,7 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('alwaysShowSubjectInput') copyInstanceOption('alwaysShowSubjectInput')
copyInstanceOption('noAttachmentLinks') copyInstanceOption('noAttachmentLinks')
copyInstanceOption('showFeaturesPanel') copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename')
return store.dispatch('setTheme', config['theme']) return store.dispatch('setTheme', config['theme'])
} }
@ -218,12 +219,21 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version }) store.dispatch('setInstanceOption', { name: 'backendVersion', value: software.version })
store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' }) store.dispatch('setInstanceOption', { name: 'pleromaBackend', value: software.name === 'pleroma' })
const priv = metadata.private
store.dispatch('setInstanceOption', { name: 'private', value: priv })
const frontendVersion = window.___pleromafe_commit_hash const frontendVersion = window.___pleromafe_commit_hash
store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion }) store.dispatch('setInstanceOption', { name: 'frontendVersion', value: frontendVersion })
store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') }) store.dispatch('setInstanceOption', { name: 'tagPolicyAvailable', value: metadata.federation.mrf_policies.includes('TagPolicy') })
const federation = metadata.federation const federation = metadata.federation
store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation }) store.dispatch('setInstanceOption', { name: 'federationPolicy', value: federation })
store.dispatch('setInstanceOption', {
name: 'federating',
value: typeof federation.enabled === 'undefined'
? true
: federation.enabled
})
const accounts = metadata.staffAccounts const accounts = metadata.staffAccounts
await resolveStaffAccounts({ store, accounts }) await resolveStaffAccounts({ store, accounts })

View file

@ -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 {

View file

@ -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

View file

@ -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

View file

@ -29,6 +29,7 @@ const MobileNav = {
unseenNotificationsCount () { unseenNotificationsCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name } sitename () { return this.$store.state.instance.name }
}, },
methods: { methods: {

View file

@ -17,6 +17,7 @@
<i class="button-icon icon-menu" /> <i class="button-icon icon-menu" />
</a> </a>
<router-link <router-link
v-if="!hideSitename"
class="site-name" class="site-name"
:to="{ name: 'root' }" :to="{ name: 'root' }"
active-class="home" active-class="home"

View file

@ -71,12 +71,7 @@ const ModerationTools = {
} }
}, },
toggleActivationStatus () { toggleActivationStatus () {
const store = this.$store this.$store.dispatch('toggleActivationStatus', { user: this.user })
const status = !!this.user.deactivated
store.state.api.backendInteractor.setActivationStatus({ user: this.user, status }).then(response => {
if (!response.ok) { return }
store.commit('updateActivationStatus', { user: this.user, status: status })
})
}, },
deleteUserDialog (show) { deleteUserDialog (show) {
this.showDeleteUserDialog = show this.showDeleteUserDialog = show

View file

@ -1,20 +1,18 @@
import { mapState } from 'vuex'
const NavPanel = { const NavPanel = {
created () { created () {
if (this.currentUser && this.currentUser.locked) { if (this.currentUser && this.currentUser.locked) {
this.$store.dispatch('startFetchingFollowRequest') this.$store.dispatch('startFetchingFollowRequest')
} }
}, },
computed: { computed: mapState({
currentUser () { currentUser: state => state.users.currentUser,
return this.$store.state.users.currentUser chat: state => state.chat.channel,
}, followRequestCount: state => state.api.followRequests.length,
chat () { privateMode: state => state.instance.private,
return this.$store.state.chat.channel federating: state => state.instance.federating
}, })
followRequestCount () {
return this.$store.state.api.followRequests.length
}
}
} }
export default NavPanel export default NavPanel

View file

@ -4,22 +4,22 @@
<ul> <ul>
<li v-if="currentUser"> <li v-if="currentUser">
<router-link :to="{ name: 'friends' }"> <router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }} <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser"> <li v-if="currentUser">
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }} <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser"> <li v-if="currentUser">
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }} <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link> </router-link>
</li> </li>
<li v-if="currentUser && currentUser.locked"> <li v-if="currentUser && currentUser.locked">
<router-link :to="{ name: 'friend-requests' }"> <router-link :to="{ name: 'friend-requests' }">
{{ $t("nav.friend_requests") }} <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span <span
v-if="followRequestCount > 0" v-if="followRequestCount > 0"
class="badge follow-request-count" class="badge follow-request-count"
@ -28,19 +28,19 @@
</span> </span>
</router-link> </router-link>
</li> </li>
<li> <li v-if="currentUser || !privateMode">
<router-link :to="{ name: 'public-timeline' }"> <router-link :to="{ name: 'public-timeline' }">
{{ $t("nav.public_tl") }} <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link> </router-link>
</li> </li>
<li> <li v-if="federating && !privateMode">
<router-link :to="{ name: 'public-external-timeline' }"> <router-link :to="{ name: 'public-external-timeline' }">
{{ $t("nav.twkn") }} <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link> </router-link>
</li> </li>
<li> <li>
<router-link :to="{ name: 'about' }"> <router-link :to="{ name: 'about' }">
{{ $t("nav.about") }} <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
@ -113,4 +113,8 @@
} }
} }
} }
.nav-panel .button-icon:before {
width: 1.1em;
}
</style> </style>

View file

@ -172,7 +172,7 @@
for="captcha-label" for="captcha-label"
>{{ $t('captcha') }}</label> >{{ $t('captcha') }}</label>
<template v-if="captcha.type == 'kocaptcha'"> <template v-if="['kocaptcha', 'native'].includes(captcha.type)">
<img <img
:src="captcha.url" :src="captcha.url"
@click="setCaptcha" @click="setCaptcha"

View file

@ -33,11 +33,20 @@ const SideDrawer = {
logo () { logo () {
return this.$store.state.instance.logo return this.$store.state.instance.logo
}, },
hideSitename () {
return this.$store.state.instance.hideSitename
},
sitename () { sitename () {
return this.$store.state.instance.name return this.$store.state.instance.name
}, },
followRequestCount () { followRequestCount () {
return this.$store.state.api.followRequests.length return this.$store.state.api.followRequests.length
},
privateMode () {
return this.$store.state.instance.private
},
federating () {
return this.$store.state.instance.federating
} }
}, },
methods: { methods: {

View file

@ -27,7 +27,7 @@
class="side-drawer-logo-wrapper" class="side-drawer-logo-wrapper"
> >
<img :src="logo"> <img :src="logo">
<span>{{ sitename }}</span> <span v-if="!hideSitename">{{ sitename }}</span>
</div> </div>
</div> </div>
<ul> <ul>
@ -36,7 +36,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'login' }"> <router-link :to="{ name: 'login' }">
{{ $t("login.login") }} <i class="button-icon icon-login" /> {{ $t("login.login") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -44,7 +44,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'dms', params: { username: currentUser.screen_name } }">
{{ $t("nav.dms") }} <i class="button-icon icon-mail-alt" /> {{ $t("nav.dms") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -52,7 +52,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"> <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
{{ $t("nav.interactions") }} <i class="button-icon icon-bell-alt" /> {{ $t("nav.interactions") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
@ -62,7 +62,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'friends' }"> <router-link :to="{ name: 'friends' }">
{{ $t("nav.timeline") }} <i class="button-icon icon-home-2" /> {{ $t("nav.timeline") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -70,7 +70,7 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link to="/friend-requests"> <router-link to="/friend-requests">
{{ $t("nav.friend_requests") }} <i class="button-icon icon-user-plus" /> {{ $t("nav.friend_requests") }}
<span <span
v-if="followRequestCount > 0" v-if="followRequestCount > 0"
class="badge follow-request-count" class="badge follow-request-count"
@ -79,14 +79,20 @@
</span> </span>
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
<router-link to="/main/public"> <router-link to="/main/public">
{{ $t("nav.public_tl") }} <i class="button-icon icon-users" /> {{ $t("nav.public_tl") }}
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li
v-if="federating && !privateMode"
@click="toggleDrawer"
>
<router-link to="/main/all"> <router-link to="/main/all">
{{ $t("nav.twkn") }} <i class="button-icon icon-globe" /> {{ $t("nav.twkn") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -94,14 +100,17 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'chat' }"> <router-link :to="{ name: 'chat' }">
{{ $t("nav.chat") }} <i class="button-icon icon-chat" /> {{ $t("nav.chat") }}
</router-link> </router-link>
</li> </li>
</ul> </ul>
<ul> <ul>
<li @click="toggleDrawer"> <li
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
<router-link :to="{ name: 'search' }"> <router-link :to="{ name: 'search' }">
{{ $t("nav.search") }} <i class="button-icon icon-search" /> {{ $t("nav.search") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -109,17 +118,17 @@
@click="toggleDrawer" @click="toggleDrawer"
> >
<router-link :to="{ name: 'who-to-follow' }"> <router-link :to="{ name: 'who-to-follow' }">
{{ $t("nav.who_to_follow") }} <i class="button-icon icon-user-plus" /> {{ $t("nav.who_to_follow") }}
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'settings' }"> <router-link :to="{ name: 'settings' }">
{{ $t("settings.settings") }} <i class="button-icon icon-cog" /> {{ $t("settings.settings") }}
</router-link> </router-link>
</li> </li>
<li @click="toggleDrawer"> <li @click="toggleDrawer">
<router-link :to="{ name: 'about'}"> <router-link :to="{ name: 'about'}">
{{ $t("nav.about") }} <i class="button-icon icon-info-circled" /> {{ $t("nav.about") }}
</router-link> </router-link>
</li> </li>
<li <li
@ -130,7 +139,7 @@
href="/pleroma/admin/#/login-pleroma" href="/pleroma/admin/#/login-pleroma"
target="_blank" target="_blank"
> >
{{ $t("nav.administration") }} <i class="button-icon icon-gauge" /> {{ $t("nav.administration") }}
</a> </a>
</li> </li>
<li <li
@ -141,7 +150,7 @@
href="#" href="#"
@click="doLogout" @click="doLogout"
> >
{{ $t("login.logout") }} <i class="button-icon icon-logout" /> {{ $t("login.logout") }}
</a> </a>
</li> </li>
</ul> </ul>
@ -215,6 +224,10 @@
box-shadow: var(--panelShadow); box-shadow: var(--panelShadow);
background-color: $fallback--bg; background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg); background-color: var(--bg, $fallback--bg);
.button-icon:before {
width: 1.1em;
}
} }
.side-drawer-logo-wrapper { .side-drawer-logo-wrapper {

View file

@ -36,27 +36,27 @@
.sticker-picker { .sticker-picker {
width: 100%; width: 100%;
position: relative; .contents {
.tab-switcher { min-height: 250px;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.sticker-picker-content { .sticker-picker-content {
display: flex;
flex-wrap: wrap;
padding: 0 4px;
.sticker { .sticker {
display: inline-block; display: flex;
width: 20%; flex: 1 1 auto;
height: 20%; margin: 4px;
width: 56px;
height: 56px;
img { img {
width: 100%; height: 100%;
&:hover { &:hover {
filter: drop-shadow(0 0 5px var(--link, $fallback--link)); filter: drop-shadow(0 0 5px var(--link, $fallback--link));
} }
} }
} }
} }
}
} }
</style> </style>

View file

@ -36,7 +36,12 @@ const Timeline = {
} }
}, },
computed: { computed: {
timelineError () { return this.$store.state.statuses.error }, timelineError () {
return this.$store.state.statuses.error
},
errorData () {
return this.$store.state.statuses.errorData
},
newStatusCount () { newStatusCount () {
return this.timeline.newStatusCount return this.timeline.newStatusCount
}, },

View file

@ -11,15 +11,22 @@
> >
{{ $t('timeline.error_fetching') }} {{ $t('timeline.error_fetching') }}
</div> </div>
<div
v-else-if="errorData"
class="loadmore-error alert error"
@click.prevent
>
{{ errorData.statusText }}
</div>
<button <button
v-if="timeline.newStatusCount > 0 && !timelineError" v-if="timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-button" class="loadmore-button"
@click.prevent="showNewStatuses" @click.prevent="showNewStatuses"
> >
{{ $t('timeline.show_new') }}{{ newStatusCountStr }} {{ $t('timeline.show_new') }}{{ newStatusCountStr }}
</button> </button>
<div <div
v-if="!timeline.newStatusCount > 0 && !timelineError" v-if="!timeline.newStatusCount > 0 && !timelineError && !errorData"
class="loadmore-text faint" class="loadmore-text faint"
@click.prevent @click.prevent
> >
@ -67,12 +74,18 @@
{{ $t('timeline.no_more_statuses') }} {{ $t('timeline.no_more_statuses') }}
</div> </div>
<a <a
v-else-if="!timeline.loading" v-else-if="!timeline.loading && !errorData"
href="#" href="#"
@click.prevent="fetchOlderStatuses()" @click.prevent="fetchOlderStatuses()"
> >
<div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div> <div class="new-status-notification text-center panel-footer">{{ $t('timeline.load_older') }}</div>
</a> </a>
<a
v-else-if="errorData"
href="#"
>
<div class="new-status-notification text-center panel-footer">{{ errorData.error }}</div>
</a>
<div <div
v-else v-else
class="new-status-notification text-center panel-footer" class="new-status-notification text-center panel-footer"
@ -93,17 +106,4 @@
opacity: 1; opacity: 1;
} }
} }
.new-status-notification {
position:relative;
margin-top: -1px;
font-size: 1.1em;
border-width: 1px 0 0 0;
border-style: solid;
border-color: var(--border, $fallback--border);
padding: 10px;
z-index: 1;
background-color: $fallback--fg;
background-color: var(--panel, $fallback--fg);
}
</style> </style>

View file

@ -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 = ({
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}>

View file

@ -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 = ({
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}>

View file

@ -1,4 +1,23 @@
{ {
"about": {
"staff": "スタッフ",
"federation": "フェデレーション",
"mrf_policies": "ゆうこうなMRFポリシー",
"mrf_policies_desc": "MRFポリシーは、このインスタンスのフェデレーションのふるまいを、いじります。これらのMRFポリシーがゆうこうになっています:",
"mrf_policy_simple": "インスタンスのポリシー",
"mrf_policy_simple_accept": "うけいれ",
"mrf_policy_simple_accept_desc": "このインスンスは、これらのインスタンスからのメッセージのみをうけいれます:",
"mrf_policy_simple_reject": "おことわり",
"mrf_policy_simple_reject_desc": "このインスタンスは、これらのインスタンスからのメッセージをうけいれません:",
"mrf_policy_simple_quarantine": "けんえき",
"mrf_policy_simple_quarantine_desc": "このインスタンスは、これらのインスタンスに、パブリックなとうこうのみを、おくります:",
"mrf_policy_simple_ftl_removal": "「つながっているすべてのネットワーク」タイムラインからのぞく",
"mrf_policy_simple_ftl_removal_desc": "このインスタンスは、つながっているすべてのネットワーク」タイムラインから、これらのインスタンスを、とりのぞきます:",
"mrf_policy_simple_media_removal": "メディアをのぞく",
"mrf_policy_simple_media_removal_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、とりのぞきます:",
"mrf_policy_simple_media_nsfw": "メディアをすべてセンシティブにする",
"mrf_policy_simple_media_nsfw_desc": "このインスタンスは、これらのインスタンスからおくられてきたメディアを、すべて、センシティブにマークします:"
},
"chat": { "chat": {
"title": "チャット" "title": "チャット"
}, },
@ -68,6 +87,7 @@
}, },
"nav": { "nav": {
"about": "これはなに?", "about": "これはなに?",
"administration": "アドミニストレーション",
"back": "もどる", "back": "もどる",
"chat": "ローカルチャット", "chat": "ローカルチャット",
"friend_requests": "フォローリクエスト", "friend_requests": "フォローリクエスト",
@ -113,7 +133,9 @@
"search_emoji": "えもじをさがす", "search_emoji": "えもじをさがす",
"add_emoji": "えもじをうちこむ", "add_emoji": "えもじをうちこむ",
"custom": "カスタムえもじ", "custom": "カスタムえもじ",
"unicode": "ユニコードえもじ" "unicode": "ユニコードえもじ",
"load_all_hint": "はじめの {saneAmount} このえもじだけがロードされています。すべてのえもじをロードすると、パフォーマンスがわるくなるかもしれません。",
"load_all": "すべてのえもじをロード ({emojiAmount} こあります)"
}, },
"stickers": { "stickers": {
"add_sticker": "ステッカーをふやす" "add_sticker": "ステッカーをふやす"
@ -173,6 +195,11 @@
"password_confirmation_match": "パスワードがちがいます" "password_confirmation_match": "パスワードがちがいます"
} }
}, },
"remote_user_resolver": {
"remote_user_resolver": "リモートユーザーリゾルバー",
"searching_for": "さがしています:",
"error": "みつかりませんでした。"
},
"selectable_list": { "selectable_list": {
"select_all": "すべてえらぶ" "select_all": "すべてえらぶ"
}, },
@ -220,6 +247,9 @@
"cGreen": "リピート", "cGreen": "リピート",
"cOrange": "おきにいり", "cOrange": "おきにいり",
"cRed": "キャンセル", "cRed": "キャンセル",
"change_email": "メールアドレスをかえる",
"change_email_error": "メールアドレスをかえようとしましたが、なにかがおかしいです。",
"changed_email": "メールアドレスをかえることができました!",
"change_password": "パスワードをかえる", "change_password": "パスワードをかえる",
"change_password_error": "パスワードをかえることが、できなかったかもしれません。", "change_password_error": "パスワードをかえることが、できなかったかもしれません。",
"changed_password": "パスワードが、かわりました!", "changed_password": "パスワードが、かわりました!",
@ -279,6 +309,7 @@
"use_contain_fit": "がぞうのサムネイルを、きりぬかない", "use_contain_fit": "がぞうのサムネイルを、きりぬかない",
"name": "なまえ", "name": "なまえ",
"name_bio": "なまえとプロフィール", "name_bio": "なまえとプロフィール",
"new_email": "あたらしいメールアドレス",
"new_password": "あたらしいパスワード", "new_password": "あたらしいパスワード",
"notification_visibility": "ひょうじするつうち", "notification_visibility": "ひょうじするつうち",
"notification_visibility_follows": "フォロー", "notification_visibility_follows": "フォロー",
@ -344,6 +375,8 @@
"false": "いいえ", "false": "いいえ",
"true": "はい" "true": "はい"
}, },
"fun": "おたのしみ",
"greentext": "ミームやじるし",
"notifications": "つうち", "notifications": "つうち",
"notification_setting": "つうちをうけとる:", "notification_setting": "つうちをうけとる:",
"notification_setting_follows": "あなたがフォローしているひとから", "notification_setting_follows": "あなたがフォローしているひとから",
@ -391,6 +424,7 @@
"_tab_label": "くわしく", "_tab_label": "くわしく",
"alert": "アラートのバックグラウンド", "alert": "アラートのバックグラウンド",
"alert_error": "エラー", "alert_error": "エラー",
"alert_warning": "けいこく",
"badge": "バッジのバックグラウンド", "badge": "バッジのバックグラウンド",
"badge_notification": "つうち", "badge_notification": "つうち",
"panel_header": "パネルヘッダー", "panel_header": "パネルヘッダー",
@ -542,6 +576,7 @@
"followers": "フォロワー", "followers": "フォロワー",
"following": "フォローしています!", "following": "フォローしています!",
"follows_you": "フォローされました!", "follows_you": "フォローされました!",
"hidden": "かくされています",
"its_you": "これはあなたです!", "its_you": "これはあなたです!",
"media": "メディア", "media": "メディア",
"mention": "メンション", "mention": "メンション",
@ -559,6 +594,8 @@
"unmute": "ミュートをやめる", "unmute": "ミュートをやめる",
"unmute_progress": "ミュートをとりけしています...", "unmute_progress": "ミュートをとりけしています...",
"mute_progress": "ミュートしています...", "mute_progress": "ミュートしています...",
"hide_repeats": "リピートをかくす",
"show_repeats": "リピートをみる",
"admin_menu": { "admin_menu": {
"moderation": "モデレーション", "moderation": "モデレーション",
"grant_admin": "アドミンにする", "grant_admin": "アドミンにする",
@ -634,6 +671,8 @@
"return_home": "ホームページにもどる", "return_home": "ホームページにもどる",
"not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。", "not_found": "そのメールアドレスまたはユーザーめいを、みつけることができませんでした。",
"too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。", "too_many_requests": "パスワードリセットを、ためすことが、おおすぎます。しばらくしてから、ためしてください。",
"password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。" "password_reset_disabled": "このインスタンスでは、パスワードリセットは、できません。インスタンスのアドミニストレーターに、おといあわせください。",
"password_reset_required": "ログインするには、パスワードをリセットしてください。",
"password_reset_required_but_mailer_is_disabled": "あなたはパスワードのリセットがひつようです。しかし、まずいことに、このインスタンスでは、パスワードのリセットができなくなっています。このインスタンスのアドミニストレーターに、おといあわせください。"
} }
} }

View file

@ -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 = {
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 = {
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) {

View file

@ -27,6 +27,7 @@ const defaultState = {
scopeCopy: true, scopeCopy: true,
subjectLineBehavior: 'email', subjectLineBehavior: 'email',
postContentType: 'text/plain', postContentType: 'text/plain',
hideSitename: false,
nsfwCensorImage: undefined, nsfwCensorImage: undefined,
vapidPublicKey: undefined, vapidPublicKey: undefined,
noAttachmentLinks: false, noAttachmentLinks: false,

View file

@ -38,6 +38,7 @@ export const defaultState = () => ({
notifications: emptyNotifications(), notifications: emptyNotifications(),
favorites: new Set(), favorites: new Set(),
error: false, error: false,
errorData: null,
timelines: { timelines: {
mentions: emptyTl(), mentions: emptyTl(),
public: emptyTl(), public: emptyTl(),
@ -479,6 +480,9 @@ export const mutations = {
setError (state, { value }) { setError (state, { value }) {
state.error = value state.error = value
}, },
setErrorData (state, { value }) {
state.errorData = value
},
setNotificationsLoading (state, { value }) { setNotificationsLoading (state, { value }) {
state.notifications.loading = value state.notifications.loading = value
}, },
@ -528,6 +532,9 @@ const statuses = {
setError ({ rootState, commit }, { value }) { setError ({ rootState, commit }, { value }) {
commit('setError', { value }) commit('setError', { value })
}, },
setErrorData ({ rootState, commit }, { value }) {
commit('setErrorData', { value })
},
setNotificationsLoading ({ rootState, commit }, { value }) { setNotificationsLoading ({ rootState, commit }, { value }) {
commit('setNotificationsLoading', { value }) commit('setNotificationsLoading', { value })
}, },

View file

@ -95,9 +95,9 @@ export const mutations = {
newRights[right] = value newRights[right] = value
set(user, 'rights', newRights) set(user, 'rights', newRights)
}, },
updateActivationStatus (state, { user: { id }, status }) { updateActivationStatus (state, { user: { id }, deactivated }) {
const user = state.usersObject[id] const user = state.usersObject[id]
set(user, 'deactivated', !status) set(user, 'deactivated', deactivated)
}, },
setCurrentUser (state, user) { setCurrentUser (state, user) {
state.lastLoginName = user.screen_name state.lastLoginName = user.screen_name
@ -331,6 +331,11 @@ const users = {
return rootState.api.backendInteractor.unsubscribeUser({ id }) return rootState.api.backendInteractor.unsubscribeUser({ id })
.then((relationship) => commit('updateUserRelationship', [relationship])) .then((relationship) => commit('updateUserRelationship', [relationship]))
}, },
toggleActivationStatus ({ rootState, commit }, user) {
const api = user.deactivated ? rootState.api.backendInteractor.activateUser : rootState.api.backendInteractor.deactivateUser
api(user)
.then(({ deactivated }) => commit('updateActivationStatus', { user, deactivated }))
},
registerPushNotifications (store) { registerPushNotifications (store) {
const token = store.state.currentUser.credentials const token = store.state.currentUser.credentials
const vapidPublicKey = store.rootState.instance.vapidPublicKey const vapidPublicKey = store.rootState.instance.vapidPublicKey

View file

@ -1,4 +1,4 @@
import { each, map, concat, last } from 'lodash' import { each, map, concat, last, get } from 'lodash'
import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js' import { parseStatus, parseUser, parseNotification, parseAttachment } from '../entity_normalizer/entity_normalizer.service.js'
import 'whatwg-fetch' import 'whatwg-fetch'
import { RegistrationError, StatusCodeError } from '../errors/errors' import { RegistrationError, StatusCodeError } from '../errors/errors'
@ -12,7 +12,8 @@ const CHANGE_EMAIL_URL = '/api/pleroma/change_email'
const CHANGE_PASSWORD_URL = '/api/pleroma/change_password' const CHANGE_PASSWORD_URL = '/api/pleroma/change_password'
const TAG_USER_URL = '/api/pleroma/admin/users/tag' const TAG_USER_URL = '/api/pleroma/admin/users/tag'
const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}` const PERMISSION_GROUP_URL = (screenName, right) => `/api/pleroma/admin/users/${screenName}/permission_group/${right}`
const ACTIVATION_STATUS_URL = screenName => `/api/pleroma/admin/users/${screenName}/activation_status` const ACTIVATE_USER_URL = '/api/pleroma/admin/users/activate'
const DEACTIVATE_USER_URL = '/api/pleroma/admin/users/deactivate'
const ADMIN_USERS_URL = '/api/pleroma/admin/users' const ADMIN_USERS_URL = '/api/pleroma/admin/users'
const SUGGESTIONS_URL = '/api/v1/suggestions' const SUGGESTIONS_URL = '/api/v1/suggestions'
const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings' const NOTIFICATION_SETTINGS_URL = '/api/pleroma/notification_settings'
@ -22,7 +23,7 @@ const MFA_BACKUP_CODES_URL = '/api/pleroma/accounts/mfa/backup_codes'
const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp' const MFA_SETUP_OTP_URL = '/api/pleroma/accounts/mfa/setup/totp'
const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp' const MFA_CONFIRM_OTP_URL = '/api/pleroma/accounts/mfa/confirm/totp'
const MFA_DISABLE_OTP_URL = '/api/pleroma/account/mfa/totp' const MFA_DISABLE_OTP_URL = '/api/pleroma/accounts/mfa/totp'
const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials' const MASTODON_LOGIN_URL = '/api/v1/accounts/verify_credentials'
const MASTODON_REGISTRATION_URL = '/api/v1/accounts' const MASTODON_REGISTRATION_URL = '/api/v1/accounts'
@ -451,20 +452,26 @@ const deleteRight = ({ right, credentials, ...user }) => {
}) })
} }
const setActivationStatus = ({ status, credentials, ...user }) => { const activateUser = ({ credentials, user: { screen_name: nickname } }) => {
const screenName = user.screen_name return promisedRequest({
const body = { url: ACTIVATE_USER_URL,
status: status method: 'PATCH',
credentials,
payload: {
nicknames: [nickname]
} }
}).then(response => get(response, 'users.0'))
}
const headers = authHeaders(credentials) const deactivateUser = ({ credentials, user: { screen_name: nickname } }) => {
headers['Content-Type'] = 'application/json' return promisedRequest({
url: DEACTIVATE_USER_URL,
return fetch(ACTIVATION_STATUS_URL(screenName), { method: 'PATCH',
method: 'PUT', credentials,
headers: headers, payload: {
body: JSON.stringify(body) nicknames: [nickname]
}) }
}).then(response => get(response, 'users.0'))
} }
const deleteUser = ({ credentials, ...user }) => { const deleteUser = ({ credentials, ...user }) => {
@ -530,16 +537,24 @@ const fetchTimeline = ({
const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&') const queryString = map(params, (param) => `${param[0]}=${param[1]}`).join('&')
url += `?${queryString}` url += `?${queryString}`
let status = ''
let statusText = ''
return fetch(url, { headers: authHeaders(credentials) }) return fetch(url, { headers: authHeaders(credentials) })
.then((data) => { .then((data) => {
if (data.ok) { status = data.status
statusText = data.statusText
return data return data
}
throw new Error('Error fetching timeline', data)
}) })
.then((data) => data.json()) .then((data) => data.json())
.then((data) => data.map(isNotifications ? parseNotification : parseStatus)) .then((data) => {
if (!data.error) {
return data.map(isNotifications ? parseNotification : parseStatus)
} else {
data.status = status
data.statusText = statusText
return data
}
})
} }
const fetchPinnedStatuses = ({ id, credentials }) => { const fetchPinnedStatuses = ({ id, credentials }) => {
@ -1065,7 +1080,8 @@ const apiService = {
deleteUser, deleteUser,
addRight, addRight,
deleteRight, deleteRight,
setActivationStatus, activateUser,
deactivateUser,
register, register,
getCaptcha, getCaptcha,
updateAvatar, updateAvatar,

View file

@ -1,9 +1,9 @@
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 }) => {
}).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')

View file

@ -12,7 +12,7 @@ export const getOrCreateApp = ({ clientId, clientSecret, instance, commit }) =>
form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`) form.append('client_name', `PleromaFE_${window.___pleromafe_commit_hash}_${(new Date()).toISOString()}`)
form.append('redirect_uris', REDIRECT_URI) form.append('redirect_uris', REDIRECT_URI)
form.append('scopes', 'read write follow') form.append('scopes', 'read write follow push admin')
return window.fetch(url, { return window.fetch(url, {
method: 'POST', method: 'POST',
@ -28,7 +28,7 @@ const login = ({ instance, clientId }) => {
response_type: 'code', response_type: 'code',
client_id: clientId, client_id: clientId,
redirect_uri: REDIRECT_URI, redirect_uri: REDIRECT_URI,
scope: 'read write follow' scope: 'read write follow push admin'
} }
const dataString = reduce(data, (acc, v, k) => { const dataString = reduce(data, (acc, v, k) => {

View file

@ -6,6 +6,7 @@ const update = ({ store, statuses, timeline, showImmediately, userId }) => {
const ccTimeline = camelCase(timeline) const ccTimeline = camelCase(timeline)
store.dispatch('setError', { value: false }) store.dispatch('setError', { value: false })
store.dispatch('setErrorData', { value: null })
store.dispatch('addNewStatuses', { store.dispatch('addNewStatuses', {
timeline: ccTimeline, timeline: ccTimeline,
@ -45,6 +46,10 @@ const fetchAndUpdate = ({
return apiService.fetchTimeline(args) return apiService.fetchTimeline(args)
.then((statuses) => { .then((statuses) => {
if (statuses.error) {
store.dispatch('setErrorData', { value: statuses })
return
}
if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) { if (!older && statuses.length >= 20 && !timelineData.loading && numStatusesBeforeFetch > 0) {
store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId }) store.dispatch('queueFlush', { timeline: timeline, id: timelineData.maxId })
} }

View file

@ -303,6 +303,36 @@
"css": "gauge", "css": "gauge",
"code": 61668, "code": 61668,
"src": "fontawesome" "src": "fontawesome"
},
{
"uid": "31972e4e9d080eaa796290349ae6c1fd",
"css": "users",
"code": 59421,
"src": "fontawesome"
},
{
"uid": "e82cedfa1d5f15b00c5a81c9bd731ea2",
"css": "info-circled",
"code": 59423,
"src": "fontawesome"
},
{
"uid": "w3nzesrlbezu6f30q7ytyq919p6gdlb6",
"css": "home-2",
"code": 59425,
"src": "typicons"
},
{
"uid": "dcedf50ab1ede3283d7a6c70e2fe32f3",
"css": "chat",
"code": 59422,
"src": "fontawesome"
},
{
"uid": "3a00327e61b997b58518bd43ed83c3df",
"css": "login",
"code": 59424,
"src": "fontawesome"
} }
] ]
} }

View file

@ -1,4 +1,4 @@
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

1437
yarn.lock

File diff suppressed because it is too large Load diff