Automatically rewrite to element-plus

This commit is contained in:
AkiraFukushima 2022-04-18 22:41:52 +09:00
parent 2751da04b7
commit 0c427e8c34
No known key found for this signature in database
GPG Key ID: B6E51BAC4DE1A957
88 changed files with 5648 additions and 2297 deletions

View File

@ -4,7 +4,13 @@
<el-header>
<el-row>
<el-col :span="24" class="close">
<el-button type="text" icon="el-icon-close" @click="close" class="close-button"> </el-button>
<el-button
type="text"
:icon="ElIconClose"
@click="close"
class="close-button"
>
</el-button>
</el-col>
</el-row>
</el-header>
@ -31,7 +37,12 @@
<el-input></el-input>
</el-form-item>
<el-form-item class="submit">
<el-button type="primary" @click="authorizeSubmit" v-loading="submitting" element-loading-background="rgba(0, 0, 0, 0.8)">
<el-button
type="primary"
@click="authorizeSubmit"
v-loading="submitting"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
{{ $t('authorize.submit') }}
</el-button>
</el-form-item>
@ -42,25 +53,27 @@
</template>
<script>
import { Close as ElIconClose } from '@element-plus/icons'
export default {
data() {
return {
authorizeForm: {
code: null,
},
submitting: false,
ElIconClose,
}
},
name: 'authorize',
props: {
url: {
type: String,
default: ''
default: '',
},
sns: {
type: String,
default: 'mastodon'
}
},
data() {
return {
authorizeForm: {
code: null
},
submitting: false
}
default: 'mastodon',
},
},
mounted() {
console.log(this.url)
@ -71,32 +84,32 @@ export default {
this.$store
.dispatch('Authorize/submit', {
code: this.authorizeForm.code,
sns: this.sns
sns: this.sns,
})
.finally(() => {
this.submitting = false
})
.then(id => {
.then((id) => {
this.$router.push({ path: `/${id}/home` })
})
.catch(err => {
.catch((err) => {
if (err.name === 'DuplicateRecordError') {
this.$message({
message: this.$t('message.authorize_duplicate_error'),
type: 'error'
type: 'error',
})
} else {
this.$message({
message: this.$t('message.authorize_error'),
type: 'error'
type: 'error',
})
}
})
},
close() {
return this.$router.push({ path: '/', query: { redirect: 'home' } })
}
}
},
},
}
</script>

View File

@ -18,13 +18,26 @@
v-bind:key="account._id"
role="menuitem"
>
<i v-if="account.avatar === undefined || account.avatar === null || account.avatar === ''" class="el-icon-menu"></i>
<FailoverImg v-else :src="account.avatar" class="avatar" :title="account.username + '@' + account.domain" />
<FailoverImg :src="`${account.baseURL}/favicon.ico`" :failoverSrc="`${account.baseURL}/favicon.png`" class="instance-icon" />
<el-icon><el-icon-menu /></el-icon>
<FailoverImg
v-else
:src="account.avatar"
class="avatar"
:title="account.username + '@' + account.domain"
/>
<FailoverImg
:src="`${account.baseURL}/favicon.ico`"
:failoverSrc="`${account.baseURL}/favicon.png`"
class="instance-icon"
/>
<span slot="title">{{ account.domain }}</span>
</el-menu-item>
<el-menu-item index="/login" :title="$t('global_header.add_new_account')" role="menuitem">
<i class="el-icon-plus"></i>
<el-menu-item
index="/login"
:title="$t('global_header.add_new_account')"
role="menuitem"
>
<el-icon><el-icon-plus /></el-icon>
<span slot="new">New</span>
</el-menu-item>
</el-menu>
@ -35,23 +48,26 @@
</template>
<script>
import { Menu as ElIconMenu, Plus as ElIconPlus } from '@element-plus/icons'
import { mapState } from 'vuex'
import FailoverImg from '~/src/renderer/components/atoms/FailoverImg'
import { StreamingError } from '~/src/errors/streamingError'
export default {
name: 'global-header',
components: {
FailoverImg
FailoverImg,
ElIconMenu,
ElIconPlus,
},
name: 'global-header',
computed: {
...mapState('GlobalHeader', {
accounts: state => state.accounts,
hide: state => state.hide
accounts: (state) => state.accounts,
hide: (state) => state.hide,
}),
...mapState({
themeColor: state => state.App.theme.global_header_color
})
themeColor: (state) => state.App.theme.global_header_color,
}),
},
created() {
this.initialize()
@ -63,12 +79,14 @@ export default {
async initialize() {
await this.$store
.dispatch('GlobalHeader/initLoad')
.then(accounts => {
this.$store.dispatch('GlobalHeader/startStreamings').catch(err => {
.then((accounts) => {
this.$store.dispatch('GlobalHeader/startStreamings').catch((err) => {
if (err instanceof StreamingError) {
this.$message({
message: this.$t('message.start_all_streamings_error', { domain: err.domain }),
type: 'error'
message: this.$t('message.start_all_streamings_error', {
domain: err.domain,
}),
type: 'error',
})
}
})
@ -76,11 +94,11 @@ export default {
this.$router.push({ path: `/${accounts[0]._id}/home` })
}
})
.catch(_ => {
.catch((_) => {
return this.$router.push({ path: '/login' })
})
}
}
},
},
}
</script>

View File

@ -3,7 +3,13 @@
<el-header>
<el-row>
<el-col :span="24" class="close">
<el-button type="text" icon="el-icon-close" @click="close" class="close-button"> </el-button>
<el-button
type="text"
:icon="ElIconClose"
@click="close"
class="close-button"
>
</el-button>
</el-col>
</el-row>
</el-header>
@ -15,23 +21,29 @@
</template>
<script>
import { Close as ElIconClose } from '@element-plus/icons'
import LoginForm from './Login/LoginForm'
import { mapState } from 'vuex'
export default {
data() {
return {
ElIconClose,
}
},
name: 'login',
components: { LoginForm },
computed: {
...mapState({
page: state => state.Login.page
})
page: (state) => state.Login.page,
}),
},
methods: {
close() {
this.$store.dispatch('Login/pageBack')
return this.$router.push({ path: '/', query: { redirect: 'home' } })
}
}
},
},
}
</script>

View File

@ -9,10 +9,18 @@
:model="form"
>
<el-form-item :label="$t('login.domain_name_label')" prop="domainName">
<el-input v-model="form.domainName" placeholder="mastodon.social" v-shortkey="['enter']" @shortkey.native="handleKey"></el-input>
<el-input
v-model="form.domainName"
placeholder="mastodon.social"
v-shortkey="['enter']"
@shortkey.native="handleKey"
></el-input>
</el-form-item>
<p class="proxy-info">
{{ $t('login.proxy_info') }}<router-link to="/preferences/network">{{ $t('login.proxy_here') }}</router-link>
{{ $t('login.proxy_info')
}}<router-link to="/preferences/network">{{
$t('login.proxy_here')
}}</router-link>
</p>
<!-- Dummy form to guard submitting with enter -->
<el-form-item class="hidden">
@ -22,7 +30,13 @@
<el-button type="primary" class="login" @click="login" v-if="allowLogin">
{{ $t('login.login') }}
</el-button>
<el-button type="primary" v-else @click="confirm('loginForm')" v-loading="searching" element-loading-background="rgba(0, 0, 0, 0.8)">
<el-button
type="primary"
v-else
@click="confirm('loginForm')"
v-loading="searching"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
{{ $t('login.search') }}
</el-button>
</el-form-item>
@ -38,18 +52,20 @@ export default {
data() {
return {
form: {
domainName: ''
}
domainName: '',
},
}
},
computed: {
...mapState({
selectedInstance: state => state.Login.selectedInstance,
searching: state => state.Login.searching,
sns: state => state.Login.sns
selectedInstance: (state) => state.Login.selectedInstance,
searching: (state) => state.Login.searching,
sns: (state) => state.Login.sns,
}),
allowLogin: function() {
return this.selectedInstance && this.form.domainName === this.selectedInstance
allowLogin: function () {
return (
this.selectedInstance && this.form.domainName === this.selectedInstance
)
},
rules: {
get() {
@ -58,17 +74,17 @@ export default {
{
type: 'string',
required: true,
message: this.$t('validation.login.require_domain_name')
message: this.$t('validation.login.require_domain_name'),
},
{
pattern: domainFormat,
trigger: 'change',
message: this.$t('validation.login.domain_format')
}
]
message: this.$t('validation.login.domain_format'),
},
],
}
}
}
},
},
},
methods: {
login() {
@ -76,44 +92,51 @@ export default {
lock: true,
text: this.$t('message.loading'),
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
background: 'rgba(0, 0, 0, 0.7)',
})
this.$store
.dispatch('Login/fetchLogin')
.then(url => {
.then((url) => {
loading.close()
this.$store.dispatch('Login/pageBack')
this.$router.push({ path: '/authorize', query: { url: url, sns: this.sns } })
this.$router.push({
path: '/authorize',
query: { url: url, sns: this.sns },
})
})
.catch(() => {
loading.close()
this.$message({
message: this.$t('message.authorize_url_error'),
type: 'error'
type: 'error',
})
})
},
confirm(formName) {
this.$refs[formName].validate(valid => {
this.$refs[formName].validate((valid) => {
if (valid) {
this.$store
.dispatch('Login/confirmInstance', this.form.domainName)
.then(() => {
this.$message({
message: this.$t('message.domain_confirmed', { domain: this.form.domainName }),
type: 'success'
message: this.$t('message.domain_confirmed', {
domain: this.form.domainName,
}),
type: 'success',
})
})
.catch(() => {
this.$message({
message: this.$t('message.domain_doesnt_exist', { domain: this.form.domainName }),
type: 'error'
message: this.$t('message.domain_doesnt_exist', {
domain: this.form.domainName,
}),
type: 'error',
})
})
} else {
this.$message({
message: this.$t('validation.login.domain_format'),
type: 'error'
type: 'error',
})
return false
}
@ -125,8 +148,8 @@ export default {
} else {
this.login()
}
}
}
},
},
}
</script>

View File

@ -6,7 +6,13 @@
<h1>{{ $t('preferences.title') }}</h1>
</el-col>
<el-col :span="1">
<el-button type="text" icon="el-icon-close" @click="close" class="close-button" role="button"></el-button>
<el-button
type="text"
:icon="ElIconClose"
@click="close"
class="close-button"
role="button"
></el-button>
</el-col>
</el-row>
</el-header>
@ -54,15 +60,21 @@
</template>
<script>
import { Close as ElIconClose } from '@element-plus/icons'
import { mapState } from 'vuex'
export default {
data() {
return {
ElIconClose,
}
},
name: 'preferences',
computed: {
...mapState({
primaryColor: state => state.App.theme.primary_color,
backgroundColor: state => state.App.theme.background_color
})
primaryColor: (state) => state.App.theme.primary_color,
backgroundColor: (state) => state.App.theme.background_color,
}),
},
methods: {
close() {
@ -70,8 +82,8 @@ export default {
},
activeRoute() {
return this.$route.path
}
}
},
},
}
</script>

View File

@ -12,12 +12,25 @@
v-loading="accountLoading"
:element-loading-background="backgroundColor"
>
<el-table-column prop="username" :label="$t('preferences.account.username')"> </el-table-column>
<el-table-column prop="domain" :label="$t('preferences.account.domain')"> </el-table-column>
<el-table-column
prop="username"
:label="$t('preferences.account.username')"
>
</el-table-column>
<el-table-column
prop="domain"
:label="$t('preferences.account.domain')"
>
</el-table-column>
<el-table-column :label="$t('preferences.account.association')">
<template slot-scope="scope">
<el-button @click.native.prevent="removeAccount(scope.$index, accounts)" type="text" class="action">
<i class="el-icon-close"></i> {{ $t('preferences.account.remove_association') }}
<el-button
@click.native.prevent="removeAccount(scope.$index, accounts)"
type="text"
class="action"
>
<el-icon><el-icon-close /></el-icon>
{{ $t('preferences.account.remove_association') }}
</el-button>
</template>
</el-table-column>
@ -27,7 +40,7 @@
<el-button
class="arrow-up action"
type="text"
icon="el-icon-arrow-up"
:icon="ElIconArrowUp"
@click.native.prevent="forward(scope.$index, accounts)"
></el-button>
</div>
@ -35,7 +48,7 @@
<el-button
class="arrow-down action"
type="text"
icon="el-icon-arrow-down"
:icon="ElIconArrowDown"
@click.native.prevent="backward(scope.$index, accounts)"
></el-button>
</div>
@ -47,10 +60,22 @@
<el-popover placement="top" width="160" v-model="deletePopoverVisible">
<p>{{ $t('preferences.account.confirm_message') }}</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="deletePopoverVisible = false">{{ $t('preferences.account.cancel') }}</el-button>
<el-button type="danger" size="mini" @click="removeAllAssociations">{{ $t('preferences.account.confirm') }}</el-button>
<el-button
size="mini"
type="text"
@click="deletePopoverVisible = false"
>{{ $t('preferences.account.cancel') }}</el-button
>
<el-button
type="danger"
size="mini"
@click="removeAllAssociations"
>{{ $t('preferences.account.confirm') }}</el-button
>
</div>
<el-button slot="reference" type="danger">{{ $t('preferences.account.remove_all_associations') }}</el-button>
<el-button slot="reference" type="danger">{{
$t('preferences.account.remove_all_associations')
}}</el-button>
</el-popover>
</el-form-item>
</el-form>
@ -58,24 +83,34 @@
</template>
<script>
import {
Close as ElIconClose,
ArrowUp as ElIconArrowUp,
ArrowDown as ElIconArrowDown,
} from '@element-plus/icons'
import { mapState } from 'vuex'
export default {
name: 'account',
data() {
return {
openRemoveDialog: false,
deletePopoverVisible: false
deletePopoverVisible: false,
ElIconArrowUp,
ElIconArrowDown,
}
},
components: {
ElIconClose,
},
name: 'account',
computed: {
...mapState({
accounts: state => state.Preferences.Account.accounts,
accountLoading: state => state.Preferences.Account.accountLoading
accounts: (state) => state.Preferences.Account.accounts,
accountLoading: (state) => state.Preferences.Account.accountLoading,
}),
...mapState({
backgroundColor: state => state.App.theme.background_color
})
backgroundColor: (state) => state.App.theme.background_color,
}),
},
created() {
this.loadAccounts()
@ -88,7 +123,7 @@ export default {
} catch (err) {
return this.$message({
message: this.$t('message.account_load_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('Preferences/Account/updateAccountLoading', false)
@ -103,27 +138,31 @@ export default {
.catch(() => {
this.$message({
message: this.$t('message.account_remove_error'),
type: 'error'
type: 'error',
})
})
},
forward(index, accounts) {
this.$store.dispatch('Preferences/Account/forwardAccount', accounts[index]).then(() => {
this.loadAccounts()
})
this.$store
.dispatch('Preferences/Account/forwardAccount', accounts[index])
.then(() => {
this.loadAccounts()
})
},
backward(index, accounts) {
this.$store.dispatch('Preferences/Account/backwardAccount', accounts[index]).then(() => {
this.loadAccounts()
})
this.$store
.dispatch('Preferences/Account/backwardAccount', accounts[index])
.then(() => {
this.loadAccounts()
})
},
removeAllAssociations() {
this.deletePopoverVisible = false
this.$store.dispatch('Preferences/Account/removeAllAccounts').then(() => {
this.$router.push('/login')
})
}
}
},
},
}
</script>

View File

@ -3,45 +3,100 @@
<h2>{{ $t('preferences.appearance.title') }}</h2>
<el-form class="theme section" size="small" label-position="top">
<div class="left">
<el-form-item for="theme" :label="$t('preferences.appearance.theme_color')">
<el-form-item
for="theme"
:label="$t('preferences.appearance.theme_color')"
>
<el-select id="theme" v-model="theme" placeholder="theme">
<el-option v-for="t in themes" :key="t.key" :label="$t(t.name)" :value="t.key"> </el-option>
<el-option
v-for="t in themes"
:key="t.key"
:label="$t(t.name)"
:value="t.key"
>
</el-option>
</el-select>
</el-form-item>
</div>
<div class="right">
<Toot :displayNameStyle="displayNameStyle" :timeFormat="timeFormat"></Toot>
<Toot
:displayNameStyle="displayNameStyle"
:timeFormat="timeFormat"
></Toot>
</div>
</el-form>
<div class="color-pallet section" v-if="customizeThemeColor">
<color-pallet></color-pallet>
</div>
<el-form class="font section" size="small" label-position="top">
<el-form-item for="font-family" :label="$t('preferences.appearance.font_family')">
<el-form-item
for="font-family"
:label="$t('preferences.appearance.font_family')"
>
<el-select id="font-family" v-model="font" placeholder="fonts">
<el-option v-for="f in fontList" :key="f" :label="f" :value="f" />
</el-select>
</el-form-item>
<el-form-item for="font-size" :label="$t('preferences.appearance.font_size')">
<el-input-number id="font-size" :value="fontSize" :min="9" :max="72" @change="updateFontSize"></el-input-number>
<el-form-item
for="font-size"
:label="$t('preferences.appearance.font_size')"
>
<el-input-number
id="font-size"
:model-value="fontSize"
:min="9"
:max="72"
@change="updateFontSize"
></el-input-number>
</el-form-item>
</el-form>
<el-form class="toot-padding section" size="small" label-position="top">
<el-form-item for="toot-padding" :label="$t('preferences.appearance.toot_padding')">
<el-input-number id="toot-padding" :value="tootPadding" :min="0" :max="24" @change="updateTootPadding"></el-input-number>
<el-form-item
for="toot-padding"
:label="$t('preferences.appearance.toot_padding')"
>
<el-input-number
id="toot-padding"
:model-value="tootPadding"
:min="0"
:max="24"
@change="updateTootPadding"
></el-input-number>
</el-form-item>
</el-form>
<el-form class="display-style section" size="small" label-position="top">
<el-form-item for="display-style" :label="$t('preferences.appearance.display_style.title')">
<el-select id="display-style" v-model="displayNameStyle" placeholder="style">
<el-option v-for="style in nameStyles" :key="style.value" :label="$t(style.name)" :value="style.value"> </el-option>
<el-form-item
for="display-style"
:label="$t('preferences.appearance.display_style.title')"
>
<el-select
id="display-style"
v-model="displayNameStyle"
placeholder="style"
>
<el-option
v-for="style in nameStyles"
:key="style.value"
:label="$t(style.name)"
:value="style.value"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
<el-form class="time-format section" size="small" label-position="top">
<el-form-item for="time-format" :label="$t('preferences.appearance.time_format.title')">
<el-form-item
for="time-format"
:label="$t('preferences.appearance.time_format.title')"
>
<el-select id="time-format" v-model="timeFormat" placeholder="format">
<el-option v-for="format in timeFormats" :key="format.value" :label="$t(format.name)" :value="format.value"> </el-option>
<el-option
v-for="format in timeFormats"
:key="format.value"
:label="$t(format.name)"
:value="format.value"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
@ -60,20 +115,32 @@ export default {
name: 'appearance',
components: {
Toot,
ColorPallet
ColorPallet,
},
data() {
return {
nameStyles: [DisplayStyle.DisplayNameAndUsername, DisplayStyle.DisplayName, DisplayStyle.Username],
themes: [Theme.System, Theme.Light, Theme.Dark, Theme.SolarizedLight, Theme.SolarizedDark, Theme.KimbieDark, Theme.Custom],
timeFormats: [TimeFormat.Absolute, TimeFormat.Relative]
nameStyles: [
DisplayStyle.DisplayNameAndUsername,
DisplayStyle.DisplayName,
DisplayStyle.Username,
],
themes: [
Theme.System,
Theme.Light,
Theme.Dark,
Theme.SolarizedLight,
Theme.SolarizedDark,
Theme.KimbieDark,
Theme.Custom,
],
timeFormats: [TimeFormat.Absolute, TimeFormat.Relative],
}
},
computed: {
...mapState('Preferences/Appearance', {
fontSize: state => state.appearance.fontSize,
fontList: state => state.fonts,
tootPadding: state => state.appearance.tootPadding
fontSize: (state) => state.appearance.fontSize,
fontList: (state) => state.fonts,
tootPadding: (state) => state.appearance.tootPadding,
}),
theme: {
get() {
@ -81,15 +148,19 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Appearance/updateTheme', value)
}
},
},
displayNameStyle: {
get() {
return this.$store.state.Preferences.Appearance.appearance.displayNameStyle
return this.$store.state.Preferences.Appearance.appearance
.displayNameStyle
},
set(value) {
this.$store.dispatch('Preferences/Appearance/updateDisplayNameStyle', value)
}
this.$store.dispatch(
'Preferences/Appearance/updateDisplayNameStyle',
value
)
},
},
timeFormat: {
get() {
@ -97,7 +168,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Appearance/updateTimeFormat', value)
}
},
},
customizeThemeColor() {
return this.theme === Theme.Custom.key
@ -108,8 +179,8 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Appearance/updateFont', value)
}
}
},
},
},
created() {
this.$store.dispatch('Preferences/Appearance/loadAppearance')
@ -120,9 +191,12 @@ export default {
await this.$store.dispatch('Preferences/Appearance/updateFontSize', value)
},
async updateTootPadding(value) {
await this.$store.dispatch('Preferences/Appearance/updateTootPadding', value)
}
}
await this.$store.dispatch(
'Preferences/Appearance/updateTootPadding',
value
)
},
},
}
</script>

View File

@ -1,46 +1,71 @@
<template>
<el-form class="pallet" label-position="top" size="small">
<div class="item">
<el-form-item :label="$t('preferences.appearance.custom_theme.background_color') ">
<el-color-picker v-model="background"></el-color-picker>
</el-form-item>
<el-form-item :label="$t('preferences.appearance.custom_theme.selected_background_color')">
<el-color-picker v-model="selectedBackground"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item :label="$t('preferences.appearance.custom_theme.global_header_color')">
<el-color-picker v-model="globalHeader"></el-color-picker>
</el-form-item>
<el-form-item :label="$t('preferences.appearance.custom_theme.side_menu_color')">
<el-color-picker v-model="sideMenu"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item :label="$t('preferences.appearance.custom_theme.primary_color')">
<el-color-picker v-model="primary"></el-color-picker>
</el-form-item>
<el-form-item :label="$t('preferences.appearance.custom_theme.regular_color')">
<el-color-picker v-model="regular"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item :label="$t('preferences.appearance.custom_theme.secondary_color')">
<el-color-picker v-model="secondary"></el-color-picker>
</el-form-item>
<el-form-item :label="$t('preferences.appearance.custom_theme.border_color')">
<el-color-picker v-model="border"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item :label="$t('preferences.appearance.custom_theme.header_menu_color')">
<el-color-picker v-model="headerMenu"></el-color-picker>
</el-form-item>
<el-form-item :label="$t('preferences.appearance.custom_theme.wrapper_mask_color')">
<el-color-picker v-model="wrapperMask" :show-alpha="true"></el-color-picker>
</el-form-item>
</div>
</el-form>
<el-form class="pallet" label-position="top" size="small">
<div class="item">
<el-form-item
:label="$t('preferences.appearance.custom_theme.background_color')"
>
<el-color-picker v-model="background"></el-color-picker>
</el-form-item>
<el-form-item
:label="
$t('preferences.appearance.custom_theme.selected_background_color')
"
>
<el-color-picker v-model="selectedBackground"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item
:label="$t('preferences.appearance.custom_theme.global_header_color')"
>
<el-color-picker v-model="globalHeader"></el-color-picker>
</el-form-item>
<el-form-item
:label="$t('preferences.appearance.custom_theme.side_menu_color')"
>
<el-color-picker v-model="sideMenu"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item
:label="$t('preferences.appearance.custom_theme.primary_color')"
>
<el-color-picker v-model="primary"></el-color-picker>
</el-form-item>
<el-form-item
:label="$t('preferences.appearance.custom_theme.regular_color')"
>
<el-color-picker v-model="regular"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item
:label="$t('preferences.appearance.custom_theme.secondary_color')"
>
<el-color-picker v-model="secondary"></el-color-picker>
</el-form-item>
<el-form-item
:label="$t('preferences.appearance.custom_theme.border_color')"
>
<el-color-picker v-model="border"></el-color-picker>
</el-form-item>
</div>
<div class="item">
<el-form-item
:label="$t('preferences.appearance.custom_theme.header_menu_color')"
>
<el-color-picker v-model="headerMenu"></el-color-picker>
</el-form-item>
<el-form-item
:label="$t('preferences.appearance.custom_theme.wrapper_mask_color')"
>
<el-color-picker
v-model="wrapperMask"
:show-alpha="true"
></el-color-picker>
</el-form-item>
</div>
</el-form>
</template>
<script>
@ -48,106 +73,116 @@ export default {
name: 'color-pallet',
computed: {
background: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.background_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.background_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
background_color: value
background_color: value,
})
}
},
},
selectedBackground: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.selected_background_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.selected_background_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
selected_background_color: value
selected_background_color: value,
})
}
},
},
globalHeader: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.global_header_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.global_header_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
global_header_color: value
global_header_color: value,
})
}
},
},
sideMenu: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.side_menu_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.side_menu_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
side_menu_color: value
side_menu_color: value,
})
}
},
},
primary: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.primary_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.primary_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
primary_color: value
primary_color: value,
})
}
},
},
regular: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.regular_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.regular_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
regular_color: value
regular_color: value,
})
}
},
},
secondary: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.secondary_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.secondary_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
secondary_color: value
secondary_color: value,
})
}
},
},
border: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.border_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.border_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
border_color: value
border_color: value,
})
}
},
},
headerMenu: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.header_menu_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.header_menu_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
header_menu_color: value
header_menu_color: value,
})
}
},
},
wrapperMask: {
get () {
return this.$store.state.Preferences.Appearance.appearance.customThemeColor.wrapper_mask_color
get() {
return this.$store.state.Preferences.Appearance.appearance
.customThemeColor.wrapper_mask_color
},
set (value) {
set(value) {
this.$store.dispatch('Preferences/Appearance/updateCustomThemeColor', {
wrapper_mask_color: value
wrapper_mask_color: value,
})
}
}
}
},
},
},
}
</script>

View File

@ -22,19 +22,31 @@
<el-button type="text" class="reply" :title="$t('cards.toot.reply')">
<font-awesome-icon icon="reply" size="sm" />
</el-button>
<el-button type="text" class="reblog" :title="$t('cards.toot.reblog')">
<el-button
type="text"
class="reblog"
:title="$t('cards.toot.reblog')"
>
<font-awesome-icon icon="retweet" size="sm" />
</el-button>
<span class="count">
{{ reblogsCount }}
</span>
<el-button type="text" class="favourite" :title="$t('cards.toot.fav')">
<el-button
type="text"
class="favourite"
:title="$t('cards.toot.fav')"
>
<font-awesome-icon icon="star" size="sm" />
</el-button>
<span class="count">
{{ favouritesCount }}
</span>
<popper trigger="click" :options="{ placement: 'bottom' }" ref="popper">
<popper
trigger="click"
:options="{ placement: 'bottom' }"
ref="popper"
>
<div class="popper toot-menu">
<ul class="menu-list">
<li role="button">
@ -51,7 +63,11 @@
</li>
</ul>
</div>
<el-button slot="reference" type="text" :title="$t('cards.toot.detail')">
<el-button
slot="reference"
type="text"
:title="$t('cards.toot.detail')"
>
<font-awesome-icon icon="ellipsis" size="sm" />
</el-button>
</popper>
@ -75,12 +91,12 @@ export default {
props: {
displayNameStyle: {
type: Number,
default: DisplayStyle.DisplayNameAndUsername.value
default: DisplayStyle.DisplayNameAndUsername.value,
},
timeFormat: {
type: Number,
default: TimeFormat.Absolute.value
}
default: TimeFormat.Absolute.value,
},
},
computed: {
sampleIcon() {
@ -119,8 +135,8 @@ export default {
},
favouritesCount() {
return 5
}
}
},
},
}
</script>

View File

@ -1,37 +1,85 @@
<template>
<div id="general" v-loading="loading" :element-loading-background="backgroundColor">
<div
id="general"
v-loading="loading"
:element-loading-background="backgroundColor"
>
<h2>{{ $t('preferences.general.title') }}</h2>
<el-form class="sounds section" label-position="right" label-width="250px" size="small">
<el-form
class="sounds section"
label-position="right"
label-width="250px"
size="small"
>
<h3>{{ $t('preferences.general.sounds.title') }}</h3>
<p class="description">{{ $t('preferences.general.sounds.description') }}</p>
<el-form-item for="fav_rb" :label="$t('preferences.general.sounds.fav_rb')">
<el-switch id="fav_rb" v-model="sound_fav_rb" active-color="#13ce66"> </el-switch>
<p class="description">
{{ $t('preferences.general.sounds.description') }}
</p>
<el-form-item
for="fav_rb"
:label="$t('preferences.general.sounds.fav_rb')"
>
<el-switch id="fav_rb" v-model="sound_fav_rb" active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item for="sound_toot" :label="$t('preferences.general.sounds.toot')">
<el-switch id="sound_toot" v-model="sound_toot" active-color="#13ce66"> </el-switch>
<el-form-item
for="sound_toot"
:label="$t('preferences.general.sounds.toot')"
>
<el-switch id="sound_toot" v-model="sound_toot" active-color="#13ce66">
</el-switch>
</el-form-item>
</el-form>
<el-form class="timeline section" label-potision="right" label-width="302px" size="samll">
<el-form
class="timeline section"
label-potision="right"
label-width="302px"
size="samll"
>
<h3>{{ $t('preferences.general.timeline.title') }}</h3>
<p class="description">{{ $t('preferences.general.timeline.description') }}</p>
<p class="description">
{{ $t('preferences.general.timeline.description') }}
</p>
<el-form-item for="cw" :label="$t('preferences.general.timeline.cw')">
<el-switch id="cw" v-model="timeline_cw" active-color="#13ce66"> </el-switch>
<el-switch id="cw" v-model="timeline_cw" active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item for="nsfw" :label="$t('preferences.general.timeline.nsfw')">
<el-switch id="nsfw" v-model="timeline_nsfw" active-color="#13ce66"> </el-switch>
<el-switch id="nsfw" v-model="timeline_nsfw" active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item for="hideAllAttachments" :label="$t('preferences.general.timeline.hideAllAttachments')">
<el-switch id="hideAllAttachments" v-model="timeline_hide_attachments" active-color="#13ce66"> </el-switch>
<el-form-item
for="hideAllAttachments"
:label="$t('preferences.general.timeline.hideAllAttachments')"
>
<el-switch
id="hideAllAttachments"
v-model="timeline_hide_attachments"
active-color="#13ce66"
>
</el-switch>
</el-form-item>
</el-form>
<el-form class="other section" label-position="right" label-width="250px" size="small" v-if="notDarwin">
<el-form
class="other section"
label-position="right"
label-width="250px"
size="small"
v-if="notDarwin"
>
<h3>{{ $t('preferences.general.other.title') }}</h3>
<el-form-item for="launch" :label="$t('preferences.general.other.launch')">
<el-switch id="launch" v-model="other_launch" active-color="#13ce66"> </el-switch>
<el-form-item
for="launch"
:label="$t('preferences.general.other.launch')"
>
<el-switch id="launch" v-model="other_launch" active-color="#13ce66">
</el-switch>
</el-form-item>
</el-form>
<el-form class="reset section">
<el-button type="info" @click="reset">{{ $t('preferences.general.reset.button') }}</el-button>
<el-button type="info" @click="reset">{{
$t('preferences.general.reset.button')
}}</el-button>
</el-form>
</div>
</template>
@ -43,10 +91,10 @@ export default {
name: 'general',
computed: {
...mapState('Preferences/General', {
loading: state => state.loading
loading: (state) => state.loading,
}),
...mapState({
backgroundColor: state => state.App.theme.background_color
backgroundColor: (state) => state.App.theme.background_color,
}),
...mapGetters('Preferences/General', ['notDarwin']),
sound_fav_rb: {
@ -55,9 +103,9 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/General/updateSound', {
fav_rb: value
fav_rb: value,
})
}
},
},
sound_toot: {
get() {
@ -65,9 +113,9 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/General/updateSound', {
toot: value
toot: value,
})
}
},
},
timeline_cw: {
get() {
@ -75,9 +123,9 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/General/updateTimeline', {
cw: value
cw: value,
})
}
},
},
timeline_nsfw: {
get() {
@ -85,19 +133,20 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/General/updateTimeline', {
nsfw: value
nsfw: value,
})
}
},
},
timeline_hide_attachments: {
get() {
return this.$store.state.Preferences.General.general.timeline.hideAllAttachments
return this.$store.state.Preferences.General.general.timeline
.hideAllAttachments
},
set(value) {
this.$store.dispatch('Preferences/General/updateTimeline', {
hideAllAttachments: value
hideAllAttachments: value,
})
}
},
},
other_launch: {
get() {
@ -105,16 +154,16 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/General/updateOther', {
launch: value
launch: value,
})
}
}
},
},
},
created() {
this.$store.dispatch('Preferences/General/loadGeneral').catch(() => {
this.$message({
message: this.$t('message.preferences_load_error'),
type: 'error'
type: 'error',
})
})
},
@ -122,17 +171,17 @@ export default {
reset() {
this.$store
.dispatch('Preferences/General/reset')
.then(language => {
.then((language) => {
this.$i18n.i18next.changeLanguage(language)
})
.catch(() => {
this.$message({
message: this.$t('message.preferences_load_error'),
type: 'error'
type: 'error',
})
})
}
}
},
},
}
</script>

View File

@ -3,19 +3,35 @@
<h2>{{ $t('preferences.language.title') }}</h2>
<el-form class="display-language section" label-position="top" size="small">
<h3>{{ $t('preferences.language.language.title') }}</h3>
<el-form-item for="language" :label="$t('preferences.language.language.description')">
<el-form-item
for="language"
:label="$t('preferences.language.language.description')"
>
<el-select id="language" v-model="displayLanguage" placeholder="style">
<el-option v-for="lang in languages" :key="lang.key" :label="lang.name" :value="lang.key"> </el-option>
<el-option
v-for="lang in languages"
:key="lang.key"
:label="lang.name"
:value="lang.key"
>
</el-option>
</el-select>
</el-form-item>
</el-form>
<el-form class="spellchecker section" label-position="top" size="small">
<h3>{{ $t('preferences.language.spellchecker.title') }}</h3>
<el-form-item for="spellcheck" :label="$t('preferences.language.spellchecker.enabled')">
<el-switch id="spellcheck" v-model="spellcheck" active-color="#13ce66"> </el-switch>
<el-form-item
for="spellcheck"
:label="$t('preferences.language.spellchecker.enabled')"
>
<el-switch id="spellcheck" v-model="spellcheck" active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item for="spellcheck_languages">
<el-checkbox-group id="spellcheck_languages" v-model="spellcheckLanguages">
<el-checkbox-group
id="spellcheck_languages"
v-model="spellcheckLanguages"
>
<el-checkbox
v-for="language in languages"
:label="language.key"
@ -56,8 +72,8 @@ export default {
Language.sv_se,
Language.tzm,
Language.zh_cn,
Language.zh_tw
]
Language.zh_tw,
],
}
},
computed: {
@ -66,36 +82,44 @@ export default {
return this.$store.state.Preferences.Language.language.language
},
set(value) {
this.$store.dispatch('Preferences/Language/changeLanguage', value).then(key => {
this.$i18n.i18next.changeLanguage(key)
})
}
this.$store
.dispatch('Preferences/Language/changeLanguage', value)
.then((key) => {
this.$i18n.i18next.changeLanguage(key)
})
},
},
spellcheck: {
get() {
return this.$store.state.Preferences.Language.language.spellchecker.enabled
return this.$store.state.Preferences.Language.language.spellchecker
.enabled
},
set(value) {
this.$store.dispatch('Preferences/Language/toggleSpellchecker', value)
}
},
},
spellcheckLanguages: {
get() {
return this.$store.state.Preferences.Language.language.spellchecker.languages
return this.$store.state.Preferences.Language.language.spellchecker
.languages
},
set(value) {
this.$store.dispatch('Preferences/Language/updateSpellcheckerLanguages', value).catch(() => {
this.$message({
message: this.$t('message.language_not_support_spellchecker_error'),
type: 'error'
this.$store
.dispatch('Preferences/Language/updateSpellcheckerLanguages', value)
.catch(() => {
this.$message({
message: this.$t(
'message.language_not_support_spellchecker_error'
),
type: 'error',
})
})
})
}
}
},
},
},
created() {
this.$store.dispatch('Preferences/Language/loadLanguage')
}
},
}
</script>

View File

@ -3,38 +3,100 @@
<h2>{{ $t('preferences.network.proxy.title') }}</h2>
<el-form class="network section" size="small" label-width="120px">
<div class="proxy-source">
<el-radio v-model="source" label="no">{{ $t('preferences.network.proxy.no') }}</el-radio>
<el-radio v-model="source" label="no">{{
$t('preferences.network.proxy.no')
}}</el-radio>
</div>
<div class="proxy-source">
<el-radio v-model="source" label="system">{{ $t('preferences.network.proxy.system') }}</el-radio>
<el-radio v-model="source" label="system">{{
$t('preferences.network.proxy.system')
}}</el-radio>
</div>
<div class="proxy-source">
<el-radio v-model="source" label="manual">{{ $t('preferences.network.proxy.manual') }}</el-radio>
<el-radio v-model="source" label="manual">{{
$t('preferences.network.proxy.manual')
}}</el-radio>
</div>
<el-form-item for="proxyProtocol" :label="$t('preferences.network.proxy.protocol')">
<el-select v-model="proxyProtocol" placeholder="Select protocol" :disabled="!manualProxyConfiguration">
<el-option :label="$t('preferences.network.proxy.protocol_list.http')" value="http"></el-option>
<el-option :label="$t('preferences.network.proxy.protocol_list.https')" value="https"></el-option>
<el-option :label="$t('preferences.network.proxy.protocol_list.socks4')" value="socks4"></el-option>
<el-option :label="$t('preferences.network.proxy.protocol_list.socks4a')" value="socks4a"></el-option>
<el-option :label="$t('preferences.network.proxy.protocol_list.socks5')" value="socks5"></el-option>
<el-option :label="$t('preferences.network.proxy.protocol_list.socks5h')" value="socks5h"></el-option>
<el-form-item
for="proxyProtocol"
:label="$t('preferences.network.proxy.protocol')"
>
<el-select
v-model="proxyProtocol"
placeholder="Select protocol"
:disabled="!manualProxyConfiguration"
>
<el-option
:label="$t('preferences.network.proxy.protocol_list.http')"
value="http"
></el-option>
<el-option
:label="$t('preferences.network.proxy.protocol_list.https')"
value="https"
></el-option>
<el-option
:label="$t('preferences.network.proxy.protocol_list.socks4')"
value="socks4"
></el-option>
<el-option
:label="$t('preferences.network.proxy.protocol_list.socks4a')"
value="socks4a"
></el-option>
<el-option
:label="$t('preferences.network.proxy.protocol_list.socks5')"
value="socks5"
></el-option>
<el-option
:label="$t('preferences.network.proxy.protocol_list.socks5h')"
value="socks5h"
></el-option>
</el-select>
</el-form-item>
<el-form-item for="proxyHost" :label="$t('preferences.network.proxy.host')">
<el-input v-model="proxyHost" :disabled="!manualProxyConfiguration" placeholder="proxy.example.com"></el-input>
<el-form-item
for="proxyHost"
:label="$t('preferences.network.proxy.host')"
>
<el-input
v-model="proxyHost"
:disabled="!manualProxyConfiguration"
placeholder="proxy.example.com"
></el-input>
</el-form-item>
<el-form-item for="proxyPort" :label="$t('preferences.network.proxy.port')">
<el-input v-model="proxyPort" :disabled="!manualProxyConfiguration" placeholder="8080"></el-input>
<el-form-item
for="proxyPort"
:label="$t('preferences.network.proxy.port')"
>
<el-input
v-model="proxyPort"
:disabled="!manualProxyConfiguration"
placeholder="8080"
></el-input>
</el-form-item>
<el-form-item for="proxyUsername" :label="$t('preferences.network.proxy.username')">
<el-input v-model="proxyUsername" :disabled="!manualProxyConfiguration" placeholder="username"></el-input>
<el-form-item
for="proxyUsername"
:label="$t('preferences.network.proxy.username')"
>
<el-input
v-model="proxyUsername"
:disabled="!manualProxyConfiguration"
placeholder="username"
></el-input>
</el-form-item>
<el-form-item for="proxyPassword" :label="$t('preferences.network.proxy.password')">
<el-input v-model="proxyPassword" :disabled="!manualProxyConfiguration" placeholder="password" show-password></el-input>
<el-form-item
for="proxyPassword"
:label="$t('preferences.network.proxy.password')"
>
<el-input
v-model="proxyPassword"
:disabled="!manualProxyConfiguration"
placeholder="password"
show-password
></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSave">{{ $t('preferences.network.save') }}</el-button>
<el-button type="primary" @click="onSave">{{
$t('preferences.network.save')
}}</el-button>
</el-form-item>
</el-form>
</div>
@ -53,7 +115,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/changeSource', value)
}
},
},
proxyProtocol: {
get() {
@ -61,7 +123,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/updateProtocol', value)
}
},
},
proxyHost: {
get() {
@ -69,7 +131,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/updateHost', value)
}
},
},
proxyPort: {
get() {
@ -77,7 +139,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/updatePort', value)
}
},
},
proxyUsername: {
get() {
@ -85,7 +147,7 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/updateUsername', value)
}
},
},
proxyPassword: {
get() {
@ -93,22 +155,22 @@ export default {
},
set(value) {
this.$store.dispatch('Preferences/Network/updatePassword', value)
}
}
},
},
},
created() {
this.$store.dispatch('Preferences/Network/loadProxy').catch(() => {
this.$message({
message: this.$t('message.preferences_load_error'),
type: 'error'
type: 'error',
})
})
},
methods: {
onSave() {
this.$store.dispatch('Preferences/Network/saveProxyConfig')
}
}
},
},
}
</script>

View File

@ -1,34 +1,90 @@
<template>
<div id="notification">
<h2>{{ $t('preferences.notification.title') }}</h2>
<el-form class="section" label-position="right" label-width="360px" size="small">
<p class="description">{{ $t('preferences.notification.enable.description') }}</p>
<el-form-item for="notifyReply" :label="$t('preferences.notification.enable.reply')">
<el-switch id="notifyReply" v-model="notifyReply" active-color="#13ce66"> </el-switch>
<el-form
class="section"
label-position="right"
label-width="360px"
size="small"
>
<p class="description">
{{ $t('preferences.notification.enable.description') }}
</p>
<el-form-item
for="notifyReply"
:label="$t('preferences.notification.enable.reply')"
>
<el-switch
id="notifyReply"
v-model="notifyReply"
active-color="#13ce66"
>
</el-switch>
</el-form-item>
<el-form-item for="notifyReblog" :label="$t('preferences.notification.enable.reblog')">
<el-switch id="notifyReblog" v-model="notifyReblog" active-color="#13ce66"> </el-switch>
<el-form-item
for="notifyReblog"
:label="$t('preferences.notification.enable.reblog')"
>
<el-switch
id="notifyReblog"
v-model="notifyReblog"
active-color="#13ce66"
>
</el-switch>
</el-form-item>
<el-form-item for="notifyReaction" :label="$t('preferences.notification.enable.reaction')">
<el-switch id="notifyReaction" v-model="notifyReaction" active-color="#13ce66"> </el-switch>
<el-form-item
for="notifyReaction"
:label="$t('preferences.notification.enable.reaction')"
>
<el-switch
id="notifyReaction"
v-model="notifyReaction"
active-color="#13ce66"
>
</el-switch>
</el-form-item>
<el-form-item for="notifyFavourite" :label="$t('preferences.notification.enable.favourite')">
<el-switch id="notifyFavourite" v-model="notifyFavourite" active-color="#13ce66"> </el-switch>
<el-form-item
for="notifyFavourite"
:label="$t('preferences.notification.enable.favourite')"
>
<el-switch
id="notifyFavourite"
v-model="notifyFavourite"
active-color="#13ce66"
>
</el-switch>
</el-form-item>
<el-form-item for="notifyFollow" :label="$t('preferences.notification.enable.follow')">
<el-form-item
for="notifyFollow"
:label="$t('preferences.notification.enable.follow')"
>
<el-switch v-model="notifyFollow" active-color="#13ce66"> </el-switch>
</el-form-item>
<el-form-item for="notifyFollowRequest" :label="$t('preferences.notification.enable.follow_request')">
<el-switch v-model="notifyFollowRequest" active-color="#13ce66"> </el-switch>
<el-form-item
for="notifyFollowRequest"
:label="$t('preferences.notification.enable.follow_request')"
>
<el-switch v-model="notifyFollowRequest" active-color="#13ce66">
</el-switch>
</el-form-item>
<el-form-item for="notifyStatus" :label="$t('preferences.notification.enable.status')">
<el-form-item
for="notifyStatus"
:label="$t('preferences.notification.enable.status')"
>
<el-switch v-model="notifyStatus" active-color="#13ce66"> </el-switch>
</el-form-item>
<el-form-item for="notifyPollVote" :label="$t('preferences.notification.enable.poll_vote')">
<el-form-item
for="notifyPollVote"
:label="$t('preferences.notification.enable.poll_vote')"
>
<el-switch v-model="notifyPollVote" active-color="#13ce66"> </el-switch>
</el-form-item>
<el-form-item for="notifyPollExpired" :label="$t('preferences.notification.enable.poll_expired')">
<el-switch v-model="notifyPollExpired" active-color="#13ce66"> </el-switch>
<el-form-item
for="notifyPollExpired"
:label="$t('preferences.notification.enable.poll_expired')"
>
<el-switch v-model="notifyPollExpired" active-color="#13ce66">
</el-switch>
</el-form-item>
</el-form>
</div>
@ -40,103 +96,114 @@ export default {
computed: {
notifyReply: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.reply
return this.$store.state.Preferences.Notification.notification.notify
.reply
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
reply: value
reply: value,
})
}
},
},
notifyReblog: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.reblog
return this.$store.state.Preferences.Notification.notification.notify
.reblog
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
reblog: value
reblog: value,
})
}
},
},
notifyFavourite: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.favourite
return this.$store.state.Preferences.Notification.notification.notify
.favourite
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
favourite: value
favourite: value,
})
}
},
},
notifyFollow: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.follow
return this.$store.state.Preferences.Notification.notification.notify
.follow
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
follow: value
follow: value,
})
}
},
},
notifyFollowRequest: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.follow_request
return this.$store.state.Preferences.Notification.notification.notify
.follow_request
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
follow_request: value
follow_request: value,
})
}
},
},
notifyReaction: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.reaction
return this.$store.state.Preferences.Notification.notification.notify
.reaction
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
reaction: value
reaction: value,
})
}
},
},
notifyStatus: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.status
return this.$store.state.Preferences.Notification.notification.notify
.status
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
status: value
status: value,
})
}
},
},
notifyPollVote: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.poll_vote
return this.$store.state.Preferences.Notification.notification.notify
.poll_vote
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
poll_vote: value
poll_vote: value,
})
}
},
},
notifyPollExpired: {
get() {
return this.$store.state.Preferences.Notification.notification.notify.poll_expired
return this.$store.state.Preferences.Notification.notification.notify
.poll_expired
},
set(value) {
this.$store.dispatch('Preferences/Notification/updateNotify', {
poll_expired: value
poll_expired: value,
})
}
}
},
},
},
created() {
this.$store.dispatch('Preferences/Notification/loadNotification').catch(() => {
this.$message({
message: this.$t('message.preferences_load_error'),
type: 'error'
this.$store
.dispatch('Preferences/Notification/loadNotification')
.catch(() => {
this.$message({
message: this.$t('message.preferences_load_error'),
type: 'error',
})
})
})
}
},
}
</script>

View File

@ -6,7 +6,13 @@
<h1>{{ $t('settings.title') }}</h1>
</el-col>
<el-col :span="1">
<el-button type="text" icon="el-icon-close" @click="close" class="close-button" role="button"></el-button>
<el-button
type="text"
:icon="ElIconClose"
@click="close"
class="close-button"
role="button"
></el-button>
</el-col>
</el-row>
</el-header>
@ -42,15 +48,21 @@
</template>
<script>
import { Close as ElIconClose } from '@element-plus/icons'
import { mapState } from 'vuex'
export default {
data() {
return {
ElIconClose,
}
},
name: 'Settings',
computed: {
...mapState({
primaryColor: state => state.App.theme.primary_color,
backgroundColor: state => state.App.theme.background_color
})
primaryColor: (state) => state.App.theme.primary_color,
backgroundColor: (state) => state.App.theme.background_color,
}),
},
created() {
this.$store.commit('Settings/changeAccountID', this.id())
@ -65,8 +77,8 @@ export default {
},
activeRoute() {
return this.$route.path
}
}
},
},
}
</script>

View File

@ -17,17 +17,24 @@
v-loading="filtersLoading"
:element-loading-background="backgroundColor"
>
<el-table-column prop="phrase" label="Keyword" width="180"> </el-table-column>
<el-table-column prop="phrase" label="Keyword" width="180">
</el-table-column>
<el-table-column label="Context">
<template slot-scope="scope">
<span>{{ filters[scope.$index].context.join(',') }}</span>
</template>
</el-table-column>
<el-table-column prop="expires_at" label="Expires" width="180"> </el-table-column>
<el-table-column prop="expires_at" label="Expires" width="180">
</el-table-column>
<el-table-column width="80">
<template slot-scope="scope">
<el-button type="text">
<router-link tag="span" :to="`/${id()}/settings/filters/${filters[scope.$index].id}/edit`">
<router-link
tag="span"
:to="`/${id()}/settings/filters/${
filters[scope.$index].id
}/edit`"
>
{{ $t('settings.filters.edit.title') }}
</router-link>
</el-button>
@ -35,7 +42,10 @@
</el-table-column>
<el-table-column width="80">
<template slot-scope="scope">
<el-button type="text" @click="deleteFilter(filters[scope.$index].id)">
<el-button
type="text"
@click="deleteFilter(filters[scope.$index].id)"
>
{{ $t('settings.filters.delete.title') }}
</el-button>
</template>
@ -52,15 +62,15 @@ export default {
name: 'Filters',
computed: {
...mapState('Settings/Filters', {
filters: state => state.filters,
filtersLoading: state => state.filtersLoading
filters: (state) => state.filters,
filtersLoading: (state) => state.filtersLoading,
}),
...mapState({
backgroundColor: state => state.App.theme.background_color
backgroundColor: (state) => state.App.theme.background_color,
}),
...mapState('TimelineSpace', {
sns: state => state.sns
})
sns: (state) => state.sns,
}),
},
async created() {
await this.$store.dispatch('Settings/Filters/fetchFilters')
@ -73,12 +83,12 @@ export default {
this.$confirm(this.$t('settings.filters.delete.confirm'), 'Warning', {
confirmButtonText: this.$t('settings.filters.delete.confirm_ok'),
cancelButtonText: this.$t('settings.filters.delete.confirm_cancel'),
type: 'warning'
type: 'warning',
}).then(() => {
return this.$store.dispatch('Settings/Filters/deleteFilter', id)
})
}
}
},
},
}
</script>

View File

@ -1,7 +1,14 @@
<template>
<div id="edit_filter">
<h2>{{ $t('settings.filters.edit.title') }}</h2>
<FilterForm v-model="filter" @cancel="cancel" @onSubmit="onSubmit" :loading="loading" :sns="sns"> </FilterForm>
<FilterForm
v-model="filter"
@cancel="cancel"
@onSubmit="onSubmit"
:loading="loading"
:sns="sns"
>
</FilterForm>
</div>
</template>
@ -15,10 +22,10 @@ export default {
components: { FilterForm },
computed: {
...mapState('Settings/Filters/Edit', {
loading: state => state.loading
loading: (state) => state.loading,
}),
...mapState('TimelineSpace', {
sns: state => state.sns
sns: (state) => state.sns,
}),
filter: {
get() {
@ -26,11 +33,14 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/Filters/Edit/editFilter', value)
}
}
},
},
},
async created() {
await this.$store.dispatch('Settings/Filters/Edit/fetchFilter', this.filter_id)
await this.$store.dispatch(
'Settings/Filters/Edit/fetchFilter',
this.filter_id
)
},
methods: {
cancel() {
@ -42,14 +52,14 @@ export default {
.then(() => {
this.$router.go(-1)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.update_filter_error'),
type: 'error'
type: 'error',
})
})
}
}
},
},
}
</script>

View File

@ -1,7 +1,14 @@
<template>
<div id="new_filter">
<h2>{{ $t('settings.filters.new.title') }}</h2>
<FilterForm v-model="filter" @cancel="cancel" @onSubmit="onSubmit" :loading="loading" :sns="sns"> </FilterForm>
<FilterForm
v-model="filter"
@cancel="cancel"
@onSubmit="onSubmit"
:loading="loading"
:sns="sns"
>
</FilterForm>
</div>
</template>
@ -14,10 +21,10 @@ export default {
components: { FilterForm },
computed: {
...mapState('Settings/Filters/New', {
loading: state => state.loading
loading: (state) => state.loading,
}),
...mapState('TimelineSpace', {
sns: state => state.sns
sns: (state) => state.sns,
}),
filter: {
get() {
@ -25,8 +32,8 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/Filters/New/editFilter', value)
}
}
},
},
},
methods: {
cancel() {
@ -38,14 +45,14 @@ export default {
.then(() => {
this.$router.go(-1)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.create_filter_error'),
type: 'error'
type: 'error',
})
})
}
}
},
},
}
</script>

View File

@ -1,11 +1,23 @@
<template>
<el-form ref="form" class="section" label-width="200px" label-position="right" size="medium">
<el-form
ref="form"
class="section"
label-width="200px"
label-position="right"
size="medium"
>
<el-form-item :label="$t('settings.filters.form.phrase')">
<el-input v-model="filterPhrase"></el-input>
</el-form-item>
<el-form-item :label="$t('settings.filters.form.expire')">
<el-select v-model="filterExpire" value-key="value">
<el-option v-for="exp in expires" :key="exp.value" :label="exp.label" :value="exp"> </el-option>
<el-option
v-for="exp in expires"
:key="exp.value"
:label="exp.label"
:value="exp"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item :label="$t('settings.filters.form.context')">
@ -15,19 +27,30 @@
<el-checkbox label="notifications"></el-checkbox>
<el-checkbox label="public"></el-checkbox>
<el-checkbox label="thread"></el-checkbox>
<el-checkbox label="account" :disabled="accountDisabled()"></el-checkbox>
<el-checkbox
label="account"
:disabled="accountDisabled()"
></el-checkbox>
</el-checkbox-group>
</template>
</el-form-item>
<el-form-item>
<el-checkbox v-model="filterIrreversible">{{ $t('settings.filters.form.irreversible') }}</el-checkbox>
<el-checkbox v-model="filterIrreversible">{{
$t('settings.filters.form.irreversible')
}}</el-checkbox>
</el-form-item>
<el-form-item>
<el-checkbox v-model="filterWholeWord">{{ $t('settings.filters.form.whole_word') }}</el-checkbox>
<el-checkbox v-model="filterWholeWord">{{
$t('settings.filters.form.whole_word')
}}</el-checkbox>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit" v-loading="loading">{{ $t('settings.filters.form.submit') }}</el-button>
<el-button @click="cancel">{{ $t('settings.filters.form.cancel') }}</el-button>
<el-button type="primary" @click="onSubmit" v-loading="loading">{{
$t('settings.filters.form.submit')
}}</el-button>
<el-button @click="cancel">{{
$t('settings.filters.form.cancel')
}}</el-button>
</el-form-item>
</el-form>
</template>
@ -38,48 +61,48 @@ export default {
props: {
loading: {
type: Boolean,
default: false
default: false,
},
value: {
type: Object
type: Object,
},
sns: {
type: String,
default: 'mastodon'
}
default: 'mastodon',
},
},
data() {
return {
expires: [
{
label: this.$t('settings.filters.expires.never'),
value: null
value: null,
},
{
label: this.$t('settings.filters.expires.30_minutes'),
value: 60 * 30
value: 60 * 30,
},
{
label: this.$t('settings.filters.expires.1_hour'),
value: 3600
value: 3600,
},
{
label: this.$t('settings.filters.expires.6_hours'),
value: 3600 * 6
value: 3600 * 6,
},
{
label: this.$t('settings.filters.expires.12_hours'),
value: 3600 * 12
value: 3600 * 12,
},
{
label: this.$t('settings.filters.expires.1_day'),
value: 3600 * 24
value: 3600 * 24,
},
{
label: this.$t('settings.filters.expires.1_week'),
value: 3600 * 24 * 7
}
]
value: 3600 * 24 * 7,
},
],
}
},
computed: {
@ -89,7 +112,7 @@ export default {
},
set(value) {
this.$emit('input', value)
}
},
},
filterPhrase: {
get() {
@ -97,9 +120,9 @@ export default {
},
set(value) {
this.filter = Object.assign({}, this.filter, {
phrase: value
phrase: value,
})
}
},
},
filterExpire: {
get() {
@ -107,9 +130,9 @@ export default {
},
set(value) {
this.filter = Object.assign({}, this.filter, {
expires_at: value
expires_at: value,
})
}
},
},
filterContext: {
get() {
@ -117,9 +140,9 @@ export default {
},
set(value) {
this.filter = Object.assign({}, this.filter, {
context: value
context: value,
})
}
},
},
filterIrreversible: {
get() {
@ -127,9 +150,9 @@ export default {
},
set(value) {
this.filter = Object.assign({}, this.filter, {
irreversible: value
irreversible: value,
})
}
},
},
filterWholeWord: {
get() {
@ -137,10 +160,10 @@ export default {
},
set(value) {
this.filter = Object.assign({}, this.filter, {
whole_word: value
whole_word: value,
})
}
}
},
},
},
methods: {
cancel() {
@ -151,8 +174,8 @@ export default {
},
accountDisabled() {
return this.sns === 'pleroma'
}
}
},
},
}
</script>

View File

@ -1,15 +1,38 @@
<template>
<div id="general">
<h2>{{ $t('settings.general.title') }}</h2>
<el-form class="toot section" label-width="250px" label-position="right" size="medium">
<el-form
class="toot section"
label-width="250px"
label-position="right"
size="medium"
>
<h3>{{ $t('settings.general.toot.title') }}</h3>
<el-form-item for="visibility" :label="$t('settings.general.toot.visibility.description')">
<el-select id="visibility" v-model="tootVisibility" placeholder="visibility">
<el-option v-for="v in visibilities" :key="v.value" :label="$t(v.name)" :value="v.value"> </el-option>
<el-form-item
for="visibility"
:label="$t('settings.general.toot.visibility.description')"
>
<el-select
id="visibility"
v-model="tootVisibility"
placeholder="visibility"
>
<el-option
v-for="v in visibilities"
:key="v.value"
:label="$t(v.name)"
:value="v.value"
>
</el-option>
</el-select>
<p class="notice">{{ $t('settings.general.toot.visibility.notice') }}</p>
<p class="notice">
{{ $t('settings.general.toot.visibility.notice') }}
</p>
</el-form-item>
<el-form-item for="sensitive" :label="$t('settings.general.toot.sensitive.description')">
<el-form-item
for="sensitive"
:label="$t('settings.general.toot.sensitive.description')"
>
<el-switch id="sensitive" v-model="tootSensitive"></el-switch>
</el-form-item>
</el-form>
@ -23,7 +46,11 @@ export default {
name: 'General',
data() {
return {
visibilities: [Visibility.Public, Visibility.Unlisted, Visibility.Private]
visibilities: [
Visibility.Public,
Visibility.Unlisted,
Visibility.Private,
],
}
},
computed: {
@ -33,7 +60,7 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/General/setVisibility', value)
}
},
},
tootSensitive: {
get() {
@ -41,12 +68,12 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/General/setSensitive', value)
}
}
},
},
},
created() {
this.$store.dispatch('Settings/General/fetchSettings')
}
},
}
</script>

View File

@ -1,30 +1,60 @@
<template>
<div id="timeline">
<h2>{{ $t('settings.timeline.title') }}</h2>
<el-form class="unread-notification section" size="medium" label-position="right" label-width="250px">
<el-form
class="unread-notification section"
size="medium"
label-position="right"
label-width="250px"
>
<h3>{{ $t('settings.timeline.unread_notification.title') }}</h3>
<p class="description">{{ $t('settings.timeline.unread_notification.description') }}</p>
<p class="description">
{{ $t('settings.timeline.unread_notification.description') }}
</p>
<el-form-item for="direct" :label="$t('settings.timeline.unread_notification.direct')">
<el-form-item
for="direct"
:label="$t('settings.timeline.unread_notification.direct')"
>
<el-switch v-model="direct" id="direct" />
</el-form-item>
<el-form-item for="local" :label="$t('settings.timeline.unread_notification.local')">
<el-form-item
for="local"
:label="$t('settings.timeline.unread_notification.local')"
>
<el-switch v-model="local" id="local" />
</el-form-item>
<el-form-item for="public" :label="$t('settings.timeline.unread_notification.public')">
<el-form-item
for="public"
:label="$t('settings.timeline.unread_notification.public')"
>
<el-switch v-model="public" id="public" />
</el-form-item>
</el-form>
<el-form class="use-marker section" size="medium" label-position="right" label-width="250px">
<el-form
class="use-marker section"
size="medium"
label-position="right"
label-width="250px"
>
<h3>{{ $t('settings.timeline.use_marker.title') }}</h3>
<el-form-item for="marker_home" :label="$t('settings.timeline.use_marker.home')">
<el-form-item
for="marker_home"
:label="$t('settings.timeline.use_marker.home')"
>
<el-switch v-model="marker_home" id="marker_home" />
</el-form-item>
<el-form-item for="marker_notifications" :label="$t('settings.timeline.use_marker.notifications')">
<el-form-item
for="marker_notifications"
:label="$t('settings.timeline.use_marker.notifications')"
>
<el-switch v-model="marker_notifications" id="marker_notifications" />
</el-form-item>
<el-form-item for="marker_mentions" :label="$t('settings.timeline.use_marker.mentions')">
<el-form-item
for="marker_mentions"
:label="$t('settings.timeline.use_marker.mentions')"
>
<el-switch v-model="marker_mentions" id="marker_mentions" />
</el-form-item>
</el-form>
@ -37,33 +67,36 @@ export default {
computed: {
direct: {
get() {
return this.$store.state.Settings.Timeline.setting.unreadNotification.direct
return this.$store.state.Settings.Timeline.setting.unreadNotification
.direct
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUnreadNotification', {
direct: value
direct: value,
})
}
},
},
local: {
get() {
return this.$store.state.Settings.Timeline.setting.unreadNotification.local
return this.$store.state.Settings.Timeline.setting.unreadNotification
.local
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUnreadNotification', {
local: value
local: value,
})
}
},
},
public: {
get() {
return this.$store.state.Settings.Timeline.setting.unreadNotification.public
return this.$store.state.Settings.Timeline.setting.unreadNotification
.public
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUnreadNotification', {
public: value
public: value,
})
}
},
},
marker_home: {
get() {
@ -71,19 +104,20 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUseMarker', {
home: value
home: value,
})
}
},
},
marker_notifications: {
get() {
return this.$store.state.Settings.Timeline.setting.useMarker.notifications
return this.$store.state.Settings.Timeline.setting.useMarker
.notifications
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUseMarker', {
notifications: value
notifications: value,
})
}
},
},
marker_mentions: {
get() {
@ -91,14 +125,14 @@ export default {
},
set(value) {
this.$store.dispatch('Settings/Timeline/changeUseMarker', {
mentions: value
mentions: value,
})
}
}
},
},
},
async created() {
await this.$store.dispatch('Settings/Timeline/loadTimelineSetting')
}
},
}
</script>

View File

@ -39,18 +39,18 @@ export default {
data() {
return {
dropTarget: null,
droppableVisible: false
droppableVisible: false,
}
},
computed: {
...mapState({
loading: state => state.TimelineSpace.loading,
collapse: state => state.TimelineSpace.SideMenu.collapse
loading: (state) => state.TimelineSpace.loading,
collapse: (state) => state.TimelineSpace.SideMenu.collapse,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
return !this.modalOpened
}
},
},
async created() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/close')
@ -88,17 +88,20 @@ export default {
await this.clear()
try {
await this.$store.dispatch('TimelineSpace/initLoad', this.$route.params.id)
await this.$store.dispatch(
'TimelineSpace/initLoad',
this.$route.params.id
)
} catch (err) {
if (err instanceof AccountLoadError) {
this.$message({
message: this.$t('message.account_load_error'),
type: 'error'
type: 'error',
})
} else if (err instanceof TimelineFetchError) {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
}
}
@ -109,14 +112,17 @@ export default {
e.preventDefault()
e.stopPropagation()
this.droppableVisible = false
if (e.dataTransfer.files.item(0) === null || e.dataTransfer.files.item(0) === undefined) {
if (
e.dataTransfer.files.item(0) === null ||
e.dataTransfer.files.item(0) === undefined
) {
return false
}
const file = e.dataTransfer.files.item(0)
if (!file.type.includes('image') && !file.type.includes('video')) {
this.$message({
message: this.$t('validation.new_toot.attach_image'),
type: 'error'
type: 'error',
})
return false
}
@ -127,16 +133,16 @@ export default {
.then(() => {
Event.$emit('image-uploaded')
})
.catch(err => {
.catch((err) => {
if (err instanceof NewTootAttachLength) {
this.$message({
message: this.$t('validation.new_toot.attach_length', { max: 4 }),
type: 'error'
type: 'error',
})
} else {
this.$message({
message: this.$t('message.attach_error'),
type: 'error'
type: 'error',
})
}
})
@ -162,8 +168,8 @@ export default {
this.$store.commit('TimelineSpace/Modals/Shortcut/changeModal', true)
break
}
}
}
},
},
}
</script>

View File

@ -1,7 +1,15 @@
<template>
<div id="contents" ref="contents" :style="customWidth" @mouseup="dragEnd" @mousemove="resize">
<div
id="contents"
ref="contents"
:style="customWidth"
@mouseup="dragEnd"
@mousemove="resize"
>
<div
:class="openSideBar ? 'timeline-wrapper-with-side-bar' : 'timeline-wrapper'"
:class="
openSideBar ? 'timeline-wrapper-with-side-bar' : 'timeline-wrapper'
"
v-loading="loading"
:element-loading-text="$t('message.loading')"
element-loading-spinner="el-icon-loading"
@ -34,25 +42,25 @@ export default {
data() {
return {
sidebarWidth: 360,
dragging: false
dragging: false,
}
},
components: {
SideBar
SideBar,
},
computed: {
...mapState('TimelineSpace/Contents', {
loading: state => state.loading
loading: (state) => state.loading,
}),
...mapState('TimelineSpace/Contents/SideBar', {
openSideBar: state => state.openSideBar
openSideBar: (state) => state.openSideBar,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
customWidth: function () {
return {
'--current-sidebar-width': `${this.sidebarWidth}px`
'--current-sidebar-width': `${this.sidebarWidth}px`,
}
}
},
},
methods: {
resize(event) {
@ -67,8 +75,8 @@ export default {
dragEnd() {
this.dragging = false
this.$refs.contents.style.setProperty('user-select', 'text')
}
}
},
},
}
</script>

View File

@ -1,9 +1,28 @@
<template>
<div id="bookmarks" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="bookmarks" :min-item-size="60" id="scroller" class="scroller" ref="scroller">
<div
id="bookmarks"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="bookmarks"
:min-item-size="60"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri === focusedId"
@ -19,49 +38,55 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import Toot from '@/components/organisms/Toot'
import reloadable from '~/src/renderer/components/mixins/reloadable'
import { Event } from '~/src/renderer/components/event'
export default {
name: 'bookmarks',
components: { Toot },
mixins: [reloadable],
data() {
return {
heading: true,
focusedId: null
focusedId: null,
ElIconArrowUp,
}
},
name: 'bookmarks',
components: { Toot },
mixins: [reloadable],
computed: {
...mapState('TimelineSpace', {
account: state => state.account
account: (state) => state.account,
}),
...mapState('App', {
backgroundColor: state => state.theme.background_color
backgroundColor: (state) => state.theme.background_color,
}),
...mapState('TimelineSpace/HeaderMenu', {
startReload: state => state.reload
startReload: (state) => state.reload,
}),
...mapState('TimelineSpace/Contents/SideBar', {
openSideBar: state => state.openSideBar
openSideBar: (state) => state.openSideBar,
}),
...mapState('TimelineSpace/Contents/Bookmarks', {
bookmarks: state => state.bookmarks,
lazyLoading: state => state.lazyLoading
bookmarks: (state) => state.bookmarks,
lazyLoading: (state) => state.lazyLoading,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
return !this.focusedId && !this.modalOpened
}
},
},
created() {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
@ -70,7 +95,7 @@ export default {
.catch(() => {
this.$message({
message: this.$t('message.bookmark_fetch_error'),
type: 'error'
type: 'error',
})
})
.finally(() => {
@ -78,7 +103,9 @@ export default {
})
},
mounted() {
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
const previousFocusedId = this.focusedId
@ -93,8 +120,13 @@ export default {
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Bookmarks/updateBookmarks', [])
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -112,7 +144,7 @@ export default {
} else if (newState === null && !this.heading) {
this.heading = true
}
}
},
},
methods: {
updateToot(message) {
@ -123,15 +155,21 @@ export default {
},
onScroll(event) {
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store.dispatch('TimelineSpace/Contents/Bookmarks/lazyFetchBookmarks', this.bookmarks[this.bookmarks.length - 1]).catch(() => {
this.$message({
message: this.$t('message.bookmark_fetch_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/Bookmarks/lazyFetchBookmarks',
this.bookmarks[this.bookmarks.length - 1]
)
.catch(() => {
this.$message({
message: this.$t('message.bookmark_fetch_error'),
type: 'error',
})
})
})
}
// for upper
if (event.target.scrollTop > 10 && this.heading) {
@ -144,12 +182,14 @@ export default {
this.$store.commit('TimelineSpace/changeLoading', true)
try {
const account = await this.reloadable()
await this.$store.dispatch('TimelineSpace/Contents/Bookmarks/fetchBookmarks', account).catch(() => {
this.$message({
message: this.$t('message.bookmark_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/Bookmarks/fetchBookmarks', account)
.catch(() => {
this.$message({
message: this.$t('message.bookmark_fetch_error'),
type: 'error',
})
})
})
} finally {
this.$store.commit('TimelineSpace/changeLoading', false)
}
@ -159,7 +199,9 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.bookmarks.findIndex(toot => this.focusedId === toot.uri)
const currentIndex = this.bookmarks.findIndex(
(toot) => this.focusedId === toot.uri
)
if (currentIndex === -1) {
this.focusedId = this.bookmarks[0].uri
} else if (currentIndex < this.bookmarks.length) {
@ -167,7 +209,9 @@ export default {
}
},
focusPrev() {
const currentIndex = this.bookmarks.findIndex(toot => this.focusedId === toot.uri)
const currentIndex = this.bookmarks.findIndex(
(toot) => this.focusedId === toot.uri
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
@ -186,8 +230,8 @@ export default {
this.focusedId = this.bookmarks[0].uri
break
}
}
}
},
},
}
</script>

View File

@ -1,9 +1,28 @@
<template>
<div id="directmessages" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
id="directmessages"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="timeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -21,13 +40,18 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -36,30 +60,32 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'directmessages',
components: { Toot },
mixins: [reloadable],
data() {
return {
focusedId: null,
scrollPosition: null,
observer: null,
scrollTime: null,
resizeTime: null
resizeTime: null,
ElIconArrowUp,
}
},
name: 'directmessages',
components: { Toot },
mixins: [reloadable],
computed: {
...mapState('TimelineSpace/Contents/DirectMessages', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
scrolling: state => state.scrolling
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
scrolling: (state) => state.scrolling,
}),
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: state => state.TimelineSpace.timelineSetting.unreadNotification
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: (state) =>
state.TimelineSpace.timelineSetting.unreadNotification,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
@ -70,16 +96,23 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
}
},
},
async mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadDirectMessagesTimeline', false)
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadDirectMessagesTimeline',
false
)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
if (!this.unreadNotification.direct) {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
await this.initialize().finally(_ => {
await this.initialize().finally((_) => {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
})
}
@ -97,18 +130,31 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling) {
if (
this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline && this.heading) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadDirectMessagesTimeline', false)
if (
this.$store.state.TimelineSpace.SideMenu.unreadDirectMessagesTimeline &&
this.heading
) {
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadDirectMessagesTimeline',
false
)
}
if (this.scrollPosition) {
this.scrollPosition.prepare()
@ -123,13 +169,21 @@ export default {
this.observer.disconnect()
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeHeading',
true
)
this.$store.commit('TimelineSpace/Contents/DirectMessages/archiveTimeline')
if (!this.unreadNotification.direct) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/clearTimeline')
}
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -143,20 +197,28 @@ export default {
},
focusedId: function (newState, _oldState) {
if (newState && this.heading) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeHeading',
false
)
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeHeading',
true
)
}
}
},
},
methods: {
async initialize() {
await this.$store.dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline').catch(_ => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/DirectMessages/fetchTimeline')
.catch((_) => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
await this.$store.dispatch('TimelineSpace/bindDirectMessagesStreaming')
this.$store.dispatch('TimelineSpace/startDirectMessagesStreaming')
},
@ -166,54 +228,82 @@ export default {
}
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
true
)
}
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/DirectMessages/lazyFetchTimeline', this.timeline[this.timeline.length - 1])
.then(statuses => {
.dispatch(
'TimelineSpace/Contents/DirectMessages/lazyFetchTimeline',
this.timeline[this.timeline.length - 1]
)
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
if (event.target.scrollTop > 10 && this.heading) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeHeading',
false
)
} else if (event.target.scrollTop <= 10 && !this.heading) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeHeading',
true
)
}
setTimeout(() => {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
false
)
}
}, 150)
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/DirectMessages/deleteToot', message.id)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/deleteToot',
message.id
)
},
async reload() {
this.$store.commit('TimelineSpace/changeLoading', true)
@ -228,19 +318,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -257,12 +355,18 @@ export default {
}
},
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/DirectMessages/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/DirectMessages/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -299,4 +403,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,9 +1,28 @@
<template>
<div id="favourites" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="favourites" :min-item-size="60" id="scroller" class="scroller" ref="scroller">
<div
id="favourites"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="favourites"
:min-item-size="60"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri === focusedId"
@ -20,50 +39,60 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import Toot from '~/src/renderer/components/organisms/Toot'
import reloadable from '~/src/renderer/components/mixins/reloadable'
import { Event } from '~/src/renderer/components/event'
export default {
name: 'favourites',
components: { Toot },
mixins: [reloadable],
data() {
return {
heading: true,
focusedId: null
focusedId: null,
ElIconArrowUp,
}
},
name: 'favourites',
components: { Toot },
mixins: [reloadable],
computed: {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
account: state => state.TimelineSpace.account,
favourites: state => state.TimelineSpace.Contents.Favourites.favourites,
lazyLoading: state => state.TimelineSpace.Contents.Favourites.lazyLoading
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
account: (state) => state.TimelineSpace.account,
favourites: (state) => state.TimelineSpace.Contents.Favourites.favourites,
lazyLoading: (state) =>
state.TimelineSpace.Contents.Favourites.lazyLoading,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
return !this.focusedId && !this.modalOpened
}
},
},
created() {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
this.$store
.dispatch('TimelineSpace/Contents/Favourites/fetchFavourites', this.account)
.dispatch(
'TimelineSpace/Contents/Favourites/fetchFavourites',
this.account
)
.catch(() => {
this.$message({
message: this.$t('message.favourite_fetch_error'),
type: 'error'
type: 'error',
})
})
.finally(() => {
@ -71,7 +100,9 @@ export default {
})
},
mounted() {
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
const previousFocusedId = this.focusedId
@ -86,8 +117,13 @@ export default {
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Favourites/updateFavourites', [])
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -105,26 +141,36 @@ export default {
} else if (newState === null && !this.heading) {
this.heading = true
}
}
},
},
methods: {
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/Favourites/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/Favourites/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/Favourites/deleteToot', message)
this.$store.commit(
'TimelineSpace/Contents/Favourites/deleteToot',
message
)
},
onScroll(event) {
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Favourites/lazyFetchFavourites', this.favourites[this.favourites.length - 1])
.dispatch(
'TimelineSpace/Contents/Favourites/lazyFetchFavourites',
this.favourites[this.favourites.length - 1]
)
.catch(() => {
this.$message({
message: this.$t('message.favourite_fetch_error'),
type: 'error'
type: 'error',
})
})
}
@ -139,12 +185,17 @@ export default {
this.$store.commit('TimelineSpace/changeLoading', true)
try {
const account = await this.reloadable()
await this.$store.dispatch('TimelineSpace/Contents/Favourites/fetchFavourites', account).catch(() => {
this.$message({
message: this.$t('message.favourite_fetch_error'),
type: 'error'
await this.$store
.dispatch(
'TimelineSpace/Contents/Favourites/fetchFavourites',
account
)
.catch(() => {
this.$message({
message: this.$t('message.favourite_fetch_error'),
type: 'error',
})
})
})
} finally {
this.$store.commit('TimelineSpace/changeLoading', false)
}
@ -154,7 +205,9 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.favourites.findIndex(toot => this.focusedId === toot.uri)
const currentIndex = this.favourites.findIndex(
(toot) => this.focusedId === toot.uri
)
if (currentIndex === -1) {
this.focusedId = this.favourites[0].uri
} else if (currentIndex < this.favourites.length) {
@ -162,7 +215,9 @@ export default {
}
},
focusPrev() {
const currentIndex = this.favourites.findIndex(toot => this.focusedId === toot.uri)
const currentIndex = this.favourites.findIndex(
(toot) => this.focusedId === toot.uri
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
@ -181,8 +236,8 @@ export default {
this.focusedId = this.favourites[0].uri
break
}
}
}
},
},
}
</script>

View File

@ -1,7 +1,12 @@
<template>
<div id="follow-requests">
<template v-for="account in requests">
<user :user="account" :request="true" @acceptRequest="accept" @rejectRequest="reject"></user>
<user
:user="account"
:request="true"
@acceptRequest="accept"
@rejectRequest="reject"
></user>
</template>
</div>
</template>
@ -15,39 +20,49 @@ export default {
components: { User },
computed: {
...mapState('TimelineSpace/Contents/FollowRequests', {
requests: state => state.requests
})
requests: (state) => state.requests,
}),
},
async mounted() {
await this.initialize()
},
methods: {
async initialize() {
await this.$store.dispatch('TimelineSpace/Contents/FollowRequests/fetchRequests').catch(_ => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/FollowRequests/fetchRequests')
.catch((_) => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
},
accept(account) {
this.$store.dispatch('TimelineSpace/Contents/FollowRequests/acceptRequest', account).catch(_ => {
this.$message({
message: this.$t('message.follow_request_accept_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/FollowRequests/acceptRequest',
account
)
.catch((_) => {
this.$message({
message: this.$t('message.follow_request_accept_error'),
type: 'error',
})
})
})
},
reject(account) {
this.$store.dispatch('TimelineSpace/Contents/FollowRequests/rejectRequest', account).catch(_ => {
this.$message({
message: this.$t('message.follow_request_reject_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/FollowRequests/rejectRequest',
account
)
.catch((_) => {
this.$message({
message: this.$t('message.follow_request_reject_error'),
type: 'error',
})
})
})
}
}
},
},
}
</script>
<style lang="scss" scorped></style>

View File

@ -19,7 +19,11 @@
/>
</div>
<div class="form-item" v-show="tagPage()">
<el-button type="text" @click="save" :title="$t('hashtag.save_tag')">
<el-button
type="text"
@click="save"
:title="$t('hashtag.save_tag')"
>
<font-awesome-icon icon="thumbtack" />
</el-button>
</div>
@ -35,7 +39,7 @@ export default {
name: 'hashtag',
data() {
return {
tag: ''
tag: '',
}
},
mounted() {
@ -48,7 +52,7 @@ export default {
if (route.name === 'tag') {
this.tag = route.params.tag
}
}
},
},
methods: {
id() {
@ -65,8 +69,8 @@ export default {
},
save() {
this.$store.dispatch('TimelineSpace/Contents/Hashtag/saveTag', this.tag)
}
}
},
},
}
</script>

View File

@ -2,12 +2,20 @@
<div id="list">
<table class="tag-list">
<tbody>
<tr v-for="tag in tags" v-bind:key="tag._id" @click.stop.prevent="openTimeline(tag.tagName)">
<tr
v-for="tag in tags"
v-bind:key="tag._id"
@click.stop.prevent="openTimeline(tag.tagName)"
>
<td>
{{ tag.tagName }}
</td>
<td class="action">
<el-button type="text" @click.stop="deleteTag(tag)" :title="$t('hashtag.delete_tag')">
<el-button
type="text"
@click.stop="deleteTag(tag)"
:title="$t('hashtag.delete_tag')"
>
<font-awesome-icon :icon="['far', 'trash-can']" />
</el-button>
</td>
@ -24,20 +32,22 @@ export default {
name: 'list',
computed: {
...mapState({
tags: state => state.TimelineSpace.Contents.Hashtag.List.tags
})
tags: (state) => state.TimelineSpace.Contents.Hashtag.List.tags,
}),
},
created() {
this.$store.dispatch('TimelineSpace/Contents/Hashtag/List/listTags')
},
methods: {
openTimeline(tagName) {
this.$router.push({ path: `/${this.$route.params.id}/hashtag/${tagName}` })
this.$router.push({
path: `/${this.$route.params.id}/hashtag/${tagName}`,
})
},
deleteTag(tag) {
this.$store.dispatch('TimelineSpace/Contents/Hashtag/List/removeTag', tag)
}
}
},
},
}
</script>

View File

@ -1,9 +1,29 @@
<template>
<div name="tag" class="tag-timeline" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
name="tag"
class="tag-timeline"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="timeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -21,13 +41,18 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -36,28 +61,30 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'tag',
components: { Toot },
mixins: [reloadable],
props: ['tag'],
data() {
return {
focusedId: null,
scrollPosition: null,
observer: null,
scrollTime: null,
resizeTime: null
resizeTime: null,
ElIconArrowUp,
}
},
name: 'tag',
components: { Toot },
mixins: [reloadable],
props: ['tag'],
computed: {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
timeline: state => state.TimelineSpace.Contents.Hashtag.Tag.timeline,
lazyLoading: state => state.TimelineSpace.Contents.Hashtag.Tag.lazyLoading,
heading: state => state.TimelineSpace.Contents.Hashtag.Tag.heading,
scrolling: state => state.TimelineSpace.Contents.Hashtag.Tag.scrolling
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
timeline: (state) => state.TimelineSpace.Contents.Hashtag.Tag.timeline,
lazyLoading: (state) =>
state.TimelineSpace.Contents.Hashtag.Tag.lazyLoading,
heading: (state) => state.TimelineSpace.Contents.Hashtag.Tag.heading,
scrolling: (state) => state.TimelineSpace.Contents.Hashtag.Tag.scrolling,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
@ -68,16 +95,20 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
}
},
},
mounted() {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
this.load(this.tag).finally(() => {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
})
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
@ -92,13 +123,20 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling) {
if (
this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
@ -123,11 +161,17 @@ export default {
},
focusedId: function (newState, _oldState) {
if (newState && this.heading) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeHeading',
false
)
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeHeading',
true
)
}
}
},
},
beforeDestroy() {
this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/stopStreaming')
@ -137,26 +181,38 @@ export default {
},
methods: {
async load(tag) {
await this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/fetch', tag).catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/Hashtag/Tag/fetch', tag)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/startStreaming', tag).catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error'
this.$store
.dispatch('TimelineSpace/Contents/Hashtag/Tag/startStreaming', tag)
.catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error',
})
})
})
return true
},
reset() {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeHeading',
true
)
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/archiveTimeline')
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/clearTimeline')
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -164,7 +220,10 @@ export default {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/updateToot', toot)
},
deleteToot(toot) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/deleteToot', toot.id)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/deleteToot',
toot.id
)
},
onScroll(event) {
if (moment().diff(this.resizeTime) < 500) {
@ -172,48 +231,67 @@ export default {
}
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
true
)
}
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Hashtag/Tag/lazyFetchTimeline', {
tag: this.tag,
status: this.timeline[this.timeline.length - 1]
status: this.timeline[this.timeline.length - 1],
})
.then(statuses => {
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
if (event.target.scrollTop > 10 && this.heading) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeHeading',
false
)
} else if (event.target.scrollTop <= 10 && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeHeading',
true
)
}
setTimeout(() => {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
false
)
}
}, 150)
},
@ -222,19 +300,25 @@ export default {
this.$store.commit('TimelineSpace/changeLoading', true)
try {
await this.reloadable()
await this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/stopStreaming')
await this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/fetch', tag).catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store.dispatch(
'TimelineSpace/Contents/Hashtag/Tag/stopStreaming'
)
await this.$store
.dispatch('TimelineSpace/Contents/Hashtag/Tag/fetch', tag)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
this.$store.dispatch('TimelineSpace/Contents/Hashtag/Tag/startStreaming', tag).catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error'
this.$store
.dispatch('TimelineSpace/Contents/Hashtag/Tag/startStreaming', tag)
.catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error',
})
})
})
} finally {
this.$store.commit('TimelineSpace/changeLoading', false)
}
@ -244,19 +328,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -273,12 +365,18 @@ export default {
}
},
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Hashtag/Tag/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Hashtag/Tag/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -306,4 +404,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,15 +1,45 @@
<template>
<div id="home" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="filteredTimeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
id="home"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="filteredTimeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<template v-if="item.id === 'loading-card'">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
<StatusLoading :since_id="item.since_id" :max_id="item.max_id" :loading="loadingMore" @load_since="fetchTimelineSince" />
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.id]"
:data-index="index"
:watchData="true"
>
<StatusLoading
:since_id="item.since_id"
:max_id="item.max_id"
:loading="loadingMore"
@load_since="fetchTimelineSince"
/>
</DynamicScrollerItem>
</template>
<template v-else>
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -29,13 +59,18 @@
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -45,9 +80,6 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'home',
components: { Toot, StatusLoading },
mixins: [reloadable],
data() {
return {
focusedId: null,
@ -55,22 +87,26 @@ export default {
observer: null,
scrollTime: null,
resizeTime: null,
loadingMore: false
loadingMore: false,
ElIconArrowUp,
}
},
name: 'home',
components: { Toot, StatusLoading },
mixins: [reloadable],
computed: {
...mapState('TimelineSpace/Contents/Home', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
showReblogs: state => state.showReblogs,
showReplies: state => state.showReplies,
scrolling: state => state.scrolling
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
showReblogs: (state) => state.showReblogs,
showReplies: (state) => state.showReplies,
scrolling: (state) => state.scrolling,
}),
...mapState({
backgroundColor: state => state.App.theme.background_color,
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
startReload: state => state.TimelineSpace.HeaderMenu.reload
backgroundColor: (state) => state.App.theme.background_color,
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
...mapGetters('TimelineSpace/Contents/Home', ['filters']),
@ -82,11 +118,13 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
},
filteredTimeline() {
return this.timeline.filter(toot => {
return this.timeline.filter((toot) => {
if (toot.in_reply_to_id) {
return this.showReplies
} else if (toot.reblog) {
@ -95,11 +133,13 @@ export default {
return true
}
})
}
},
},
mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadHomeTimeline', false)
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
const previousFocusedId = this.focusedId
@ -117,18 +157,32 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.loadingMore || (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling)) {
if (
this.loadingMore ||
(this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling)
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadHomeTimeline && this.heading) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadHomeTimeline', false)
if (
this.$store.state.TimelineSpace.SideMenu.unreadHomeTimeline &&
this.heading
) {
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadHomeTimeline',
false
)
}
if (this.scrollPosition) {
this.scrollPosition.prepare()
@ -141,8 +195,13 @@ export default {
destroyed() {
this.$store.commit('TimelineSpace/Contents/Home/changeHeading', true)
this.$store.commit('TimelineSpace/Contents/Home/archiveTimeline')
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -165,7 +224,7 @@ export default {
if (this.heading && newState.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Home/saveMarker')
}
}
},
},
methods: {
onScroll(event) {
@ -179,26 +238,36 @@ export default {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Home/lazyFetchTimeline', this.timeline[this.timeline.length - 1])
.then(statuses => {
.dispatch(
'TimelineSpace/Contents/Home/lazyFetchTimeline',
this.timeline[this.timeline.length - 1]
)
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Home/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Home/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Home/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Home/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
@ -213,7 +282,10 @@ export default {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Home/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Home/changeScrolling',
false
)
}
}, 150)
},
@ -225,11 +297,13 @@ export default {
},
fetchTimelineSince(since_id) {
this.loadingMore = true
this.$store.dispatch('TimelineSpace/Contents/Home/fetchTimelineSince', since_id).finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
this.$store
.dispatch('TimelineSpace/Contents/Home/fetchTimelineSince', since_id)
.finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
},
async reload() {
this.$store.commit('TimelineSpace/changeLoading', true)
@ -244,19 +318,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -277,8 +359,8 @@ export default {
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Home/changeScrolling', false)
}, 500)
}
}
},
},
}
</script>
@ -315,4 +397,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -6,7 +6,11 @@
</el-button>
</div>
<template v-for="account in members">
<user :user="account" :remove="true" @removeAccount="removeAccount"></user>
<user
:user="account"
:remove="true"
@removeAccount="removeAccount"
></user>
</template>
</div>
</template>
@ -21,8 +25,8 @@ export default {
components: { User },
computed: {
...mapState({
members: state => state.TimelineSpace.Contents.Lists.Edit.members
})
members: (state) => state.TimelineSpace.Contents.Lists.Edit.members,
}),
},
created() {
this.init()
@ -31,11 +35,14 @@ export default {
async init() {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.list_id)
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Edit/fetchMembers',
this.list_id
)
} catch (err) {
this.$message({
message: this.$t('message.members_fetch_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
@ -44,25 +51,37 @@ export default {
async removeAccount(account) {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/removeAccount', {
account: account,
listId: this.list_id
})
await this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.list_id)
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Edit/removeAccount',
{
account: account,
listId: this.list_id,
}
)
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Edit/fetchMembers',
this.list_id
)
} catch (err) {
this.$message({
message: this.$t('message.remove_user_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
}
},
addAccount() {
this.$store.commit('TimelineSpace/Modals/AddListMember/setListId', this.list_id)
this.$store.dispatch('TimelineSpace/Modals/AddListMember/changeModal', true)
}
}
this.$store.commit(
'TimelineSpace/Modals/AddListMember/setListId',
this.list_id
)
this.$store.dispatch(
'TimelineSpace/Modals/AddListMember/changeModal',
true
)
},
},
}
</script>

View File

@ -1,8 +1,16 @@
<template>
<div id="lists">
<div class="new-list" v-loading="creating" :element-loading-background="loadingBackground">
<div
class="new-list"
v-loading="creating"
:element-loading-background="loadingBackground"
>
<el-form :inline="true">
<input v-model="title" :placeholder="$t('lists.index.new_list')" class="list-title" />
<input
v-model="title"
:placeholder="$t('lists.index.new_list')"
class="list-title"
/>
<el-button type="text" class="create" @click="createList">
<font-awesome-icon icon="plus" />
</el-button>
@ -32,14 +40,14 @@ export default {
data() {
return {
title: '',
creating: false
creating: false,
}
},
computed: {
...mapState({
lists: state => state.TimelineSpace.Contents.Lists.Index.lists,
loadingBackground: state => state.App.theme.wrapper_mask_color
})
lists: (state) => state.TimelineSpace.Contents.Lists.Index.lists,
loadingBackground: (state) => state.App.theme.wrapper_mask_color,
}),
},
created() {
this.$store.commit('TimelineSpace/changeLoading', true)
@ -52,22 +60,29 @@ export default {
return this.$route.params.id
},
fetch() {
return this.$store.dispatch('TimelineSpace/Contents/Lists/Index/fetchLists').catch(() => {
this.$message({
message: this.$t('message.lists_fetch_error'),
type: 'error'
return this.$store
.dispatch('TimelineSpace/Contents/Lists/Index/fetchLists')
.catch(() => {
this.$message({
message: this.$t('message.lists_fetch_error'),
type: 'error',
})
})
})
},
async createList() {
this.creating = true
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Index/createList', this.title)
await this.$store.dispatch('TimelineSpace/Contents/Lists/Index/fetchLists')
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Index/createList',
this.title
)
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Index/fetchLists'
)
} catch (err) {
this.$message({
message: this.$t('message.list_create_error'),
type: 'error'
type: 'error',
})
} finally {
this.creating = false
@ -78,17 +93,24 @@ export default {
return this.$router.push(`/${this.id()}/lists/${list.id}/edit`)
},
del(list) {
this.$confirm(this.$t('lists.index.delete.confirm.message'), this.$t('lists.index.delete.confirm.title'), {
confirmButtonText: this.$t('lists.index.delete.confirm.ok'),
cancelButtonText: this.$t('lists.index.delete.confirm.cancel'),
type: 'warning'
})
this.$confirm(
this.$t('lists.index.delete.confirm.message'),
this.$t('lists.index.delete.confirm.title'),
{
confirmButtonText: this.$t('lists.index.delete.confirm.ok'),
cancelButtonText: this.$t('lists.index.delete.confirm.cancel'),
type: 'warning',
}
)
.then(() => {
this.$store.dispatch('TimelineSpace/Contents/Lists/Index/deleteList', list)
this.$store.dispatch(
'TimelineSpace/Contents/Lists/Index/deleteList',
list
)
})
.catch(() => {})
}
}
},
},
}
</script>

View File

@ -1,9 +1,29 @@
<template>
<div name="list" class="list-timeline" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
name="list"
class="list-timeline"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="timeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -21,13 +41,18 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -36,28 +61,30 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'list',
props: ['list_id'],
components: { Toot },
mixins: [reloadable],
data() {
return {
focusedId: null,
scrollPosition: null,
observer: null,
scrollTime: null,
resizeTime: null
resizeTime: null,
ElIconArrowUp,
}
},
name: 'list',
props: ['list_id'],
components: { Toot },
mixins: [reloadable],
computed: {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
timeline: state => state.TimelineSpace.Contents.Lists.Show.timeline,
lazyLoading: state => state.TimelineSpace.Contents.Lists.Show.lazyLoading,
heading: state => state.TimelineSpace.Contents.Lists.Show.heading,
scrolling: state => state.TimelineSpace.Contents.Lists.Show.scrolling
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
timeline: (state) => state.TimelineSpace.Contents.Lists.Show.timeline,
lazyLoading: (state) =>
state.TimelineSpace.Contents.Lists.Show.lazyLoading,
heading: (state) => state.TimelineSpace.Contents.Lists.Show.heading,
scrolling: (state) => state.TimelineSpace.Contents.Lists.Show.scrolling,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
@ -68,9 +95,11 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
}
},
},
created() {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
@ -79,7 +108,9 @@ export default {
})
},
mounted() {
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
const previousFocusedId = this.focusedId
@ -93,13 +124,20 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling) {
if (
this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
@ -123,11 +161,17 @@ export default {
},
focusedId: function (newState, _oldState) {
if (newState && this.heading) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeHeading',
false
)
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeHeading',
true
)
}
}
},
},
beforeDestroy() {
this.$store.dispatch('TimelineSpace/Contents/Lists/Show/stopStreaming')
@ -137,35 +181,56 @@ export default {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeHeading', true)
this.$store.commit('TimelineSpace/Contents/Lists/Show/archiveTimeline')
this.$store.commit('TimelineSpace/Contents/Lists/Show/clearTimeline')
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
methods: {
async load() {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Show/stopStreaming')
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Show/stopStreaming'
)
try {
await this.$store.dispatch('TimelineSpace/Contents/Lists/Show/fetchTimeline', this.list_id)
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Show/fetchTimeline',
this.list_id
)
} catch (err) {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
}
this.$store.dispatch('TimelineSpace/Contents/Lists/Show/startStreaming', this.list_id).catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/Lists/Show/startStreaming',
this.list_id
)
.catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error',
})
})
})
return 'started'
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/deleteToot', message.id)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/deleteToot',
message.id
)
},
onScroll(event) {
if (moment().diff(this.resizeTime) < 500) {
@ -173,48 +238,67 @@ export default {
}
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
true
)
}
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Lists/Show/lazyFetchTimeline', {
list_id: this.list_id,
status: this.timeline[this.timeline.length - 1]
status: this.timeline[this.timeline.length - 1],
})
.then(statuses => {
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
if (event.target.scrollTop > 10 && this.heading) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeHeading',
false
)
} else if (event.target.scrollTop <= 10 && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeHeading',
true
)
}
setTimeout(() => {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
false
)
}
}, 150)
},
@ -222,19 +306,31 @@ export default {
this.$store.commit('TimelineSpace/changeLoading', true)
try {
await this.reloadable()
await this.$store.dispatch('TimelineSpace/Contents/Lists/Show/stopStreaming')
await this.$store.dispatch('TimelineSpace/Contents/Lists/Show/fetchTimeline', this.list_id).catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store.dispatch(
'TimelineSpace/Contents/Lists/Show/stopStreaming'
)
await this.$store
.dispatch(
'TimelineSpace/Contents/Lists/Show/fetchTimeline',
this.list_id
)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
this.$store.dispatch('TimelineSpace/Contents/Lists/Show/startStreaming', this.list_id).catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/Lists/Show/startStreaming',
this.list_id
)
.catch(() => {
this.$message({
message: this.$t('message.start_streaming_error'),
type: 'error',
})
})
})
} finally {
this.$store.commit('TimelineSpace/changeLoading', false)
}
@ -244,19 +340,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -273,12 +377,18 @@ export default {
}
},
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Lists/Show/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Lists/Show/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -306,4 +416,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,9 +1,28 @@
<template>
<div id="local" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
id="local"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="timeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -21,13 +40,18 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -36,30 +60,31 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'local',
components: { Toot },
mixins: [reloadable],
data() {
return {
focusedId: null,
scrollPosition: null,
observer: null,
scrollTime: null,
resizeTime: null
resizeTime: null,
ElIconArrowUp,
}
},
name: 'local',
components: { Toot },
mixins: [reloadable],
computed: {
...mapState('TimelineSpace/Contents/Local', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
scrolling: state => state.scrolling
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
scrolling: (state) => state.scrolling,
}),
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: state => state.TimelineSpace.unreadNotification
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: (state) => state.TimelineSpace.unreadNotification,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
@ -70,16 +95,23 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
}
},
},
async mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadLocalTimeline', false)
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadLocalTimeline',
false
)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
if (!this.unreadNotification.local) {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
await this.initialize().finally(_ => {
await this.initialize().finally((_) => {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
})
}
@ -97,18 +129,31 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling) {
if (
this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadLocalTimeline && this.heading) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadLocalTimeline', false)
if (
this.$store.state.TimelineSpace.SideMenu.unreadLocalTimeline &&
this.heading
) {
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadLocalTimeline',
false
)
}
if (this.scrollPosition) {
this.scrollPosition.prepare()
@ -128,8 +173,13 @@ export default {
if (!this.unreadNotification.local) {
this.$store.commit('TimelineSpace/Contents/Local/clearTimeline')
}
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -147,16 +197,18 @@ export default {
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Local/changeHeading', true)
}
}
},
},
methods: {
async initialize() {
await this.$store.dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline').catch(_ => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/Local/fetchLocalTimeline')
.catch((_) => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
await this.$store.dispatch('TimelineSpace/bindLocalStreaming')
this.$store.dispatch('TimelineSpace/startLocalStreaming')
},
@ -176,26 +228,36 @@ export default {
}
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Local/lazyFetchTimeline', this.timeline[this.timeline.length - 1])
.then(statuses => {
.dispatch(
'TimelineSpace/Contents/Local/lazyFetchTimeline',
this.timeline[this.timeline.length - 1]
)
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Local/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Local/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Local/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Local/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
@ -209,7 +271,10 @@ export default {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Local/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Local/changeScrolling',
false
)
}
}, 150)
},
@ -226,19 +291,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -257,10 +330,13 @@ export default {
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/Local/changeScrolling', true)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Local/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Local/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -296,4 +372,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,15 +1,45 @@
<template>
<div id="mentions" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="mentions" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
id="mentions"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="mentions"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<template v-if="item.id === 'loading-card'">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
<StatusLoading :since_id="item.since_id" :max_id="item.max_id" :loading="loadingMore" @load_since="fetchMentionsSince" />
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.id]"
:data-index="index"
:watchData="true"
>
<StatusLoading
:since_id="item.since_id"
:max_id="item.max_id"
:loading="loadingMore"
@load_since="fetchMentionsSince"
/>
</DynamicScrollerItem>
</template>
<template v-else>
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.url]"
:data-index="index"
:watchData="true"
>
<notification
:message="item"
:focused="item.id === focusedId"
@ -27,13 +57,18 @@
</template>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Notification from '~/src/renderer/components/organisms/Notification'
@ -43,9 +78,6 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'mentions',
components: { Notification, StatusLoading },
mixins: [reloadable],
data() {
return {
focusedId: null,
@ -53,23 +85,27 @@ export default {
observer: null,
scrollTime: null,
resizeTime: null,
loadingMore: false
loadingMore: false,
ElIconArrowUp,
}
},
name: 'mentions',
components: { Notification, StatusLoading },
mixins: [reloadable],
computed: {
...mapState('App', {
backgroundColor: state => state.theme.background_color
backgroundColor: (state) => state.theme.background_color,
}),
...mapState('TimelineSpace/HeaderMenu', {
startReload: state => state.reload
startReload: (state) => state.reload,
}),
...mapState('TimelineSpace/Contents/SideBar', {
openSideBar: state => state.openSideBar
openSideBar: (state) => state.openSideBar,
}),
...mapState('TimelineSpace/Contents/Mentions', {
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
scrolling: state => state.scrolling
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
scrolling: (state) => state.scrolling,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
...mapGetters('TimelineSpace/Contents/Mentions', ['mentions']),
@ -81,13 +117,17 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.mentions.findIndex(toot => this.focusedId === toot.id)
const currentIndex = this.mentions.findIndex(
(toot) => this.focusedId === toot.id
)
return currentIndex === -1
}
},
},
mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadMentions', false)
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
const previousFocusedId = this.focusedId
@ -105,17 +145,28 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.loadingMore || (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling)) {
if (
this.loadingMore ||
(this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling)
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadMentions && this.heading) {
if (
this.$store.state.TimelineSpace.SideMenu.unreadMentions &&
this.heading
) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadMentions', false)
}
if (this.scrollPosition) {
@ -129,8 +180,13 @@ export default {
destroyed() {
this.$store.commit('TimelineSpace/Contents/Mentions/changeHeading', true)
this.$store.commit('TimelineSpace/Contents/Mentions/archiveMentions')
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -144,16 +200,22 @@ export default {
},
focusedId: function (newState, _oldState) {
if (newState && this.heading) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeHeading',
false
)
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeHeading',
true
)
}
},
mentions: function (newState, _oldState) {
if (this.heading && newState.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Mentions/saveMarker')
}
}
},
},
methods: {
onScroll(event) {
@ -162,39 +224,58 @@ export default {
}
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
true
)
}
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Mentions/lazyFetchMentions', this.mentions[this.mentions.length - 1])
.then(statuses => {
.dispatch(
'TimelineSpace/Contents/Mentions/lazyFetchMentions',
this.mentions[this.mentions.length - 1]
)
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
if (event.target.scrollTop > 10 && this.heading) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeHeading',
false
)
} else if (event.target.scrollTop <= 10 && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Mentions/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeHeading',
true
)
this.$store.dispatch('TimelineSpace/Contents/Mentions/saveMarker')
}
@ -202,17 +283,25 @@ export default {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
false
)
}
}, 150)
},
fetchMentionsSince(since_id) {
this.loadingMore = true
this.$store.dispatch('TimelineSpace/Contents/Mentions/fetchMentionsSince', since_id).finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
this.$store
.dispatch(
'TimelineSpace/Contents/Mentions/fetchMentionsSince',
since_id
)
.finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
},
async reload() {
this.$store.commit('TimelineSpace/changeLoading', true)
@ -230,7 +319,9 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.mentions.findIndex(toot => this.focusedId === toot.id)
const currentIndex = this.mentions.findIndex(
(toot) => this.focusedId === toot.id
)
if (currentIndex === -1) {
this.focusedId = this.mentions[0].id
} else if (currentIndex < this.mentions.length) {
@ -238,7 +329,9 @@ export default {
}
},
focusPrev() {
const currentIndex = this.mentions.findIndex(toot => this.focusedId === toot.id)
const currentIndex = this.mentions.findIndex(
(toot) => this.focusedId === toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
@ -259,12 +352,18 @@ export default {
}
},
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Mentions/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Mentions/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -300,4 +399,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,15 +1,45 @@
<template>
<div id="notifications" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="handledNotifications" :min-item-size="20" id="scroller" class="scroller" ref="scroller">
<div
id="notifications"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="handledNotifications"
:min-item-size="20"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<template v-if="item.id === 'loading-card'">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.id]" :data-index="index" :watchData="true">
<StatusLoading :since_id="item.since_id" :max_id="item.max_id" :loading="loadingMore" @load_since="fetchNotificationsSince" />
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.id]"
:data-index="index"
:watchData="true"
>
<StatusLoading
:since_id="item.since_id"
:max_id="item.max_id"
:loading="loadingMore"
@load_since="fetchNotificationsSince"
/>
</DynamicScrollerItem>
</template>
<template v-else>
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.url]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.url]"
:data-index="index"
:watchData="true"
>
<notification
:message="item"
:focused="item.id === focusedId"
@ -26,13 +56,18 @@
</template>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Notification from '~/src/renderer/components/organisms/Notification'
@ -42,9 +77,6 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'notifications',
components: { Notification, StatusLoading },
mixins: [reloadable],
data() {
return {
focusedId: null,
@ -52,22 +84,29 @@ export default {
observer: null,
scrollTime: null,
resizeTime: null,
loadingMore: false
loadingMore: false,
ElIconArrowUp,
}
},
name: 'notifications',
components: { Notification, StatusLoading },
mixins: [reloadable],
computed: {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
backgroundColor: state => state.App.theme.background_color
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
backgroundColor: (state) => state.App.theme.background_color,
}),
...mapState('TimelineSpace/Contents/Notifications', {
notifications: state => state.notifications,
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
scrolling: state => state.scrolling
notifications: (state) => state.notifications,
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
scrolling: (state) => state.scrolling,
}),
...mapGetters('TimelineSpace/Contents/Notifications', ['handledNotifications', 'filters']),
...mapGetters('TimelineSpace/Contents/Notifications', [
'handledNotifications',
'filters',
]),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
shortcutEnabled: function () {
if (this.modalOpened) {
@ -77,14 +116,21 @@ export default {
return true
}
// Sometimes notifications are deleted, so perhaps focused notification don't exist.
const currentIndex = this.handledNotifications.findIndex(notification => this.focusedId === notification.id)
const currentIndex = this.handledNotifications.findIndex(
(notification) => this.focusedId === notification.id
)
return currentIndex === -1
}
},
},
mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadNotifications', false)
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadNotifications',
false
)
this.$store.dispatch('TimelineSpace/Contents/Notifications/resetBadge')
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-timeline', () => {
// If focusedId does not change, we have to refresh focusedId because Toot component watch change events.
@ -103,18 +149,32 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.loadingMore || (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling)) {
if (
this.loadingMore ||
(this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling)
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadNotifications && this.heading) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadNotifications', false)
if (
this.$store.state.TimelineSpace.SideMenu.unreadNotifications &&
this.heading
) {
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadNotifications',
false
)
}
if (this.scrollPosition) {
this.scrollPosition.prepare()
@ -125,10 +185,20 @@ export default {
this.observer.disconnect()
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Notifications/changeHeading', true)
this.$store.commit('TimelineSpace/Contents/Notifications/archiveNotifications')
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeHeading',
true
)
this.$store.commit(
'TimelineSpace/Contents/Notifications/archiveNotifications'
)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -142,9 +212,15 @@ export default {
},
focusedId: function (newState, _oldState) {
if (newState >= 0 && this.heading) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeHeading',
false
)
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeHeading',
true
)
this.$store.dispatch('TimelineSpace/Contents/Notifications/resetBadge')
}
},
@ -152,7 +228,7 @@ export default {
if (this.heading && newState.length > 0) {
this.$store.dispatch('TimelineSpace/Contents/Notifications/saveMarker')
}
}
},
},
methods: {
onScroll(event) {
@ -161,11 +237,15 @@ export default {
}
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeScrolling',
true
)
}
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
@ -173,29 +253,41 @@ export default {
'TimelineSpace/Contents/Notifications/lazyFetchNotifications',
this.handledNotifications[this.handledNotifications.length - 1]
)
.then(statuses => {
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Notifications/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.notification_fetch_error'),
type: 'error'
type: 'error',
})
})
}
if (event.target.scrollTop > 10 && this.heading) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeHeading', false)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeHeading',
false
)
} else if (event.target.scrollTop <= 10 && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Notifications/changeHeading', true)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeHeading',
true
)
this.$store.dispatch('TimelineSpace/Contents/Notifications/resetBadge')
this.$store.dispatch('TimelineSpace/Contents/Notifications/saveMarker')
}
@ -204,17 +296,25 @@ export default {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Notifications/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Notifications/changeScrolling',
false
)
}
}, 150)
},
fetchNotificationsSince(since_id) {
this.loadingMore = true
this.$store.dispatch('TimelineSpace/Contents/Notifications/fetchNotificationsSince', since_id).finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
this.$store
.dispatch(
'TimelineSpace/Contents/Notifications/fetchNotificationsSince',
since_id
)
.finally(() => {
setTimeout(() => {
this.loadingMore = false
}, 500)
})
},
async reload() {
this.$store.commit('TimelineSpace/changeLoading', true)
@ -226,14 +326,19 @@ export default {
}
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/Notifications/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/Notifications/updateToot',
message
)
},
upper() {
this.$refs.scroller.scrollToItem(0)
this.focusedId = null
},
focusNext() {
const currentIndex = this.handledNotifications.findIndex(notification => this.focusedId === notification.id)
const currentIndex = this.handledNotifications.findIndex(
(notification) => this.focusedId === notification.id
)
if (currentIndex === -1) {
this.focusedId = this.handledNotifications[0].id
} else if (currentIndex < this.handledNotifications.length) {
@ -241,7 +346,9 @@ export default {
}
},
focusPrev() {
const currentIndex = this.handledNotifications.findIndex(notification => this.focusedId === notification.id)
const currentIndex = this.handledNotifications.findIndex(
(notification) => this.focusedId === notification.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
@ -260,8 +367,8 @@ export default {
this.focusedId = this.handledNotifications[0].id
break
}
}
}
},
},
}
</script>
@ -297,4 +404,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -1,9 +1,28 @@
<template>
<div id="public" v-shortkey="shortcutEnabled ? { next: ['j'] } : {}" @shortkey="handleKey">
<div v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }" @shortkey="reload()"></div>
<DynamicScroller :items="timeline" :min-item-size="86" id="scroller" class="scroller" ref="scroller">
<div
id="public"
v-shortkey="shortcutEnabled ? { next: ['j'] } : {}"
@shortkey="handleKey"
>
<div
v-shortkey="{ linux: ['ctrl', 'r'], mac: ['meta', 'r'] }"
@shortkey="reload()"
></div>
<DynamicScroller
:items="timeline"
:min-item-size="86"
id="scroller"
class="scroller"
ref="scroller"
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:focused="item.uri + item.id === focusedId"
@ -21,13 +40,18 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div :class="openSideBar ? 'upper-with-side-bar' : 'upper'" v-show="!heading">
<el-button type="primary" icon="el-icon-arrow-up" @click="upper" circle> </el-button>
<div
:class="openSideBar ? 'upper-with-side-bar' : 'upper'"
v-show="!heading"
>
<el-button type="primary" :icon="ElIconArrowUp" @click="upper" circle>
</el-button>
</div>
</div>
</template>
<script>
import { ArrowUp as ElIconArrowUp } from '@element-plus/icons'
import { mapState, mapGetters } from 'vuex'
import moment from 'moment'
import Toot from '~/src/renderer/components/organisms/Toot'
@ -36,30 +60,32 @@ import { Event } from '~/src/renderer/components/event'
import { ScrollPosition } from '~/src/renderer/components/utils/scroll'
export default {
name: 'public',
components: { Toot },
mixins: [reloadable],
data() {
return {
focusedId: null,
scrollPosition: null,
observer: null,
scrollTime: null,
resizeTime: null
resizeTime: null,
ElIconArrowUp,
}
},
name: 'public',
components: { Toot },
mixins: [reloadable],
computed: {
...mapState('TimelineSpace/Contents/Public', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading,
heading: state => state.heading,
scrolling: state => state.scrolling
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
heading: (state) => state.heading,
scrolling: (state) => state.scrolling,
}),
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: state => state.App.theme.background_color,
startReload: state => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: state => state.TimelineSpace.timelineSetting.unreadNotification
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
backgroundColor: (state) => state.App.theme.background_color,
startReload: (state) => state.TimelineSpace.HeaderMenu.reload,
unreadNotification: (state) =>
state.TimelineSpace.timelineSetting.unreadNotification,
}),
...mapGetters('TimelineSpace/Contents/Public', ['filters']),
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
@ -71,16 +97,23 @@ export default {
return true
}
// Sometimes toots are deleted, so perhaps focused toot don't exist.
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
return currentIndex === -1
}
},
},
async mounted() {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadPublicTimeline', false)
document.getElementById('scroller').addEventListener('scroll', this.onScroll)
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadPublicTimeline',
false
)
document
.getElementById('scroller')
.addEventListener('scroll', this.onScroll)
if (!this.unreadNotification.public) {
this.$store.commit('TimelineSpace/Contents/changeLoading', true)
await this.initialize().finally(_ => {
await this.initialize().finally((_) => {
this.$store.commit('TimelineSpace/Contents/changeLoading', false)
})
}
@ -98,18 +131,31 @@ export default {
this.scrollPosition.prepare()
this.observer = new ResizeObserver(() => {
if (this.scrollPosition && !this.heading && !this.lazyLoading && !this.scrolling) {
if (
this.scrollPosition &&
!this.heading &&
!this.lazyLoading &&
!this.scrolling
) {
this.resizeTime = moment()
this.scrollPosition.restore()
}
})
const scrollWrapper = el.getElementsByClassName('vue-recycle-scroller__item-wrapper')[0]
const scrollWrapper = el.getElementsByClassName(
'vue-recycle-scroller__item-wrapper'
)[0]
this.observer.observe(scrollWrapper)
},
beforeUpdate() {
if (this.$store.state.TimelineSpace.SideMenu.unreadPublicTimeline && this.heading) {
this.$store.commit('TimelineSpace/SideMenu/changeUnreadPublicTimeline', false)
if (
this.$store.state.TimelineSpace.SideMenu.unreadPublicTimeline &&
this.heading
) {
this.$store.commit(
'TimelineSpace/SideMenu/changeUnreadPublicTimeline',
false
)
}
if (this.scrollPosition) {
this.scrollPosition.prepare()
@ -129,8 +175,13 @@ export default {
if (!this.unreadNotification.public) {
this.$store.commit('TimelineSpace/Contents/Public/clearTimeline')
}
if (document.getElementById('scroller') !== undefined && document.getElementById('scroller') !== null) {
document.getElementById('scroller').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('scroller') !== undefined &&
document.getElementById('scroller') !== null
) {
document
.getElementById('scroller')
.removeEventListener('scroll', this.onScroll)
document.getElementById('scroller').scrollTop = 0
}
},
@ -148,16 +199,18 @@ export default {
} else if (newState === null && !this.heading) {
this.$store.commit('TimelineSpace/Contents/Public/changeHeading', true)
}
}
},
},
methods: {
async initialize() {
await this.$store.dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline').catch(_ => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
await this.$store
.dispatch('TimelineSpace/Contents/Public/fetchPublicTimeline')
.catch((_) => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
await this.$store.dispatch('TimelineSpace/bindPublicStreaming')
this.$store.dispatch('TimelineSpace/startPublicStreaming')
},
@ -174,30 +227,43 @@ export default {
this.scrollTime = moment()
if (!this.scrolling) {
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Public/changeScrolling',
true
)
}
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('scroller').scrollHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('scroller').scrollHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/Public/lazyFetchTimeline', this.timeline[this.timeline.length - 1])
.then(statuses => {
.dispatch(
'TimelineSpace/Contents/Public/lazyFetchTimeline',
this.timeline[this.timeline.length - 1]
)
.then((statuses) => {
if (statuses === null) {
return
}
if (statuses.length > 0) {
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', true)
this.$store.commit(
'TimelineSpace/Contents/Public/changeScrolling',
true
)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Public/changeScrolling',
false
)
}, 500)
}
})
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
@ -212,7 +278,10 @@ export default {
const now = moment()
if (now.diff(this.scrollTime) >= 150) {
this.scrollTime = null
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Public/changeScrolling',
false
)
}
}, 150)
},
@ -229,19 +298,27 @@ export default {
this.focusedId = null
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === 0) {
this.focusedId = null
} else if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -260,10 +337,13 @@ export default {
sizeChanged() {
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', true)
setTimeout(() => {
this.$store.commit('TimelineSpace/Contents/Public/changeScrolling', false)
this.$store.commit(
'TimelineSpace/Contents/Public/changeScrolling',
false
)
}, 500)
}
}
},
},
}
</script>
@ -299,4 +379,5 @@ export default {
}
}
</style>
<style lang="scss" src="@/assets/timeline-transition.scss"></style>

View File

@ -2,8 +2,18 @@
<div id="search">
<div class="search-header">
<el-form :inline="true">
<el-select v-model="target" :placeholder="$t('search.search')" class="search-target">
<el-option v-for="item in searchTargets" :key="item.target" :label="item.label" :value="item.target"> </el-option>
<el-select
v-model="target"
:placeholder="$t('search.search')"
class="search-target"
>
<el-option
v-for="item in searchTargets"
:key="item.target"
:label="item.label"
:value="item.target"
>
</el-option>
</el-select>
<input
v-model="query"
@ -35,7 +45,7 @@ export default {
data() {
return {
target: 'account',
query: ''
query: '',
}
},
computed: {
@ -44,52 +54,64 @@ export default {
return [
{
target: 'account',
label: this.$t('search.account')
label: this.$t('search.account'),
},
{
target: 'tag',
label: this.$t('search.tag')
label: this.$t('search.tag'),
},
{
target: 'toot',
label: this.$t('search.toot')
}
label: this.$t('search.toot'),
},
]
}
}
},
},
},
methods: {
search() {
switch (this.target) {
case 'account':
this.$store.dispatch('TimelineSpace/Contents/Search/Account/search', this.query).catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/Search/Account/search',
this.query
)
.catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error',
})
})
})
break
case 'tag':
this.$store.dispatch('TimelineSpace/Contents/Search/Tag/search', `#${this.query}`).catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/Search/Tag/search',
`#${this.query}`
)
.catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error',
})
})
})
break
case 'toot':
this.$store.dispatch('TimelineSpace/Contents/Search/Toots/search', this.query).catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error'
this.$store
.dispatch('TimelineSpace/Contents/Search/Toots/search', this.query)
.catch(() => {
this.$message({
message: this.$t('message.search_error'),
type: 'error',
})
})
})
break
default:
break
}
}
}
},
},
}
</script>

View File

@ -1,8 +1,19 @@
<template>
<div id="search_account">
<DynamicScroller :items="results" :min-item-size="20" class="scroller" page-mode>
<DynamicScroller
:items="results"
:min-item-size="20"
class="scroller"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.acct]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.acct]"
:data-index="index"
:watchData="true"
>
<user :user="item"></user>
</DynamicScrollerItem>
</template>
@ -19,13 +30,14 @@ export default {
components: { User },
computed: {
...mapState({
results: state => state.TimelineSpace.Contents.Search.Account.results
})
results: (state) => state.TimelineSpace.Contents.Search.Account.results,
}),
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Search/Account/updateResults', [])
}
this.$store.commit(
'TimelineSpace/Contents/Search/Account/updateResults',
[]
)
},
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,8 +1,20 @@
<template>
<div id="search_tag">
<DynamicScroller :items="results" :min-item-size="46" key-field="name" class="scroller" page-mode>
<DynamicScroller
:items="results"
:min-item-size="46"
key-field="name"
class="scroller"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.name]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.name]"
:data-index="index"
:watchData="true"
>
<tag :tag="item"></tag>
</DynamicScrollerItem>
</template>
@ -19,11 +31,11 @@ export default {
components: { Tag },
computed: {
...mapState('TimelineSpace/Contents/Search/Tag', {
results: state => state.results
})
results: (state) => state.results,
}),
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Search/Tag/updateResults', [])
}
},
}
</script>

View File

@ -1,8 +1,19 @@
<template>
<div id="search_account">
<DynamicScroller :items="results" :min-item-size="60" class="scroller" page-mode>
<DynamicScroller
:items="results"
:min-item-size="60"
class="scroller"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot :message="item"></toot>
</DynamicScrollerItem>
</template>
@ -19,13 +30,11 @@ export default {
components: { Toot },
computed: {
...mapState({
results: state => state.TimelineSpace.Contents.Search.Toots.results
})
results: (state) => state.TimelineSpace.Contents.Search.Toots.results,
}),
},
destroyed() {
this.$store.commit('TimelineSpace/Contents/Search/Toots/updateResults', [])
}
},
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,12 +1,20 @@
<template>
<div class="side-bar" v-if="openSideBar" v-shortkey="shortcutEnabled ? { close: ['esc'] } : {}" @shortkey="handleKey">
<div
class="side-bar"
v-if="openSideBar"
v-shortkey="shortcutEnabled ? { close: ['esc'] } : {}"
@shortkey="handleKey"
>
<div class="header">
<i class="el-icon-loading" v-show="loading"></i>
<i class="el-icon-refresh" @click="reload"></i>
<i class="el-icon-close" @click="close"></i>
<el-icon><el-icon-loading /></el-icon>
<el-icon><el-icon-refresh /></el-icon>
<el-icon><el-icon-close /></el-icon>
</div>
<div id="sidebar_scrollable">
<account-profile v-if="component === 1" v-on:change-loading="changeLoading"></account-profile>
<account-profile
v-if="component === 1"
v-on:change-loading="changeLoading"
></account-profile>
<toot-detail v-else-if="component === 2"></toot-detail>
<div
class="loading"
@ -21,36 +29,44 @@
</template>
<script>
import {
Loading as ElIconLoading,
Refresh as ElIconRefresh,
Close as ElIconClose,
} from '@element-plus/icons'
import { mapState } from 'vuex'
import TootDetail from './SideBar/TootDetail'
import AccountProfile from './SideBar/AccountProfile'
export default {
name: 'side-bar',
components: {
TootDetail,
AccountProfile
AccountProfile,
ElIconLoading,
ElIconRefresh,
ElIconClose,
},
name: 'side-bar',
props: {
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
loading: false
loading: false,
}
},
computed: {
...mapState({
openSideBar: state => state.TimelineSpace.Contents.SideBar.openSideBar,
component: state => state.TimelineSpace.Contents.SideBar.component,
backgroundColor: state => state.App.theme.background_color
openSideBar: (state) => state.TimelineSpace.Contents.SideBar.openSideBar,
component: (state) => state.TimelineSpace.Contents.SideBar.component,
backgroundColor: (state) => state.App.theme.background_color,
}),
shortcutEnabled: function() {
shortcutEnabled: function () {
return !this.overlaid
}
},
},
beforeDestroy() {
this.close()
@ -71,8 +87,8 @@ export default {
this.close()
break
}
}
}
},
},
}
</script>

View File

@ -8,14 +8,30 @@
role="article"
aria-label="account profile"
>
<div class="header-background" v-bind:style="{ backgroundImage: 'url(' + account.header + ')' }">
<div
class="header-background"
v-bind:style="{ backgroundImage: 'url(' + account.header + ')' }"
>
<div class="header">
<div class="relationship" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<div
class="relationship"
v-if="relationship !== null && relationship !== '' && !isOwnProfile"
>
<div class="follower-status">
<el-tag class="status" size="small" v-if="relationship.followed_by">{{ $t('side_bar.account_profile.follows_you') }}</el-tag>
<el-tag class="status" size="medium" v-else>{{ $t('side_bar.account_profile.doesnt_follow_you') }}</el-tag>
<el-tag
class="status"
size="small"
v-if="relationship.followed_by"
>{{ $t('side_bar.account_profile.follows_you') }}</el-tag
>
<el-tag class="status" size="medium" v-else>{{
$t('side_bar.account_profile.doesnt_follow_you')
}}</el-tag>
</div>
<div class="notify" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<div
class="notify"
v-if="relationship !== null && relationship !== '' && !isOwnProfile"
>
<div
v-if="relationship.notifying"
class="unsubscribe"
@ -24,15 +40,27 @@
>
<font-awesome-icon icon="bell" size="xl" />
</div>
<div v-else class="subscribe" @click="subscribe(account)" :title="$t('side_bar.account_profile.subscribe')">
<div
v-else
class="subscribe"
@click="subscribe(account)"
:title="$t('side_bar.account_profile.subscribe')"
>
<font-awesome-icon :icon="['far', 'bell']" size="xl" />
</div>
</div>
</div>
<div class="user-info">
<div class="more" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<popper trigger="click" :options="{ placement: 'bottom' }" ref="popper">
<div
class="more"
v-if="relationship !== null && relationship !== '' && !isOwnProfile"
>
<popper
trigger="click"
:options="{ placement: 'bottom' }"
ref="popper"
>
<div class="popper">
<ul class="menu-list">
<li role="button" @click="openBrowser(account)">
@ -56,22 +84,45 @@
</ul>
</div>
<el-button slot="reference" type="text" :title="$t('side_bar.account_profile.detail')">
<el-button
slot="reference"
type="text"
:title="$t('side_bar.account_profile.detail')"
>
<font-awesome-icon icon="gear" size="xl" />
</el-button>
</popper>
</div>
<div class="icon" role="presentation">
<FailoverImg :src="account.avatar" :alt="`Avatar of ${account.username}`" />
<FailoverImg
:src="account.avatar"
:alt="`Avatar of ${account.username}`"
/>
</div>
<div class="follow-status" v-if="relationship !== null && relationship !== '' && !isOwnProfile">
<div v-if="relationship.following" class="unfollow" @click="unfollow(account)" :title="$t('side_bar.account_profile.unfollow')">
<div
class="follow-status"
v-if="relationship !== null && relationship !== '' && !isOwnProfile"
>
<div
v-if="relationship.following"
class="unfollow"
@click="unfollow(account)"
:title="$t('side_bar.account_profile.unfollow')"
>
<font-awesome-icon icon="user-xmark" size="xl" />
</div>
<div v-else-if="relationship.requested" :title="$t('side_bar.account_profile.follow_requested')">
<div
v-else-if="relationship.requested"
:title="$t('side_bar.account_profile.follow_requested')"
>
<font-awesome-icon icon="hourglass" size="xl" />
</div>
<div v-else class="follow" @click="follow(account)" :title="$t('side_bar.account_profile.follow')">
<div
v-else
class="follow"
@click="follow(account)"
:title="$t('side_bar.account_profile.follow')"
>
<font-awesome-icon icon="user-plus" size="xl" />
</div>
</div>
@ -80,7 +131,11 @@
<bdi v-html="username(account)"></bdi>
</div>
<div class="account">@{{ account.acct }}</div>
<div class="note" v-html="note(account)" @click.capture.prevent="noteClick"></div>
<div
class="note"
v-html="note(account)"
@click.capture.prevent="noteClick"
></div>
</div>
</div>
<div class="identity">
@ -88,7 +143,9 @@
<dt>
{{ identity.provider }}
</dt>
<dd @click.capture.prevent="identityOpen(identity.profile_url)">{{ identity.provider_username }}</dd>
<dd @click.capture.prevent="identityOpen(identity.profile_url)">
{{ identity.provider_username }}
</dd>
</dl>
</div>
<div class="metadata">
@ -100,7 +157,11 @@
</dl>
</div>
<el-row class="basic-info">
<el-col :span="8" :class="activeTab === 1 ? 'info info-active' : 'info'" @click="changeTab">
<el-col
:span="8"
:class="activeTab === 1 ? 'info info-active' : 'info'"
@click="changeTab"
>
<el-button type="text" class="tab" @click="changeTab(1)">
<div class="title">{{ $t('side_bar.account_profile.toots') }}</div>
<div class="count">{{ account.statuses_count }}</div>
@ -114,7 +175,9 @@
</el-col>
<el-col :span="8" :class="activeTab === 3 ? 'info info-active' : 'info'">
<el-button type="text" class="tab" @click="changeTab(3)">
<div class="title">{{ $t('side_bar.account_profile.followers') }}</div>
<div class="title">
{{ $t('side_bar.account_profile.followers') }}
</div>
<div class="count">{{ account.followers_count }}</div>
</el-button>
</el-col>
@ -142,32 +205,34 @@ export default {
Timeline,
Follows,
Followers,
FailoverImg
FailoverImg,
},
data() {
return {
activeTab: 1
activeTab: 1,
}
},
computed: {
...mapState({
theme: state => {
theme: (state) => {
return {
'--theme-mask-color': state.App.theme.wrapper_mask_color,
'--theme-border-color': state.App.theme.border_color,
'--theme-primary-color': state.App.theme.primary_color
'--theme-primary-color': state.App.theme.primary_color,
}
}
},
}),
...mapState('TimelineSpace/Contents/SideBar/AccountProfile', {
account: state => state.account,
identityProofs: state => state.identityProofs,
relationship: state => state.relationship,
loading: state => state.loading,
muting: state => state.relationship && state.relationship.muting,
blocking: state => state.relationship && state.relationship.blocking
account: (state) => state.account,
identityProofs: (state) => state.identityProofs,
relationship: (state) => state.relationship,
loading: (state) => state.loading,
muting: (state) => state.relationship && state.relationship.muting,
blocking: (state) => state.relationship && state.relationship.blocking,
}),
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile', ['isOwnProfile'])
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile', [
'isOwnProfile',
]),
},
watch: {
account: function () {
@ -175,7 +240,7 @@ export default {
},
loading: function (newState, _oldState) {
this.$emit('change-loading', newState)
}
},
},
methods: {
username(account) {
@ -195,29 +260,47 @@ export default {
}
},
follow(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/follow', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/follow',
account
)
} catch (err) {
this.$message({
message: this.$t('message.follow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
unfollow(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unfollow', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unfollow',
account
)
} catch (err) {
this.$message({
message: this.$t('message.unfollow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
changeTab(index) {
@ -228,25 +311,43 @@ export default {
this.$refs.popper.doClose()
},
addToList(account) {
this.$store.dispatch('TimelineSpace/Modals/ListMembership/setAccount', account)
this.$store.dispatch('TimelineSpace/Modals/ListMembership/changeModal', true)
this.$store.dispatch(
'TimelineSpace/Modals/ListMembership/setAccount',
account
)
this.$store.dispatch(
'TimelineSpace/Modals/ListMembership/changeModal',
true
)
this.$refs.popper.doClose()
},
confirmMute(account) {
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeAccount', account)
this.$store.dispatch(
'TimelineSpace/Modals/MuteConfirm/changeAccount',
account
)
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeModal', true)
this.$refs.popper.doClose()
},
unmute(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unmute', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unmute',
account
)
this.$refs.popper.doClose()
},
block(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/block', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/block',
account
)
this.$refs.popper.doClose()
},
unblock(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unblock', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unblock',
account
)
this.$refs.popper.doClose()
},
metadataClick(e) {
@ -259,32 +360,50 @@ export default {
return window.shell.openExternal(link)
},
subscribe(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/subscribe', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/subscribe',
account
)
} catch (err) {
this.$message({
message: this.$t('message.subscribe_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
unsubscribe(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unsubscribe', account)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unsubscribe',
account
)
} catch (err) {
this.$message({
message: this.$t('message.unsubscribe_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
}
}
},
},
}
</script>

View File

@ -1,14 +1,34 @@
<template>
<div id="followers">
<DynamicScroller :items="followers" :min-item-size="53" class="scroller" page-mode>
<DynamicScroller
:items="followers"
:min-item-size="53"
class="scroller"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.item]" :data-index="index" :watchData="true">
<user :user="item" :relationship="targetRelation(item.id)" @followAccount="followAccount" @unfollowAccount="unfollowAccount">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.item]"
:data-index="index"
:watchData="true"
>
<user
:user="item"
:relationship="targetRelation(item.id)"
@followAccount="followAccount"
@unfollowAccount="unfollowAccount"
>
</user>
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
<div
class="loading-card"
v-loading="lazyLoading"
:element-loading-background="backgroundColor"
></div>
</div>
</template>
@ -22,13 +42,13 @@ export default {
components: { User },
computed: {
...mapState('TimelineSpace/Contents/SideBar/AccountProfile/Followers', {
followers: state => state.followers,
relationships: state => state.relationships,
lazyLoading: state => state.lazyLoading
followers: (state) => state.followers,
relationships: (state) => state.relationships,
lazyLoading: (state) => state.lazyLoading,
}),
...mapState('App', {
backgroundColor: state => state.theme.background_color
})
backgroundColor: (state) => state.theme.background_color,
}),
},
created() {
this.load()
@ -36,78 +56,127 @@ export default {
watch: {
account: function (_newAccount, _oldAccount) {
this.load()
}
},
},
mounted() {
document.getElementById('sidebar_scrollable').addEventListener('scroll', this.onScroll)
document
.getElementById('sidebar_scrollable')
.addEventListener('scroll', this.onScroll)
},
destroyed() {
if (document.getElementById('sidebar_scrollable') !== undefined && document.getElementById('sidebar_scrollable') !== null) {
document.getElementById('sidebar_scrollable').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('sidebar_scrollable') !== undefined &&
document.getElementById('sidebar_scrollable') !== null
) {
document
.getElementById('sidebar_scrollable')
.removeEventListener('scroll', this.onScroll)
}
},
methods: {
async load() {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
const followers = await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchFollowers', this.account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships', followers)
const followers = await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchFollowers',
this.account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships',
followers
)
} catch (err) {
this.$message({
message: this.$t('message.followers_fetch_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
onScroll(event) {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('account_profile').clientHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('account_profile').clientHeight - 10 &&
!this.lazyloading
) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/lazyFetchFollowers', this.account).catch(err => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Followers/lazyFetchFollowers',
this.account
)
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
}
},
targetRelation(id) {
return this.relationships.find(r => r.id === id)
return this.relationships.find((r) => r.id === id)
},
async followAccount(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/follow', account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships', this.followers)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/follow',
account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships',
this.followers
)
} catch (err) {
this.$message({
message: this.$t('message.follow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
async unfollowAccount(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unfollow', account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships', this.followers)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unfollow',
account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Followers/fetchRelationships',
this.followers
)
} catch (err) {
this.$message({
message: this.$t('message.unfollow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
}
}
},
},
}
</script>

View File

@ -1,8 +1,19 @@
<template>
<div id="follows">
<DynamicScroller :items="follows" :min-item-size="53" class="scroller" page-mode>
<DynamicScroller
:items="follows"
:min-item-size="53"
class="scroller"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.item]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.item]"
:data-index="index"
:watchData="true"
>
<user
:user="item"
v-bind:key="item.id"
@ -14,7 +25,11 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
<div
class="loading-card"
v-loading="lazyLoading"
:element-loading-background="backgroundColor"
></div>
</div>
</template>
@ -28,93 +43,142 @@ export default {
components: { User },
computed: {
...mapState('TimelineSpace/Contents/SideBar/AccountProfile/Follows', {
follows: state => state.follows,
relationships: state => state.relationships,
lazyLoading: state => state.lazyLoading
follows: (state) => state.follows,
relationships: (state) => state.relationships,
lazyLoading: (state) => state.lazyLoading,
}),
...mapState('App', {
backgroundColor: state => state.theme.background_color
})
backgroundColor: (state) => state.theme.background_color,
}),
},
created() {
this.load()
},
mounted() {
document.getElementById('sidebar_scrollable').addEventListener('scroll', this.onScroll)
document
.getElementById('sidebar_scrollable')
.addEventListener('scroll', this.onScroll)
},
destroyed() {
if (document.getElementById('sidebar_scrollable') !== undefined && document.getElementById('sidebar_scrollable') !== null) {
document.getElementById('sidebar_scrollable').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('sidebar_scrollable') !== undefined &&
document.getElementById('sidebar_scrollable') !== null
) {
document
.getElementById('sidebar_scrollable')
.removeEventListener('scroll', this.onScroll)
}
},
watch: {
account: function (_newAccount, _oldAccount) {
this.load()
}
},
},
methods: {
async load() {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
const follows = await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchFollows', this.account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships', follows)
const follows = await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchFollows',
this.account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships',
follows
)
} catch (err) {
console.error(err)
this.$message({
message: this.$t('message.follows_fetch_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
onScroll(event) {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('account_profile').clientHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('account_profile').clientHeight - 10 &&
!this.lazyloading
) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/lazyFetchFollows', this.account).catch(err => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Follows/lazyFetchFollows',
this.account
)
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
}
},
targetRelation(id) {
return this.relationships.find(r => r.id === id)
return this.relationships.find((r) => r.id === id)
},
async followAccount(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/follow', account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships', this.follows)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/follow',
account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships',
this.follows
)
} catch (err) {
this.$message({
message: this.$t('message.follow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
},
async unfollowAccount(account) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
true
)
try {
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/unfollow', account)
await this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships', this.follows)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/unfollow',
account
)
await this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Follows/fetchRelationships',
this.follows
)
} catch (err) {
this.$message({
message: this.$t('message.unfollow_error'),
type: 'error'
type: 'error',
})
} finally {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/changeLoading', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/changeLoading',
false
)
}
}
}
},
},
}
</script>

View File

@ -1,11 +1,18 @@
<template>
<div class="tabs" id="sidebar_tabs">
<el-tabs v-model="activeName" @tab-click="handleClick" stretch>
<el-tab-pane label="Posts" name="posts"><Posts :account="account" :buffer="buffer" :filters="filters" /></el-tab-pane>
<el-tab-pane label="Posts and replies" name="posts_and_replies"
><PostsAndReplies :account="account" :buffer="buffer" :filters="filters"
<el-tab-pane label="Posts" name="posts"
><Posts :account="account" :buffer="buffer" :filters="filters"
/></el-tab-pane>
<el-tab-pane label="Posts and replies" name="posts_and_replies"
><PostsAndReplies
:account="account"
:buffer="buffer"
:filters="filters"
/></el-tab-pane>
<el-tab-pane label="Media" name="media"
><Media :account="account" :buffer="buffer" :filters="filters"
/></el-tab-pane>
<el-tab-pane label="Media" name="media"><Media :account="account" :buffer="buffer" :filters="filters" /></el-tab-pane>
</el-tabs>
</div>
</template>
@ -22,17 +29,19 @@ export default {
components: {
Posts,
PostsAndReplies,
Media
Media,
},
data() {
return {
activeName: 'posts',
defaultBuffer: 200,
buffer: 200
buffer: 200,
}
},
computed: {
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile/Timeline', ['filters'])
...mapGetters('TimelineSpace/Contents/SideBar/AccountProfile/Timeline', [
'filters',
]),
},
mounted() {
const timeline = document.getElementById('sidebar_tabs')
@ -43,8 +52,8 @@ export default {
methods: {
handleClick(tab, event) {
console.log(tab, event)
}
}
},
},
}
</script>

View File

@ -1,8 +1,20 @@
<template>
<div id="timeline">
<DynamicScroller :items="timeline" :min-item-size="60" class="scroller" :buffer="buffer" page-mode>
<DynamicScroller
:items="timeline"
:min-item-size="60"
class="scroller"
:buffer="buffer"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:key="item.id"
@ -20,7 +32,11 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
<div
class="loading-card"
v-loading="lazyLoading"
:element-loading-background="backgroundColor"
></div>
</div>
</template>
@ -35,25 +51,32 @@ export default {
components: { Toot },
data() {
return {
focusedId: null
focusedId: null,
}
},
computed: {
...mapState('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading
}),
...mapState(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media',
{
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
}
),
...mapState('App', {
backgroundColor: state => state.theme.background_color
backgroundColor: (state) => state.theme.background_color,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened'])
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
},
created() {
this.load()
},
mounted() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/clearTimeline')
document.getElementById('sidebar_scrollable').addEventListener('scroll', this.onScroll)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/clearTimeline'
)
document
.getElementById('sidebar_scrollable')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-sidebar', () => {
this.focusedId = 0
this.$nextTick(function () {
@ -66,63 +89,93 @@ export default {
Event.$off('focus-sidebar')
},
destroyed() {
if (document.getElementById('sidebar_scrollable') !== undefined && document.getElementById('sidebar_scrollable') !== null) {
document.getElementById('sidebar_scrollable').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('sidebar_scrollable') !== undefined &&
document.getElementById('sidebar_scrollable') !== null
) {
document
.getElementById('sidebar_scrollable')
.removeEventListener('scroll', this.onScroll)
}
},
watch: {
account: function (_newAccount, _oldAccount) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/clearTimeline')
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/clearTimeline'
)
this.load()
}
},
},
methods: {
load() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/fetchTimeline', this.account).catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/fetchTimeline',
this.account
)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/deleteToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/deleteToot',
message
)
},
onScroll(event) {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('account_profile').clientHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('account_profile').clientHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/lazyFetchTimeline', {
account: this.account,
status: this.timeline[this.timeline.length - 1]
})
.catch(err => {
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Media/lazyFetchTimeline',
{
account: this.account,
status: this.timeline[this.timeline.length - 1],
}
)
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length - 1) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -131,8 +184,8 @@ export default {
focusTimeline() {
this.focusedId = 0
Event.$emit('focus-timeline')
}
}
},
},
}
</script>

View File

@ -17,9 +17,21 @@
>
</toot>
</template>
<DynamicScroller :items="timeline" :min-item-size="60" class="scroller" :buffer="buffer" page-mode>
<DynamicScroller
:items="timeline"
:min-item-size="60"
class="scroller"
:buffer="buffer"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:key="item.id"
@ -37,7 +49,11 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
<div
class="loading-card"
v-loading="lazyLoading"
:element-loading-background="backgroundColor"
></div>
</div>
</template>
@ -52,26 +68,33 @@ export default {
components: { Toot },
data() {
return {
focusedId: null
focusedId: null,
}
},
computed: {
...mapState('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts', {
timeline: state => state.timeline,
pinnedToots: state => state.pinnedToots,
lazyLoading: state => state.lazyLoading
}),
...mapState(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts',
{
timeline: (state) => state.timeline,
pinnedToots: (state) => state.pinnedToots,
lazyLoading: (state) => state.lazyLoading,
}
),
...mapState('App', {
backgroundColor: state => state.theme.background_color
backgroundColor: (state) => state.theme.background_color,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened'])
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
},
created() {
this.load()
},
mounted() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/clearTimeline')
document.getElementById('sidebar_scrollable').addEventListener('scroll', this.onScroll)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/clearTimeline'
)
document
.getElementById('sidebar_scrollable')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-sidebar', () => {
this.focusedId = 0
this.$nextTick(function () {
@ -84,66 +107,99 @@ export default {
Event.$off('focus-sidebar')
},
destroyed() {
if (document.getElementById('sidebar_scrollable') !== undefined && document.getElementById('sidebar_scrollable') !== null) {
document.getElementById('sidebar_scrollable').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('sidebar_scrollable') !== undefined &&
document.getElementById('sidebar_scrollable') !== null
) {
document
.getElementById('sidebar_scrollable')
.removeEventListener('scroll', this.onScroll)
}
},
watch: {
account: function (_newAccount, _oldAccount) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/clearTimeline')
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/clearTimeline'
)
this.load()
}
},
},
methods: {
load() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/fetchTimeline', this.account).catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
this.$store
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/fetchTimeline',
this.account
)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error',
})
})
})
},
onScroll(event) {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('account_profile').clientHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('account_profile').clientHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/lazyFetchTimeline', {
account: this.account,
status: this.timeline[this.timeline.length - 1]
})
.catch(err => {
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/lazyFetchTimeline',
{
account: this.account,
status: this.timeline[this.timeline.length - 1],
}
)
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
},
updatePinnedToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/updatePinnedToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/updatePinnedToot',
message
)
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/deleteToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/Posts/deleteToot',
message
)
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length - 1) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -152,8 +208,8 @@ export default {
focusTimeline() {
this.focusedId = 0
Event.$emit('focus-timeline')
}
}
},
},
}
</script>

View File

@ -1,8 +1,20 @@
<template>
<div id="timeline">
<DynamicScroller :items="timeline" :min-item-size="60" class="scroller" :buffer="buffer" page-mode>
<DynamicScroller
:items="timeline"
:min-item-size="60"
class="scroller"
:buffer="buffer"
page-mode
>
<template v-slot="{ item, index, active }">
<DynamicScrollerItem :item="item" :active="active" :size-dependencies="[item.uri]" :data-index="index" :watchData="true">
<DynamicScrollerItem
:item="item"
:active="active"
:size-dependencies="[item.uri]"
:data-index="index"
:watchData="true"
>
<toot
:message="item"
:key="item.id"
@ -20,7 +32,11 @@
</DynamicScrollerItem>
</template>
</DynamicScroller>
<div class="loading-card" v-loading="lazyLoading" :element-loading-background="backgroundColor"></div>
<div
class="loading-card"
v-loading="lazyLoading"
:element-loading-background="backgroundColor"
></div>
</div>
</template>
@ -35,25 +51,32 @@ export default {
components: { Toot },
data() {
return {
focusedId: null
focusedId: null,
}
},
computed: {
...mapState('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies', {
timeline: state => state.timeline,
lazyLoading: state => state.lazyLoading
}),
...mapState(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies',
{
timeline: (state) => state.timeline,
lazyLoading: (state) => state.lazyLoading,
}
),
...mapState('App', {
backgroundColor: state => state.theme.background_color
backgroundColor: (state) => state.theme.background_color,
}),
...mapGetters('TimelineSpace/Modals', ['modalOpened'])
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
},
created() {
this.load()
},
mounted() {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/clearTimeline')
document.getElementById('sidebar_scrollable').addEventListener('scroll', this.onScroll)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/clearTimeline'
)
document
.getElementById('sidebar_scrollable')
.addEventListener('scroll', this.onScroll)
Event.$on('focus-sidebar', () => {
this.focusedId = 0
this.$nextTick(function () {
@ -66,65 +89,93 @@ export default {
Event.$off('focus-sidebar')
},
destroyed() {
if (document.getElementById('sidebar_scrollable') !== undefined && document.getElementById('sidebar_scrollable') !== null) {
document.getElementById('sidebar_scrollable').removeEventListener('scroll', this.onScroll)
if (
document.getElementById('sidebar_scrollable') !== undefined &&
document.getElementById('sidebar_scrollable') !== null
) {
document
.getElementById('sidebar_scrollable')
.removeEventListener('scroll', this.onScroll)
}
},
watch: {
account: function (_newAccount, _oldAccount) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/clearTimeline')
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/clearTimeline'
)
this.load()
}
},
},
methods: {
load() {
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/fetchTimeline', this.account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/fetchTimeline',
this.account
)
.catch(() => {
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/deleteToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/deleteToot',
message
)
},
onScroll(event) {
// for lazyLoading
if (
event.target.clientHeight + event.target.scrollTop >= document.getElementById('account_profile').clientHeight - 10 &&
event.target.clientHeight + event.target.scrollTop >=
document.getElementById('account_profile').clientHeight - 10 &&
!this.lazyloading
) {
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/lazyFetchTimeline', {
account: this.account,
status: this.timeline[this.timeline.length - 1]
})
.catch(err => {
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/Timeline/PostsAndReplies/lazyFetchTimeline',
{
account: this.account,
status: this.timeline[this.timeline.length - 1],
}
)
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.timeline_fetch_error'),
type: 'error'
type: 'error',
})
})
}
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length - 1) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -133,8 +184,8 @@ export default {
focusTimeline() {
this.focusedId = 0
Event.$emit('focus-timeline')
}
}
},
},
}
</script>

View File

@ -1,6 +1,10 @@
<template>
<div class="toot-detail" ref="detail">
<div class="toot-ancestors" v-for="(message, index) in ancestors" v-bind:key="'ancestors-' + index">
<div
class="toot-ancestors"
v-for="(message, index) in ancestors"
v-bind:key="'ancestors-' + index"
>
<toot
:message="message"
:focused="message.uri + message.id === focusedId"
@ -31,7 +35,11 @@
>
</toot>
</div>
<div class="toot-descendants" v-for="(message, index) in descendants" v-bind:key="'descendants' + index">
<div
class="toot-descendants"
v-for="(message, index) in descendants"
v-bind:key="'descendants' + index"
>
<toot
:message="message"
:focused="message.uri + message.id === focusedId"
@ -59,18 +67,19 @@ export default {
components: { Toot },
data() {
return {
focusedId: null
focusedId: null,
}
},
computed: {
...mapState('TimelineSpace/Contents/SideBar/TootDetail', {
message: state => state.message,
ancestors: state => state.ancestors,
descendants: state => state.descendants,
timeline: state => state.ancestors.concat([state.message]).concat(state.descendants)
message: (state) => state.message,
ancestors: (state) => state.ancestors,
descendants: (state) => state.descendants,
timeline: (state) =>
state.ancestors.concat([state.message]).concat(state.descendants),
}),
...mapGetters('TimelineSpace/Contents/SideBar/TootDetail', ['filters']),
...mapGetters('TimelineSpace/Modals', ['modalOpened'])
...mapGetters('TimelineSpace/Modals', ['modalOpened']),
},
created() {
this.load()
@ -86,7 +95,7 @@ export default {
watch: {
message: function () {
this.load()
}
},
},
beforeDestroy() {
Event.$emit('focus-timeline')
@ -102,7 +111,10 @@ export default {
},
load() {
this.$store
.dispatch('TimelineSpace/Contents/SideBar/TootDetail/fetchToot', this.originalMessage(this.message))
.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/fetchToot',
this.originalMessage(this.message)
)
.then(() => {
const toot = this.$refs.original
toot.scrollIntoView()
@ -110,40 +122,66 @@ export default {
.catch(() => {
this.$message({
message: this.$t('message.toot_fetch_error'),
type: 'error'
type: 'error',
})
})
},
updateAncestorsToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/updateAncestorsToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/updateAncestorsToot',
message
)
},
deleteAncestorsToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/deleteAncestorsToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/deleteAncestorsToot',
message
)
},
updateToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/updateToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/updateToot',
message
)
},
deleteToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/deleteToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/deleteToot',
message
)
},
updateDescendantsToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/updateDescendantsToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/updateDescendantsToot',
message
)
},
deleteDescendantsToot(message) {
this.$store.commit('TimelineSpace/Contents/SideBar/TootDetail/deleteDescendantsToot', message)
this.$store.commit(
'TimelineSpace/Contents/SideBar/TootDetail/deleteDescendantsToot',
message
)
},
focusNext() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex === -1) {
this.focusedId = this.timeline[0].uri + this.timeline[0].id
} else if (currentIndex < this.timeline.length - 1) {
this.focusedId = this.timeline[currentIndex + 1].uri + this.timeline[currentIndex + 1].id
this.focusedId =
this.timeline[currentIndex + 1].uri +
this.timeline[currentIndex + 1].id
}
},
focusPrev() {
const currentIndex = this.timeline.findIndex(toot => this.focusedId === toot.uri + toot.id)
const currentIndex = this.timeline.findIndex(
(toot) => this.focusedId === toot.uri + toot.id
)
if (currentIndex > 0) {
this.focusedId = this.timeline[currentIndex - 1].uri + this.timeline[currentIndex - 1].id
this.focusedId =
this.timeline[currentIndex - 1].uri +
this.timeline[currentIndex - 1].id
}
},
focusToot(message) {
@ -152,8 +190,8 @@ export default {
focusTimeline() {
this.focusedId = 0
Event.$emit('focus-timeline')
}
}
},
},
}
</script>

View File

@ -4,30 +4,81 @@
<h1>{{ title }}</h1>
</div>
<div class="tools">
<img src="../../assets/images/loading-spinner-wide.svg" v-show="loading" class="header-loading" />
<el-button type="text" class="action" @click="openNewTootModal" :title="$t('header_menu.new_toot')">
<img
src="../../assets/images/loading-spinner-wide.svg"
v-show="loading"
class="header-loading"
/>
<el-button
type="text"
class="action"
@click="openNewTootModal"
:title="$t('header_menu.new_toot')"
>
<font-awesome-icon :icon="['far', 'pen-to-square']" />
</el-button>
<el-button v-show="reloadable()" type="text" class="action" @click="reload" :title="$t('header_menu.reload')">
<el-button
v-show="reloadable()"
type="text"
class="action"
@click="reload"
:title="$t('header_menu.reload')"
>
<font-awesome-icon icon="rotate" />
</el-button>
<el-popover placement="left-start" width="180" popper-class="theme-popover" trigger="click" v-model="TLOptionVisible">
<el-popover
placement="left-start"
width="180"
popper-class="theme-popover"
trigger="click"
v-model="TLOptionVisible"
>
<div>
<el-form role="form" label-position="left" label-width="125px" size="medium">
<el-form-item for="show-reblogs" :label="$t('header_menu.option.show_reblogs')">
<el-checkbox id="show-reblogs" v-model="showReblogs"></el-checkbox>
<el-form
role="form"
label-position="left"
label-width="125px"
size="medium"
>
<el-form-item
for="show-reblogs"
:label="$t('header_menu.option.show_reblogs')"
>
<el-checkbox
id="show-reblogs"
v-model="showReblogs"
></el-checkbox>
</el-form-item>
<el-form-item for="show-replies" :label="$t('header_menu.option.show_replies')">
<el-checkbox id="show-replies" v-model="showReplies"></el-checkbox>
<el-form-item
for="show-replies"
:label="$t('header_menu.option.show_replies')"
>
<el-checkbox
id="show-replies"
v-model="showReplies"
></el-checkbox>
</el-form-item>
<el-button type="primary" @click="applyTLOption">{{ $t('header_menu.option.apply') }}</el-button>
<el-button type="primary" @click="applyTLOption">{{
$t('header_menu.option.apply')
}}</el-button>
</el-form>
</div>
<el-button v-show="TLOption()" slot="reference" type="text" class="action" :title="$t('header_menu.option.title')">
<el-button
v-show="TLOption()"
slot="reference"
type="text"
class="action"
:title="$t('header_menu.option.title')"
>
<font-awesome-icon icon="sliders" />
</el-button>
</el-popover>
<el-button type="text" class="action" @click="settings" :title="$t('header_menu.settings')">
<el-button
type="text"
class="action"
@click="settings"
:title="$t('header_menu.settings')"
>
<font-awesome-icon icon="gear" />
</el-button>
</div>
@ -43,14 +94,14 @@ export default {
return {
TLOptionVisible: false,
showReblogs: true,
showReplies: true
showReplies: true,
}
},
computed: {
...mapState('TimelineSpace/HeaderMenu', {
title: state => state.title,
loading: state => state.loading
})
title: (state) => state.title,
loading: (state) => state.loading,
}),
},
created() {
this.channelName()
@ -61,7 +112,7 @@ export default {
$route: function () {
this.channelName()
this.loadTLOption()
}
},
},
methods: {
id() {
@ -70,53 +121,101 @@ export default {
channelName() {
switch (this.$route.name) {
case 'home':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.home'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.home')
)
break
case 'notifications':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.notification'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.notification')
)
break
case 'favourites':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.favourite'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.favourite')
)
break
case 'bookmarks':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.bookmark'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.bookmark')
)
break
case 'mentions':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.mention'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.mention')
)
break
case 'follow-requests':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.follow_requests'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.follow_requests')
)
break
case 'local':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.local'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.local')
)
break
case 'public':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.public'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.public')
)
break
case 'hashtag-list':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.hashtag'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.hashtag')
)
break
case 'tag':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', `#${this.$route.params.tag}`)
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
`#${this.$route.params.tag}`
)
break
case 'search':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.search'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.search')
)
break
case 'lists':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.lists'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.lists')
)
break
case 'direct-messages':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.direct_messages'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.direct_messages')
)
break
case 'edit-list':
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.members'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.members')
)
break
case 'list':
this.$store.dispatch('TimelineSpace/HeaderMenu/fetchList', this.$route.params.list_id)
this.$store.dispatch(
'TimelineSpace/HeaderMenu/fetchList',
this.$route.params.list_id
)
break
default:
console.log(this.$route)
this.$store.commit('TimelineSpace/HeaderMenu/updateTitle', this.$t('header_menu.home'))
this.$store.commit(
'TimelineSpace/HeaderMenu/updateTitle',
this.$t('header_menu.home')
)
break
}
},
@ -161,8 +260,10 @@ export default {
loadTLOption() {
switch (this.$route.name) {
case 'home':
this.showReblogs = this.$store.state.TimelineSpace.Contents.Home.showReblogs
this.showReplies = this.$store.state.TimelineSpace.Contents.Home.showReplies
this.showReblogs =
this.$store.state.TimelineSpace.Contents.Home.showReblogs
this.showReplies =
this.$store.state.TimelineSpace.Contents.Home.showReplies
break
default:
console.log('Not implemented')
@ -171,8 +272,14 @@ export default {
applyTLOption() {
switch (this.$route.name) {
case 'home':
this.$store.commit('TimelineSpace/Contents/Home/showReblogs', this.showReblogs)
this.$store.commit('TimelineSpace/Contents/Home/showReplies', this.showReplies)
this.$store.commit(
'TimelineSpace/Contents/Home/showReblogs',
this.showReblogs
)
this.$store.commit(
'TimelineSpace/Contents/Home/showReplies',
this.showReplies
)
break
default:
console.log('Not implemented')
@ -190,8 +297,8 @@ export default {
settings() {
const url = `/${this.id()}/settings`
this.$router.push(url)
}
}
},
},
}
</script>

View File

@ -31,7 +31,7 @@ export default {
AddListMember,
MuteConfirm,
Shortcut,
Report
}
Report,
},
}
</script>

View File

@ -1,8 +1,20 @@
<template>
<el-dialog :title="$t('modals.add_list_member.title')" :visible.sync="addListMemberModal" width="400px" class="add-member">
<el-dialog
:title="$t('modals.add_list_member.title')"
:visible.sync="addListMemberModal"
width="400px"
class="add-member"
>
<div class="search-account" :element-loading-background="loadingBackground">
<el-form :inline="true">
<input v-model="name" placeholder="Account name" class="account-name" v-shortkey="['enter']" @shortkey="search" autofocus />
<input
v-model="name"
placeholder="Account name"
class="account-name"
v-shortkey="['enter']"
@shortkey="search"
autofocus
/>
<el-button type="text" class="search" @click="search">
<font-awesome-icon icon="magnifying-glass" />
</el-button>
@ -38,23 +50,26 @@ export default {
name: 'add-list-member',
data() {
return {
name: ''
name: '',
}
},
computed: {
...mapState({
loadingBackground: state => state.App.theme.wrapper_mask_color,
accounts: state => state.TimelineSpace.Modals.AddListMember.accounts,
listId: state => state.TimelineSpace.Modals.AddListMember.targetListId
loadingBackground: (state) => state.App.theme.wrapper_mask_color,
accounts: (state) => state.TimelineSpace.Modals.AddListMember.accounts,
listId: (state) => state.TimelineSpace.Modals.AddListMember.targetListId,
}),
addListMemberModal: {
get() {
return this.$store.state.TimelineSpace.Modals.AddListMember.modalOpen
},
set(value) {
this.$store.dispatch('TimelineSpace/Modals/AddListMember/changeModal', value)
}
}
this.$store.dispatch(
'TimelineSpace/Modals/AddListMember/changeModal',
value
)
},
},
},
methods: {
username(account) {
@ -65,23 +80,29 @@ export default {
}
},
search() {
this.$store.dispatch('TimelineSpace/Modals/AddListMember/search', this.name)
this.$store.dispatch(
'TimelineSpace/Modals/AddListMember/search',
this.name
)
},
add(user) {
this.$store
.dispatch('TimelineSpace/Modals/AddListMember/add', user)
.then(() => {
this.addListMemberModal = false
this.$store.dispatch('TimelineSpace/Contents/Lists/Edit/fetchMembers', this.listId)
this.$store.dispatch(
'TimelineSpace/Contents/Lists/Edit/fetchMembers',
this.listId
)
})
.catch(() => {
this.$message({
message: this.$t('message.add_user_error'),
type: 'error'
type: 'error',
})
})
}
}
},
},
}
</script>

View File

@ -1,64 +1,108 @@
<template>
<transition name="image-viewer">
<div id="image" v-show="modalOpen" @click="close" :aria-hidden="!modalOpen" aria-modal="true" role="dialog">
<div class="image-wrapper" v-shortkey="modalOpen ? {close: ['esc']} : {}" @shortkey="closeHandle">
<div class="image-header">
<el-button type="text" icon="el-icon-close" @click="close" class="close-button"></el-button>
</div>
<div class="image-content" role="presentation">
<span class="button-area"><el-button type="text" v-show="showLeft" v-shortkey="['arrowleft']" @shortkey.native="decrementIndex()"><i class="el-icon-arrow-left" @click.stop="decrementIndex"></i></el-button></span>
<Media :src="imageURL" :type="imageType"></Media>
<span class="button-area"><el-button type="text" v-show="showRight" v-shortkey="['arrowright']" @shortkey.native="incrementIndex()"><i class="el-icon-arrow-right" @click.stop="incrementIndex"></i></el-button></span>
<transition name="image-viewer">
<div
id="image"
v-show="modalOpen"
@click="close"
:aria-hidden="!modalOpen"
aria-modal="true"
role="dialog"
>
<div
class="image-wrapper"
v-shortkey="modalOpen ? { close: ['esc'] } : {}"
@shortkey="closeHandle"
>
<div class="image-header">
<el-button
type="text"
:icon="ElIconClose"
@click="close"
class="close-button"
></el-button>
</div>
<div class="image-content" role="presentation">
<span class="button-area"
><el-button
type="text"
v-show="showLeft"
v-shortkey="['arrowleft']"
@shortkey.native="decrementIndex()"
><el-icon><el-icon-arrow-left /></el-icon></el-button
></span>
<Media :src="imageURL" :type="imageType"></Media>
<span class="button-area"
><el-button
type="text"
v-show="showRight"
v-shortkey="['arrowright']"
@shortkey.native="incrementIndex()"
><el-icon><el-icon-arrow-right /></el-icon></el-button
></span>
</div>
</div>
</div>
</div>
</transition>
</transition>
</template>
<script>
import {
ArrowLeft as ElIconArrowLeft,
ArrowRight as ElIconArrowRight,
Close as ElIconClose,
} from '@element-plus/icons'
import Media from './Media'
import { mapState } from 'vuex'
export default {
name: 'image-viewer',
components: {
Media
},
computed: {
...mapState({
modalOpen: state => state.TimelineSpace.Modals.ImageViewer.modalOpen
}),
imageURL () {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/imageURL']
},
imageType () {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/imageType']
},
showLeft () {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/showLeft']
},
showRight () {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/showRight']
data() {
return {
ElIconClose,
}
},
components: {
Media,
ElIconArrowLeft,
ElIconArrowRight,
},
name: 'image-viewer',
computed: {
...mapState({
modalOpen: (state) => state.TimelineSpace.Modals.ImageViewer.modalOpen,
}),
imageURL() {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/imageURL']
},
imageType() {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/imageType']
},
showLeft() {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/showLeft']
},
showRight() {
return this.$store.getters['TimelineSpace/Modals/ImageViewer/showRight']
},
},
methods: {
close () {
close() {
this.$store.dispatch('TimelineSpace/Modals/ImageViewer/closeModal')
},
decrementIndex () {
if (this.showLeft) this.$store.dispatch('TimelineSpace/Modals/ImageViewer/decrementIndex')
decrementIndex() {
if (this.showLeft)
this.$store.dispatch('TimelineSpace/Modals/ImageViewer/decrementIndex')
},
incrementIndex () {
if (this.showRight) this.$store.dispatch('TimelineSpace/Modals/ImageViewer/incrementIndex')
incrementIndex() {
if (this.showRight)
this.$store.dispatch('TimelineSpace/Modals/ImageViewer/incrementIndex')
},
closeHandle (event) {
closeHandle(event) {
switch (event.srcKey) {
case 'close':
this.close()
break
}
}
}
},
},
}
</script>
@ -75,7 +119,7 @@ export default {
.image-header {
width: 100%;
text-align: right;
padding: 8px 8px 0 0 ;
padding: 8px 8px 0 0;
color: #409eff;
box-sizing: border-box;
position: fixed;
@ -93,10 +137,12 @@ export default {
}
}
.image-viewer-enter-active, .image-viewer-leave-active {
.image-viewer-enter-active,
.image-viewer-leave-active {
transition: opacity 0.5s;
}
.image-viewer-enter, .image-viewer-leave-to {
.image-viewer-enter,
.image-viewer-leave-to {
opacity: 0;
}
.button-area {

View File

@ -1,8 +1,5 @@
<template>
<el-dialog
:visible.sync="jumpModal"
width="440px"
class="jump-modal">
<el-dialog :visible.sync="jumpModal" width="440px" class="jump-modal">
<el-form class="jump-form" v-on:submit.prevent="jump">
<div class="channel">
<input
@ -10,11 +7,26 @@
v-model="channel"
:placeholder="$t('modals.jump.jump_to')"
ref="channel"
v-shortkey="shortcutEnabled ? {next: ['arrowdown'], prev: ['arrowup'], select: ['enter']} : {}"
v-shortkey="
shortcutEnabled
? { next: ['arrowdown'], prev: ['arrowup'], select: ['enter'] }
: {}
"
@shortkey="handleKey"
/>
/>
<ul class="channel-list">
<li v-for="c in filterdChannel" :class="c.name === selectedChannel.name ? 'channel-list-item selected' : 'channel-list-item'" @click="jump(c)" @mouseover="changeSelected(c)">{{ c.name }}</li>
<li
v-for="c in filterdChannel"
:class="
c.name === selectedChannel.name
? 'channel-list-item selected'
: 'channel-list-item'
"
@click="jump(c)"
@mouseover="changeSelected(c)"
>
{{ c.name }}
</li>
</ul>
</div>
<!-- Dummy form to guard submitting with enter -->
@ -32,35 +44,41 @@ export default {
name: 'jump',
computed: {
...mapState('TimelineSpace/Modals/Jump', {
channelList: state => state.defaultChannelList.concat(state.tagChannelList).concat(state.listChannelList),
selectedChannel: state => state.selectedChannel
channelList: (state) =>
state.defaultChannelList
.concat(state.tagChannelList)
.concat(state.listChannelList),
selectedChannel: (state) => state.selectedChannel,
}),
channel: {
get () {
get() {
return this.$store.state.TimelineSpace.Modals.Jump.channel
},
set (value) {
set(value) {
this.$store.commit('TimelineSpace/Modals/Jump/updateChannel', value)
}
},
},
filterdChannel () {
filterdChannel() {
return this.filterChannelForm()
},
jumpModal: {
get () {
get() {
return this.$store.state.TimelineSpace.Modals.Jump.modalOpen
},
set (value) {
set(value) {
this.$store.commit('TimelineSpace/Modals/Jump/changeModal', value)
}
},
},
shortcutEnabled: function () {
return this.jumpModal
}
},
},
watch: {
channel: function (_newChannel, _oldChannel) {
this.$store.commit('TimelineSpace/Modals/Jump/changeSelected', this.filterChannelForm()[0])
this.$store.commit(
'TimelineSpace/Modals/Jump/changeSelected',
this.filterChannelForm()[0]
)
},
jumpModal: function (newModal, oldModal) {
if (!oldModal && newModal) {
@ -72,44 +90,50 @@ export default {
} else {
this.channel = ''
}
}
},
},
methods: {
filterChannelForm () {
filterChannelForm() {
return this.channelList.filter((c) => {
return c.name.toLowerCase().indexOf(this.channel.toLowerCase()) !== -1
})
},
nextChannel () {
nextChannel() {
const filterd = this.filterChannelForm()
const i = filterd.findIndex((e) => {
return e.name === this.selectedChannel.name
})
if (i !== undefined && i < (filterd.length - 1)) {
this.$store.commit('TimelineSpace/Modals/Jump/changeSelected', filterd[i + 1])
if (i !== undefined && i < filterd.length - 1) {
this.$store.commit(
'TimelineSpace/Modals/Jump/changeSelected',
filterd[i + 1]
)
}
},
prevChannel () {
prevChannel() {
const filterd = this.filterChannelForm()
const i = filterd.findIndex((e) => {
return e.name === this.selectedChannel.name
})
if (i !== undefined && i > 0) {
this.$store.commit('TimelineSpace/Modals/Jump/changeSelected', filterd[i - 1])
this.$store.commit(
'TimelineSpace/Modals/Jump/changeSelected',
filterd[i - 1]
)
}
},
changeSelected (channel) {
changeSelected(channel) {
this.$store.commit('TimelineSpace/Modals/Jump/changeSelected', channel)
},
jumpCurrentSelected () {
jumpCurrentSelected() {
if (this.jumpModal) {
this.$store.dispatch('TimelineSpace/Modals/Jump/jumpCurrentSelected')
}
},
jump (channel) {
jump(channel) {
this.$store.dispatch('TimelineSpace/Modals/Jump/jump', channel)
},
handleKey (event) {
handleKey(event) {
switch (event.srcKey) {
case 'next':
this.nextChannel()
@ -123,8 +147,8 @@ export default {
default:
return true
}
}
}
},
},
}
</script>

View File

@ -1,5 +1,10 @@
<template>
<el-dialog :title="$t('modals.list_membership.title')" :visible.sync="listMembershipModal" width="400px" class="list-membership-modal">
<el-dialog
:title="$t('modals.list_membership.title')"
:visible.sync="listMembershipModal"
width="400px"
class="list-membership-modal"
>
<el-checkbox-group v-model="belongToLists" v-loading="loading">
<table class="lists">
<tbody>
@ -21,63 +26,76 @@ export default {
name: 'list-membership',
data() {
return {
loading: false
loading: false,
}
},
computed: {
...mapState({
account: state => state.TimelineSpace.Modals.ListMembership.account,
lists: state => state.TimelineSpace.Modals.ListMembership.lists
account: (state) => state.TimelineSpace.Modals.ListMembership.account,
lists: (state) => state.TimelineSpace.Modals.ListMembership.lists,
}),
listMembershipModal: {
get() {
return this.$store.state.TimelineSpace.Modals.ListMembership.modalOpen
},
set(value) {
this.$store.dispatch('TimelineSpace/Modals/ListMembership/changeModal', value)
}
this.$store.dispatch(
'TimelineSpace/Modals/ListMembership/changeModal',
value
)
},
},
belongToLists: {
get() {
return this.$store.state.TimelineSpace.Modals.ListMembership.belongToLists.map(l => l.id)
return this.$store.state.TimelineSpace.Modals.ListMembership.belongToLists.map(
(l) => l.id
)
},
set(value) {
this.loading = true
return this.$store
.dispatch('TimelineSpace/Modals/ListMembership/changeBelongToLists', value)
.dispatch(
'TimelineSpace/Modals/ListMembership/changeBelongToLists',
value
)
.catch(() => {
this.$message({
message: this.$t('message.update_list_memberships_error'),
type: 'error'
type: 'error',
})
})
.finally(() => (this.loading = false))
}
}
},
},
},
watch: {
listMembershipModal: function (newState, oldState) {
if (!oldState && newState) {
this.init()
}
}
},
},
methods: {
async init() {
this.loading = true
try {
await this.$store.dispatch('TimelineSpace/Modals/ListMembership/fetchListMembership', this.account)
await this.$store.dispatch('TimelineSpace/Modals/ListMembership/fetchLists')
await this.$store.dispatch(
'TimelineSpace/Modals/ListMembership/fetchListMembership',
this.account
)
await this.$store.dispatch(
'TimelineSpace/Modals/ListMembership/fetchLists'
)
} catch (err) {
this.$message({
message: this.$t('message.lists_fetch_error'),
type: 'error'
type: 'error',
})
} finally {
this.loading = false
}
}
}
},
},
}
</script>

View File

@ -1,8 +1,32 @@
<template>
<div id="current-media" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.8)">
<video :src="src" v-if="isMovieFile()" autoplay loop controls v-on:loadstart="loaded()"></video>
<video :src="src" v-else-if="isGIF()" autoplay loop v-on:loadstart="loaded()"></video>
<video :src="src" v-else-if="isAudio()" autoplay loop controls v-on:loadstart="loaded()"></video>
<div
id="current-media"
v-loading="loading"
element-loading-background="rgba(0, 0, 0, 0.8)"
>
<video
:src="src"
v-if="isMovieFile()"
autoplay
loop
controls
v-on:loadstart="loaded()"
></video>
<video
:src="src"
v-else-if="isGIF()"
autoplay
loop
v-on:loadstart="loaded()"
></video>
<video
:src="src"
v-else-if="isAudio()"
autoplay
loop
controls
v-on:loadstart="loaded()"
></video>
<img :src="imageSrc" v-else v-on:load="loaded()" />
</div>
</template>
@ -15,16 +39,16 @@ export default {
props: {
src: {
type: String,
default: ''
default: '',
},
type: {
type: String,
default: ''
}
default: '',
},
},
data() {
return {
imageSrc: this.src
imageSrc: this.src,
}
},
watch: {
@ -38,12 +62,12 @@ export default {
console.error(err)
}
}
}
},
},
computed: {
...mapState({
loading: state => state.TimelineSpace.Modals.ImageViewer.loading
})
loading: (state) => state.TimelineSpace.Modals.ImageViewer.loading,
}),
},
methods: {
isMovieFile() {
@ -57,8 +81,8 @@ export default {
},
async loaded() {
this.$store.dispatch('TimelineSpace/Modals/ImageViewer/loaded')
}
}
},
},
}
</script>

View File

@ -1,20 +1,24 @@
<template>
<el-dialog
:title="$t('modals.mute_confirm.title')"
:visible.sync="muteConfirmModal"
width="400px"
custom-class="mute-confirm"
<el-dialog
:title="$t('modals.mute_confirm.title')"
:visible.sync="muteConfirmModal"
width="400px"
custom-class="mute-confirm"
>
<el-form class="description">
<el-form-item for="notify" :label="$t('modals.mute_confirm.body')">
<el-switch id="notify" v-model="notify"></el-switch>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">{{ $t('modals.mute_confirm.cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ $t('modals.mute_confirm.ok') }}</el-button>
</span>
</el-dialog>
<el-form class="description">
<el-form-item for="notify" :label="$t('modals.mute_confirm.body')">
<el-switch id="notify" v-model="notify"></el-switch>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">{{
$t('modals.mute_confirm.cancel')
}}</el-button>
<el-button type="primary" @click="submit">{{
$t('modals.mute_confirm.ok')
}}</el-button>
</span>
</el-dialog>
</template>
<script>
@ -22,33 +26,39 @@ import { mapState } from 'vuex'
export default {
name: 'MuteConfirm',
data () {
data() {
return {
notify: true
notify: true,
}
},
computed: {
...mapState('TimelineSpace/Modals/MuteConfirm', {
account: state => state.account
account: (state) => state.account,
}),
muteConfirmModal: {
get () {
get() {
return this.$store.state.TimelineSpace.Modals.MuteConfirm.modalOpen
},
set (value) {
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeModal', value)
}
}
set(value) {
this.$store.dispatch(
'TimelineSpace/Modals/MuteConfirm/changeModal',
value
)
},
},
},
methods: {
closeModal () {
closeModal() {
this.muteConfirmModal = false
},
async submit () {
async submit() {
this.closeModal()
await this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/submit', this.notify)
}
}
await this.$store.dispatch(
'TimelineSpace/Modals/MuteConfirm/submit',
this.notify
)
},
},
}
</script>

View File

@ -9,10 +9,21 @@
ref="dialog"
>
<el-form v-on:submit.prevent="toot" role="form">
<Quote :message="quoteToMessage" :displayNameStyle="displayNameStyle" v-if="quoteToMessage !== null" ref="quote"></Quote>
<Quote
:message="quoteToMessage"
:displayNameStyle="displayNameStyle"
v-if="quoteToMessage !== null"
ref="quote"
></Quote>
<div class="spoiler" v-if="showContentWarning" ref="spoiler">
<div class="el-input">
<input type="text" class="el-input__inner" :placeholder="$t('modals.new_toot.cw')" v-model="spoiler" v-shortkey.avoid />
<input
type="text"
class="el-input__inner"
:placeholder="$t('modals.new_toot.cw')"
v-model="spoiler"
v-shortkey.avoid
/>
</div>
</div>
<Status
@ -36,9 +47,18 @@
ref="poll"
></Poll>
<div class="preview" ref="preview">
<div class="image-wrapper" v-for="media in attachedMedias" v-bind:key="media.id">
<div
class="image-wrapper"
v-for="media in attachedMedias"
v-bind:key="media.id"
>
<img :src="media.preview_url" class="preview-image" />
<el-button type="text" @click="removeAttachment(media)" class="remove-image"><font-awesome-icon icon="circle-xmark" /></el-button>
<el-button
type="text"
@click="removeAttachment(media)"
class="remove-image"
><font-awesome-icon icon="circle-xmark"
/></el-button>
<textarea
maxlength="420"
class="image-description"
@ -55,19 +75,40 @@
</div>
<div slot="footer" class="dialog-footer">
<div class="upload-image">
<el-button size="default" type="text" @click="selectImage" :title="$t('modals.new_toot.footer.add_image')">
<el-button
size="default"
type="text"
@click="selectImage"
:title="$t('modals.new_toot.footer.add_image')"
>
<font-awesome-icon icon="camera" />
</el-button>
<input name="image" type="file" class="image-input" ref="image" @change="onChangeImage" :key="attachedMediaId" />
<input
name="image"
type="file"
class="image-input"
ref="image"
@change="onChangeImage"
:key="attachedMediaId"
/>
</div>
<div class="poll">
<el-button size="default" type="text" @click="togglePollForm" :title="$t('modals.new_toot.footer.poll')">
<el-button
size="default"
type="text"
@click="togglePollForm"
:title="$t('modals.new_toot.footer.poll')"
>
<font-awesome-icon icon="square-poll-horizontal" />
</el-button>
</div>
<div class="privacy">
<el-dropdown trigger="click" @command="changeVisibility">
<el-button size="default" type="text" :title="$t('modals.new_toot.footer.change_visibility')">
<el-button
size="default"
type="text"
:title="$t('modals.new_toot.footer.change_visibility')"
>
<font-awesome-icon :icon="visibilityIcon" />
</el-button>
<el-dropdown-menu slot="dropdown">
@ -84,7 +125,11 @@
{{ $t(visibilityList.Private.name) }}
</el-dropdown-item>
<el-dropdown-item :command="visibilityList.Direct.value">
<font-awesome-icon icon="envelope" class="privacy-icon" size="sm" />
<font-awesome-icon
icon="envelope"
class="privacy-icon"
size="sm"
/>
{{ $t(visibilityList.Direct.name) }}
</el-dropdown-item>
</el-dropdown-menu>
@ -127,13 +172,27 @@
</el-button>
</div>
<div class="info">
<img src="../../../assets/images/loading-spinner-wide.svg" v-show="loading" class="loading" />
<img
src="../../../assets/images/loading-spinner-wide.svg"
v-show="loading"
class="loading"
/>
<span class="text-count">{{ tootMax - status.length }}</span>
<el-button class="toot-action" size="small" @click="closeConfirm(close)">{{ $t('modals.new_toot.cancel') }}</el-button>
<el-button class="toot-action" size="small" type="primary" @click="toot" :loading="blockSubmit">{{
$t('modals.new_toot.toot')
}}</el-button>
<el-button
class="toot-action"
size="small"
@click="closeConfirm(close)"
>{{ $t('modals.new_toot.cancel') }}</el-button
>
<el-button
class="toot-action"
size="small"
type="primary"
@click="toot"
:loading="blockSubmit"
>{{ $t('modals.new_toot.toot') }}</el-button
>
</div>
<div class="clearfix"></div>
</div>
@ -147,7 +206,13 @@ import Visibility from '~/src/constants/visibility'
import Status from './NewToot/Status'
import Poll from './NewToot/Poll'
import Quote from './NewToot/Quote'
import { NewTootTootLength, NewTootAttachLength, NewTootModalOpen, NewTootBlockSubmit, NewTootPollInvalid } from '@/errors/validations'
import {
NewTootTootLength,
NewTootAttachLength,
NewTootModalOpen,
NewTootBlockSubmit,
NewTootPollInvalid,
} from '@/errors/validations'
import { Event } from '~/src/renderer/components/event'
export default {
@ -155,7 +220,7 @@ export default {
components: {
Status,
Poll,
Quote
Quote,
},
data() {
return {
@ -167,30 +232,30 @@ export default {
polls: [],
pollExpire: {
label: this.$t('modals.new_toot.poll.expires.1_day'),
value: 3600 * 24
value: 3600 * 24,
},
statusHeight: 240
statusHeight: 240,
}
},
computed: {
...mapState('TimelineSpace/Modals/NewToot', {
replyToId: state => {
replyToId: (state) => {
if (state.replyToMessage !== null) {
return state.replyToMessage.id
} else {
return null
}
},
quoteToMessage: state => state.quoteToMessage,
attachedMedias: state => state.attachedMedias,
attachedMediaId: state => state.attachedMediaId,
mediaDescriptions: state => state.mediaDescriptions,
blockSubmit: state => state.blockSubmit,
visibility: state => state.visibility,
sensitive: state => state.sensitive,
initialStatus: state => state.initialStatus,
initialSpoiler: state => state.initialSpoiler,
visibilityIcon: state => {
quoteToMessage: (state) => state.quoteToMessage,
attachedMedias: (state) => state.attachedMedias,
attachedMediaId: (state) => state.attachedMediaId,
mediaDescriptions: (state) => state.mediaDescriptions,
blockSubmit: (state) => state.blockSubmit,
visibility: (state) => state.visibility,
sensitive: (state) => state.sensitive,
initialStatus: (state) => state.initialStatus,
initialSpoiler: (state) => state.initialSpoiler,
visibilityIcon: (state) => {
switch (state.visibility) {
case Visibility.Public.value:
return 'globe'
@ -204,13 +269,13 @@ export default {
return 'globe'
}
},
loading: state => state.loading
loading: (state) => state.loading,
}),
...mapState('TimelineSpace', {
tootMax: state => state.tootMax
tootMax: (state) => state.tootMax,
}),
...mapState('App', {
displayNameStyle: state => state.displayNameStyle
displayNameStyle: (state) => state.displayNameStyle,
}),
...mapGetters('TimelineSpace/Modals/NewToot', ['hashtagInserting']),
newTootModal: {
@ -223,16 +288,19 @@ export default {
} else {
this.$store.dispatch('TimelineSpace/Modals/NewToot/closeModal')
}
}
},
},
pinedHashtag: {
get() {
return this.$store.state.TimelineSpace.Modals.NewToot.pinedHashtag
},
set(value) {
this.$store.commit('TimelineSpace/Modals/NewToot/changePinedHashtag', value)
}
}
this.$store.commit(
'TimelineSpace/Modals/NewToot/changePinedHashtag',
value
)
},
},
},
created() {
this.$store.dispatch('TimelineSpace/Modals/NewToot/setupLoading')
@ -251,12 +319,14 @@ export default {
this.status = this.initialStatus
this.spoiler = this.initialSpoiler
}
}
},
},
methods: {
close() {
this.filteredAccount = []
const spoilerHeight = this.$refs.spoiler ? this.$refs.spoiler.offsetHeight : 0
const spoilerHeight = this.$refs.spoiler
? this.$refs.spoiler.offsetHeight
: 0
this.showContentWarning = false
this.spoiler = ''
this.statusHeight = this.statusHeight + spoilerHeight
@ -265,12 +335,16 @@ export default {
this.polls = []
this.pollExpire = {
label: this.$t('modals.new_toot.poll.expires.1_day'),
value: 3600 * 24
value: 3600 * 24,
}
this.statusHeight = this.statusHeight + pollHeight
const quoteHeight = this.$refs.quote ? this.$refs.quote.$el.offsetHeight : 0
const quoteHeight = this.$refs.quote
? this.$refs.quote.$el.offsetHeight
: 0
this.statusHeight = this.statusHeight + quoteHeight
const attachmentHeight = this.$refs.preview ? this.$refs.preview.offsetHeight : 0
const attachmentHeight = this.$refs.preview
? this.$refs.preview.offsetHeight
: 0
this.statusHeight = this.statusHeight + attachmentHeight
this.$store.dispatch('TimelineSpace/Modals/NewToot/resetMediaCount')
this.$store.dispatch('TimelineSpace/Modals/NewToot/closeModal')
@ -280,36 +354,48 @@ export default {
status: this.status,
spoiler: this.spoiler,
polls: this.polls,
pollExpireSeconds: this.pollExpire.value
pollExpireSeconds: this.pollExpire.value,
}
try {
const status = await this.$store.dispatch('TimelineSpace/Modals/NewToot/postToot', form)
this.$store.dispatch('TimelineSpace/Modals/NewToot/updateHashtags', status.tags)
const status = await this.$store.dispatch(
'TimelineSpace/Modals/NewToot/postToot',
form
)
this.$store.dispatch(
'TimelineSpace/Modals/NewToot/updateHashtags',
status.tags
)
this.close()
} catch (err) {
console.error(err)
if (err instanceof NewTootTootLength) {
this.$message({
message: this.$t('validation.new_toot.toot_length', { min: 1, max: this.tootMax }),
type: 'error'
message: this.$t('validation.new_toot.toot_length', {
min: 1,
max: this.tootMax,
}),
type: 'error',
})
} else if (err instanceof NewTootAttachLength) {
this.$message({
message: this.$t('validation.new_toot.attach_length', { max: 4 }),
type: 'error'
type: 'error',
})
} else if (err instanceof NewTootPollInvalid) {
this.$message({
message: this.$t('validation.new_toot.poll_invalid'),
type: 'error'
type: 'error',
})
} else if (err instanceof NewTootModalOpen || err instanceof NewTootBlockSubmit) {
} else if (
err instanceof NewTootModalOpen ||
err instanceof NewTootBlockSubmit
) {
// Nothing
} else {
this.$message({
message: this.$t('message.toot_error'),
type: 'error'
type: 'error',
})
}
}
@ -318,21 +404,26 @@ export default {
this.$refs.image.click()
},
onChangeImage(e) {
if (e.target.files.item(0) === null || e.target.files.item(0) === undefined) {
if (
e.target.files.item(0) === null ||
e.target.files.item(0) === undefined
) {
return
}
const file = e.target.files.item(0)
if (!file.type.includes('image') && !file.type.includes('video')) {
this.$message({
message: this.$t('validation.new_toot.attach_image'),
type: 'error'
type: 'error',
})
return
}
this.updateImage(file)
},
onPaste(e) {
const mimeTypes = window.clipboard.availableFormats().filter(type => type.startsWith('image'))
const mimeTypes = window.clipboard
.availableFormats()
.filter((type) => type.startsWith('image'))
if (mimeTypes.length === 0) {
return
}
@ -352,33 +443,42 @@ export default {
this.$store
.dispatch('TimelineSpace/Modals/NewToot/uploadImage', file)
.then(() => {
this.statusHeight = this.statusHeight - this.$refs.preview.offsetHeight
this.statusHeight =
this.statusHeight - this.$refs.preview.offsetHeight
})
.catch(err => {
.catch((err) => {
if (err instanceof NewTootAttachLength) {
this.$message({
message: this.$t('validation.new_toot.attach_length', { max: 4 }),
type: 'error'
type: 'error',
})
} else {
this.$message({
message: this.$t('message.attach_error'),
type: 'error'
type: 'error',
})
}
})
},
removeAttachment(media) {
const previousHeight = this.$refs.preview.offsetHeight
this.$store.dispatch('TimelineSpace/Modals/NewToot/removeMedia', media).then(() => {
this.statusHeight = this.statusHeight + previousHeight
})
this.$store
.dispatch('TimelineSpace/Modals/NewToot/removeMedia', media)
.then(() => {
this.statusHeight = this.statusHeight + previousHeight
})
},
changeVisibility(level) {
this.$store.commit('TimelineSpace/Modals/NewToot/changeVisibilityValue', level)
this.$store.commit(
'TimelineSpace/Modals/NewToot/changeVisibilityValue',
level
)
},
changeSensitive() {
this.$store.commit('TimelineSpace/Modals/NewToot/changeSensitive', !this.sensitive)
this.$store.commit(
'TimelineSpace/Modals/NewToot/changeSensitive',
!this.sensitive
)
},
closeConfirm(done) {
if (this.status.length === 0) {
@ -386,19 +486,24 @@ export default {
} else {
this.$confirm(this.$t('modals.new_toot.close_confirm'), {
confirmButtonText: this.$t('modals.new_toot.close_confirm_ok'),
cancelButtonText: this.$t('modals.new_toot.close_confirm_cancel')
cancelButtonText: this.$t('modals.new_toot.close_confirm_cancel'),
})
.then(_ => {
.then((_) => {
done()
})
.catch(_ => {})
.catch((_) => {})
}
},
updateDescription(id, value) {
this.$store.commit('TimelineSpace/Modals/NewToot/updateMediaDescription', { id: id, description: value })
this.$store.commit(
'TimelineSpace/Modals/NewToot/updateMediaDescription',
{ id: id, description: value }
)
},
async togglePollForm() {
const previousHeight = this.$refs.poll ? this.$refs.poll.$el.offsetHeight : 0
const previousHeight = this.$refs.poll
? this.$refs.poll.$el.offsetHeight
: 0
const toggle = () => {
this.openPoll = !this.openPoll
if (this.openPoll) {
@ -430,7 +535,9 @@ export default {
this.pollExpire = obj
},
async toggleContentWarning() {
const previousHeight = this.$refs.spoiler ? this.$refs.spoiler.offsetHeight : 0
const previousHeight = this.$refs.spoiler
? this.$refs.spoiler.offsetHeight
: 0
await (this.showContentWarning = !this.showContentWarning)
if (this.showContentWarning) {
this.statusHeight = this.statusHeight - this.$refs.spoiler.offsetHeight
@ -441,7 +548,8 @@ export default {
handleResize(event) {
// Ignore when the modal height already reach window height.
const vHeight = this.$refs.dialog.$el.firstChild.style.marginTop
const marginTop = (document.documentElement.clientHeight / 100) * parseInt(vHeight)
const marginTop =
(document.documentElement.clientHeight / 100) * parseInt(vHeight)
const limitHeight = document.documentElement.clientHeight - marginTop - 80
if (this.$refs.dialog.$el.firstChild.offsetHeight >= limitHeight) {
return
@ -449,13 +557,25 @@ export default {
// When emoji picker is opened, resize event has to be stopped.
const style = this.$refs.dialog.$el.firstChild.style
if (style.overflow === '' || style.overflow === 'hidden') {
const pollHeight = this.$refs.poll ? this.$refs.poll.$el.offsetHeight : 0
const spoilerHeight = this.$refs.spoiler ? this.$refs.spoiler.offsetHeight : 0
const quoteHeight = this.$refs.quote ? this.$refs.quote.$el.offsetHeight : 0
const pollHeight = this.$refs.poll
? this.$refs.poll.$el.offsetHeight
: 0
const spoilerHeight = this.$refs.spoiler
? this.$refs.spoiler.offsetHeight
: 0
const quoteHeight = this.$refs.quote
? this.$refs.quote.$el.offsetHeight
: 0
const headerHeight = 54
const footerHeight = 63
this.statusHeight =
event.height - footerHeight - headerHeight - this.$refs.preview.offsetHeight - pollHeight - spoilerHeight - quoteHeight
event.height -
footerHeight -
headerHeight -
this.$refs.preview.offsetHeight -
pollHeight -
spoilerHeight -
quoteHeight
}
},
innerElementOpened(open) {
@ -464,8 +584,8 @@ export default {
} else {
this.$refs.dialog.$el.firstChild.style.overflow = 'hidden'
}
}
}
},
},
}
</script>
@ -541,7 +661,12 @@ export default {
border: 0;
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
background: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 0, rgba(0, 0, 0, 0.35) 80%, transparent);
background: linear-gradient(
0deg,
rgba(0, 0, 0, 0.8) 0,
rgba(0, 0, 0, 0.35) 80%,
transparent
);
font-size: var(--font-base-size);
color: #fff;
opacity: 1;

View File

@ -3,14 +3,43 @@
<ul class="poll-list">
<li class="poll-option" v-for="(option, id) in value" v-bind:key="id">
<el-radio :disabled="true" :label="id">
<el-input :placeholder="`choice ${id}`" :value="value[id]" @input="value => updateOption(value, id)" size="small"></el-input>
<el-button class="remove-poll" type="text" @click="removePoll(id)" size="small"><font-awesome-icon icon="xmark" /></el-button>
<el-input
:placeholder="`choice ${id}`"
:model-value="value[id]"
@input="(value) => updateOption(value, id)"
size="small"
></el-input>
<el-button
class="remove-poll"
type="text"
@click="removePoll(id)"
size="small"
><font-awesome-icon icon="xmark"
/></el-button>
</el-radio>
</li>
</ul>
<el-button class="add-poll" type="info" size="small" @click="addPoll" plain>{{ $t('modals.new_toot.poll.add_choice') }}</el-button>
<el-select v-model="expiresIn" size="small" value-key="value" @change="changeExpire">
<el-option v-for="exp in expires" :key="exp.value" :label="exp.label" :value="exp"> </el-option>
<el-button
class="add-poll"
type="info"
size="small"
@click="addPoll"
plain
>{{ $t('modals.new_toot.poll.add_choice') }}</el-button
>
<el-select
v-model="expiresIn"
size="small"
value-key="value"
@change="changeExpire"
>
<el-option
v-for="exp in expires"
:key="exp.value"
:label="exp.label"
:value="exp"
>
</el-option>
</el-select>
</div>
</template>
@ -24,34 +53,34 @@ export default {
expires: [
{
label: this.$t('modals.new_toot.poll.expires.5_minutes'),
value: 60 * 5
value: 60 * 5,
},
{
label: this.$t('modals.new_toot.poll.expires.30_minutes'),
value: 60 * 30
value: 60 * 30,
},
{
label: this.$t('modals.new_toot.poll.expires.1_hour'),
value: 3600
value: 3600,
},
{
label: this.$t('modals.new_toot.poll.expires.6_hours'),
value: 3600 * 6
value: 3600 * 6,
},
{
label: this.$t('modals.new_toot.poll.expires.1_day'),
value: 3600 * 24
value: 3600 * 24,
},
{
label: this.$t('modals.new_toot.poll.expires.3_days'),
value: 3600 * 24 * 3
value: 3600 * 24 * 3,
},
{
label: this.$t('modals.new_toot.poll.expires.7_days'),
value: 3600 * 24 * 7
}
value: 3600 * 24 * 7,
},
],
expiresIn: null
expiresIn: null,
}
},
created() {
@ -65,13 +94,17 @@ export default {
this.$emit('removePoll', id)
},
updateOption(item, index) {
const newValue = [...this.value.slice(0, index), item, ...this.value.slice(index + 1)]
const newValue = [
...this.value.slice(0, index),
item,
...this.value.slice(index + 1),
]
this.$emit('input', newValue)
},
changeExpire(exp) {
this.$emit('changeExpire', exp)
}
}
},
},
}
</script>

View File

@ -6,14 +6,22 @@
<div class="detail">
<div class="toot-header">
<div class="user">
<span class="display-name"><bdi v-html="username(message.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(message.account)"></bdi
></span>
<span class="acct">{{ accountName(message.account) }}</span>
</div>
<div class="clearfix"></div>
</div>
<div class="content-wrapper">
<div class="spoiler" v-html="emojiText(message.spoiler_text, message.emojis)"></div>
<div class="content" v-html="emojiText(message.content, message.emojis)"></div>
<div
class="spoiler"
v-html="emojiText(message.spoiler_text, message.emojis)"
></div>
<div
class="content"
v-html="emojiText(message.content, message.emojis)"
></div>
</div>
</div>
<div class="clearfix"></div>
@ -28,17 +36,17 @@ import emojify from '@/utils/emojify'
export default {
new: 'quote-target',
components: {
FailoverImg
FailoverImg,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
displayNameStyle: {
type: Number,
default: 0
}
default: 0,
},
},
methods: {
username(account) {
@ -70,8 +78,8 @@ export default {
},
emojiText(content, emojis) {
return emojify(content, emojis)
}
}
},
},
}
</script>

View File

@ -5,8 +5,18 @@
ref="status"
v-shortkey="
openSuggest
? { up: ['arrowup'], down: ['arrowdown'], enter: ['enter'], esc: ['esc'] }
: { linux: ['ctrl', 'enter'], mac: ['meta', 'enter'], left: ['arrowleft'], right: ['arrowright'] }
? {
up: ['arrowup'],
down: ['arrowdown'],
enter: ['enter'],
esc: ['esc'],
}
: {
linux: ['ctrl', 'enter'],
mac: ['meta', 'enter'],
left: ['arrowleft'],
right: ['arrowright'],
}
"
@shortkey="handleKey"
@paste="onPaste"
@ -19,7 +29,13 @@
autofocus
>
</textarea>
<el-popover placement="bottom-start" width="300" trigger="manual" :value="openSuggest" popper-class="suggest-popper">
<el-popover
placement="bottom-start"
width="300"
trigger="manual"
:model-value="openSuggest"
popper-class="suggest-popper"
>
<ul class="suggest-list">
<li
v-for="(item, index) in filteredSuggestion"
@ -44,7 +60,12 @@
<font-awesome-icon :icon="['far', 'face-smile']" size="lg" />
</el-button>
<div v-if="openEmojiPicker" class="emoji-picker">
<picker set="emojione" :autoFocus="true" :custom="pickerEmojis" @select="selectEmoji" />
<picker
set="emojione"
:autoFocus="true"
:custom="pickerEmojis"
@select="selectEmoji"
/>
</div>
</div>
</div>
@ -59,42 +80,42 @@ import suggestText from '@/utils/suggestText'
export default {
name: 'status',
directives: {
ClickOutside
ClickOutside,
},
components: {
Picker
Picker,
},
props: {
value: {
type: String
type: String,
},
opened: {
type: Boolean,
default: false
default: false,
},
fixCursorPos: {
type: Boolean,
default: false
default: false,
},
height: {
type: Number,
default: 120
}
default: 120,
},
},
data() {
return {
highlightedIndex: 0,
openEmojiPicker: false
openEmojiPicker: false,
}
},
computed: {
...mapState('TimelineSpace/Modals/NewToot/Status', {
filteredAccounts: state => state.filteredAccounts,
filteredHashtags: state => state.filteredHashtags,
openSuggest: state => state.openSuggest,
startIndex: state => state.startIndex,
matchWord: state => state.matchWord,
filteredSuggestion: state => state.filteredSuggestion
filteredAccounts: (state) => state.filteredAccounts,
filteredHashtags: (state) => state.filteredHashtags,
openSuggest: (state) => state.openSuggest,
startIndex: (state) => state.startIndex,
matchWord: (state) => state.matchWord,
filteredSuggestion: (state) => state.filteredSuggestion,
}),
...mapGetters('TimelineSpace/Modals/NewToot/Status', ['pickerEmojis']),
status: {
@ -103,8 +124,8 @@ export default {
},
set: function (value) {
this.$emit('input', value)
}
}
},
},
},
mounted() {
// When change account, the new toot modal is recreated.
@ -127,7 +148,7 @@ export default {
this.closeSuggest()
this.hideEmojiPicker()
}
}
},
},
methods: {
async startSuggest(e) {
@ -163,7 +184,10 @@ export default {
},
async suggestAccount(start, word) {
try {
await this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestAccount', { word: word, start: start })
await this.$store.dispatch(
'TimelineSpace/Modals/NewToot/Status/suggestAccount',
{ word: word, start: start }
)
this.$emit('suggestOpened', true)
return true
} catch (err) {
@ -173,7 +197,10 @@ export default {
},
async suggestHashtag(start, word) {
try {
await this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestHashtag', { word: word, start: start })
await this.$store.dispatch(
'TimelineSpace/Modals/NewToot/Status/suggestHashtag',
{ word: word, start: start }
)
this.$emit('suggestOpened', true)
return true
} catch (err) {
@ -183,7 +210,10 @@ export default {
},
suggestEmoji(start, word) {
try {
this.$store.dispatch('TimelineSpace/Modals/NewToot/Status/suggestEmoji', { word: word, start: start })
this.$store.dispatch(
'TimelineSpace/Modals/NewToot/Status/suggestEmoji',
{ word: word, start: start }
)
this.$emit('suggestOpened', true)
return true
} catch (err) {
@ -209,10 +239,14 @@ export default {
},
insertItem(item) {
if (item.code) {
const str = `${this.status.slice(0, this.startIndex - 1)}${item.code} ${this.status.slice(this.startIndex + this.matchWord.length)}`
const str = `${this.status.slice(0, this.startIndex - 1)}${
item.code
} ${this.status.slice(this.startIndex + this.matchWord.length)}`
this.status = str
} else {
const str = `${this.status.slice(0, this.startIndex - 1)}${item.name} ${this.status.slice(this.startIndex + this.matchWord.length)}`
const str = `${this.status.slice(0, this.startIndex - 1)}${
item.name
} ${this.status.slice(this.startIndex + this.matchWord.length)}`
this.status = str
}
this.closeSuggest()
@ -266,14 +300,18 @@ export default {
selectEmoji(emoji) {
const current = this.$refs.status.selectionStart
if (emoji.native) {
this.status = `${this.status.slice(0, current)}${emoji.native} ${this.status.slice(current)}`
this.status = `${this.status.slice(0, current)}${
emoji.native
} ${this.status.slice(current)}`
} else {
// Custom emoji don't have natvie code
this.status = `${this.status.slice(0, current)}${emoji.name} ${this.status.slice(current)}`
this.status = `${this.status.slice(0, current)}${
emoji.name
} ${this.status.slice(current)}`
}
this.hideEmojiPicker()
}
}
},
},
}
</script>

View File

@ -1,16 +1,24 @@
<template>
<el-dialog
:title="$t('modals.report.title')"
:visible.sync="reportModal"
width="400px"
custom-class="report"
<el-dialog
:title="$t('modals.report.title')"
:visible.sync="reportModal"
width="400px"
custom-class="report"
>
<el-input type="textarea" v-model="comment" :placeholder="$t('modals.report.comment')"></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">{{ $t('modals.report.cancel') }}</el-button>
<el-button type="primary" @click="submit">{{ $t('modals.report.ok') }}</el-button>
</span>
</el-dialog>
<el-input
type="textarea"
v-model="comment"
:placeholder="$t('modals.report.comment')"
></el-input>
<span slot="footer" class="dialog-footer">
<el-button @click="closeModal">{{
$t('modals.report.cancel')
}}</el-button>
<el-button type="primary" @click="submit">{{
$t('modals.report.ok')
}}</el-button>
</span>
</el-dialog>
</template>
<script>
@ -18,39 +26,38 @@ import { mapState } from 'vuex'
export default {
name: 'Report',
data () {
data() {
return {
comment: ''
comment: '',
}
},
computed: {
...mapState('TimelineSpace/Modals/Report', {
toot: state => state.message
toot: (state) => state.message,
}),
reportModal: {
get () {
get() {
return this.$store.state.TimelineSpace.Modals.Report.modalOpen
},
set (value) {
set(value) {
this.$store.commit('TimelineSpace/Modals/Report/changeModalOpen', value)
}
}
},
},
},
methods: {
closeModal () {
closeModal() {
this.reportModal = false
},
async submit () {
async submit() {
this.closeModal()
await this.$store.dispatch('TimelineSpace/Modals/Report/submit', {
account_id: this.toot.account.id,
status_id: this.toot.id,
comment: this.comment
comment: this.comment,
})
}
}
},
},
}
</script>
<style lang="scss" scoped>
</style>
<style lang="scss" scoped></style>

View File

@ -1,5 +1,10 @@
<template>
<el-dialog :title="$t('modals.shortcut.title')" :visible.sync="shortcutModal" width="500px" class="shortcut-modal">
<el-dialog
:title="$t('modals.shortcut.title')"
:visible.sync="shortcutModal"
width="500px"
class="shortcut-modal"
>
<table class="shortcuts">
<tbody>
<tr>
@ -89,9 +94,9 @@ export default {
},
set(value) {
this.$store.commit('TimelineSpace/Modals/Shortcut/changeModal', value)
}
}
}
},
},
},
}
</script>

View File

@ -1,16 +1,16 @@
<template>
<div id="receive_drop">
<div class="drop-area">
<div class="drop-message">
<h1>{{ $t('receive_drop.drop_message') }}</h1>
<div id="receive_drop">
<div class="drop-area">
<div class="drop-message">
<h1>{{ $t('receive_drop.drop_message') }}</h1>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'receive-drop'
name: 'receive-drop',
}
</script>

View File

@ -1,6 +1,9 @@
<template>
<div id="side_menu">
<div :class="collapse ? 'profile-wrapper narrow-menu' : 'profile-wrapper'" style="-webkit-app-region: drag">
<div
:class="collapse ? 'profile-wrapper narrow-menu' : 'profile-wrapper'"
style="-webkit-app-region: drag"
>
<div :class="collapse ? 'profile-narrow' : 'profile-wide'">
<div class="account">
<div class="avatar" v-if="collapse">
@ -10,23 +13,45 @@
@{{ account.username }}
<span class="domain-name">{{ account.domain }}</span>
</div>
<el-dropdown trigger="click" @command="handleProfile" :title="$t('side_menu.profile')">
<el-dropdown
trigger="click"
@command="handleProfile"
:title="$t('side_menu.profile')"
>
<span class="el-dropdown-link">
<i class="el-icon-arrow-down el-icon--right"></i>
<el-icon class="el-icon--right"><el-icon-arrow-down /></el-icon>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="show">{{ $t('side_menu.show_profile') }}</el-dropdown-item>
<el-dropdown-item command="edit">{{ $t('side_menu.edit_profile') }}</el-dropdown-item>
<el-dropdown-item command="settings">{{ $t('side_menu.settings') }}</el-dropdown-item>
<el-dropdown-item command="show">{{
$t('side_menu.show_profile')
}}</el-dropdown-item>
<el-dropdown-item command="edit">{{
$t('side_menu.edit_profile')
}}</el-dropdown-item>
<el-dropdown-item command="settings">{{
$t('side_menu.settings')
}}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
</div>
<div class="collapse">
<el-button type="text" class="release-collapse" @click="releaseCollapse" v-if="collapse" :title="$t('side_menu.expand')">
<el-button
type="text"
class="release-collapse"
@click="releaseCollapse"
v-if="collapse"
:title="$t('side_menu.expand')"
>
<font-awesome-icon :icon="['fa', 'angles-right']" />
</el-button>
<el-button type="text" class="do-collapse" @click="doCollapse" v-else :title="$t('side_menu.collapse')">
<el-button
type="text"
class="do-collapse"
@click="doCollapse"
v-else
:title="$t('side_menu.collapse')"
>
<font-awesome-icon :icon="['fa', 'angles-left']" />
</el-button>
</div>
@ -38,10 +63,19 @@
:collapse="collapse"
active-text-color="#ffffff"
:router="true"
:class="collapse ? 'el-menu-vertical timeline-menu narrow-menu' : 'el-menu-vertical timeline-menu'"
:class="
collapse
? 'el-menu-vertical timeline-menu narrow-menu'
: 'el-menu-vertical timeline-menu'
"
role="menu"
>
<el-menu-item :index="`/${id()}/home`" role="menuitem" :title="$t('side_menu.home')" class="menu-item">
<el-menu-item
:index="`/${id()}/home`"
role="menuitem"
:title="$t('side_menu.home')"
class="menu-item"
>
<div class="menu-item-icon">
<font-awesome-icon icon="home" />
</div>
@ -168,7 +202,12 @@
<el-badge is-dot :hidden="!unreadPublicTimeline"> </el-badge>
</div>
</el-menu-item>
<el-menu-item :index="`/${id()}/search`" role="menuitem" :title="$t('side_menu.search')" class="menu-item">
<el-menu-item
:index="`/${id()}/search`"
role="menuitem"
:title="$t('side_menu.search')"
class="menu-item"
>
<div class="menu-item-icon">
<font-awesome-icon icon="magnifying-glass" />
</div>
@ -176,7 +215,12 @@
<span>{{ $t('side_menu.search') }}</span>
</div>
</el-menu-item>
<el-menu-item :index="`/${id()}/hashtag`" role="menuitem" :title="$t('side_menu.hashtag')" class="menu-item">
<el-menu-item
:index="`/${id()}/hashtag`"
role="menuitem"
:title="$t('side_menu.hashtag')"
class="menu-item"
>
<div class="menu-item-icon">
<font-awesome-icon icon="hashtag" />
</div>
@ -201,7 +245,12 @@
</div>
</el-menu-item>
</template>
<el-menu-item :index="`/${id()}/lists`" role="menuitem" :title="$t('side_menu.lists')" class="menu-item">
<el-menu-item
:index="`/${id()}/lists`"
role="menuitem"
:title="$t('side_menu.lists')"
class="menu-item"
>
<div class="menu-item-icon">
<font-awesome-icon icon="list-ul" />
</div>
@ -227,39 +276,54 @@
</el-menu-item>
</template>
</el-menu>
<el-button v-if="hideGlobalHeader" class="global-header-control" type="text" @click="changeGlobalHeader(false)">
<el-button
v-if="hideGlobalHeader"
class="global-header-control"
type="text"
@click="changeGlobalHeader(false)"
>
<font-awesome-icon icon="angles-right" />
</el-button>
<el-button v-else class="global-header-control" type="text" @click="changeGlobalHeader(true)">
<el-button
v-else
class="global-header-control"
type="text"
@click="changeGlobalHeader(true)"
>
<font-awesome-icon icon="angles-left" />
</el-button>
</div>
</template>
<script>
import { ArrowDown as ElIconArrowDown } from '@element-plus/icons'
import { mapState } from 'vuex'
export default {
components: {
ElIconArrowDown,
},
name: 'side-menu',
computed: {
...mapState('TimelineSpace/SideMenu', {
unreadHomeTimeline: state => state.unreadHomeTimeline,
unreadNotifications: state => state.unreadNotifications,
unreadMentions: state => state.unreadMentions,
unreadLocalTimeline: state => state.unreadLocalTimeline,
unreadDirectMessagesTimeline: state => state.unreadDirectMessagesTimeline,
unreadPublicTimeline: state => state.unreadPublicTimeline,
unreadFollowRequests: state => state.unreadFollowRequests,
lists: state => state.lists,
tags: state => state.tags,
collapse: state => state.collapse,
enabledTimelines: state => state.enabledTimelines
unreadHomeTimeline: (state) => state.unreadHomeTimeline,
unreadNotifications: (state) => state.unreadNotifications,
unreadMentions: (state) => state.unreadMentions,
unreadLocalTimeline: (state) => state.unreadLocalTimeline,
unreadDirectMessagesTimeline: (state) =>
state.unreadDirectMessagesTimeline,
unreadPublicTimeline: (state) => state.unreadPublicTimeline,
unreadFollowRequests: (state) => state.unreadFollowRequests,
lists: (state) => state.lists,
tags: (state) => state.tags,
collapse: (state) => state.collapse,
enabledTimelines: (state) => state.enabledTimelines,
}),
...mapState({
account: state => state.TimelineSpace.account,
themeColor: state => state.App.theme.side_menu_color,
hideGlobalHeader: state => state.GlobalHeader.hide
})
account: (state) => state.TimelineSpace.account,
themeColor: (state) => state.App.theme.side_menu_color,
hideGlobalHeader: (state) => state.GlobalHeader.hide,
}),
},
created() {
this.$store.dispatch('TimelineSpace/SideMenu/readCollapse')
@ -275,12 +339,25 @@ export default {
handleProfile(command) {
switch (command) {
case 'show':
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/fetchAccount', this.account.accountId).then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
})
this.$store
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/fetchAccount',
this.account.accountId
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
})
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
break
case 'edit':
window.shell.openExternal(this.account.baseURL + '/settings/profile')
@ -300,8 +377,8 @@ export default {
},
async changeGlobalHeader(value) {
await this.$store.dispatch('GlobalHeader/switchHide', value)
}
}
},
},
}
</script>

View File

@ -18,30 +18,30 @@ export default {
props: {
src: {
type: String,
default: ''
default: '',
},
title: {
type: String,
default: ''
default: '',
},
alt: {
type: String,
default: ''
default: '',
},
readExif: {
type: Boolean,
default: false
default: false,
},
failoverSrc: {
type: String,
default:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAIAAAD2HxkiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACvtJREFUeNrs3WtrFEkbgOGsCS4GBEVREBIUAgFBQfD//4EIQgQhoCQkICgGhUDEQGAfLN6i3p6ZPsXp6Z5c14dl3cQ5dPfdVX2Y2X8ODg42gNW5YxGACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEIg21rS425ubm5vb4/qrV5fX19eXlrl3JYIo8D9/f1RvdWLi4ujoyOrHNNRQIQgQmDpx4QVqzoY29nZGdv5IVhNhBcXFyt5e9fX19YxpqOACEGEwIqPCee6f//+06dP45+bm5tx0Hh+fv79+3erBBEOZGdnJwosgwx379798uXLbVsH8ca3t7djT5T+GPujy8tLp5SydAtkLKX0x3T74apO9a1PhA8ePCgLDNHe169fx7nlxUuNXUZ+nY27ib29vXiD8S8/f/789OnTol/7999/nz179ujRo7k/jXlBPNHv3797vODYZF++fJn+/ezsLBbsop/GsxwfH7eP4fXr12ln0fIewPKvXF1dHR4e9pgrpYVZEZtKLN76RfTmzZu8a2sjHvP9+/e3JcLd3d3yjycnJ2OeiJYrMraJ2HDr28i/X7MFPH78OMKu+YVHf/RbMuXDzj5F+V9i+44/ttz3xWvutE2Hhw8f5r8S05yIqv0IVpkrzb6LWD7x+mMvs2gRdX21XX9/widmYgSI9VHu8id0KBjr6fnz5zd8kNia40HarPL4tfjlpb6dmg19dkbQY8pTeeN/pcDK6ljqIlrPkbAsMPz48WNyh3CxbcVcqPc+KE9u0xQopovxaOljVungJ6apOdHYyC7/WNLbiS24zXF4vOXKimtTSCXCuRPLxqOVmMfGK4whNE1AYhGlX8iLKJZn/ulc8QhtdvSrOhpaQYSVCckUj7BjOh0vu986KwOLjSOOrMqtJ/UWW8z+/n6+4S42suXd9xdpRYeN22iPYXB2gEpZNu6/yqOVWM5xXF0u6rSIYs+VF1E8bCyimsPvWMJjPuG3muuEZXgxMkwuwthwe2yU+Ugm/zG2m7n779jmPn/+XI69S11KT548aRy988nJ9vI7LROKo8TGs0p5yI2dVKXAyiLKP5r0x7VXE2EcTJcLfULLK6/1GNB6hFFuynkKumjnHUfLXSdy/ZQXABaN3j1m3XnNxjCb32mUWX8wXL7T+hPmsYiOj49jKX348GHSV7ZWE2GskryHK6dn41ee7u9xhqbc4zTOysqj5WXvqhrPQ/YeBjf+nHv79u1by8Gwsp+qf5b4heiw34Wc2x5hWnyHh4cxJEaQ/aZ2KxF73LxTT2doekfYuOmUk/auJ0VaivleHn8WDezl2ml/AJ8PCOMpYomVO5T6hVbukade19gj3PjficEYEqc1lyjn0ru7u52G8fKXGw9jBjhZVw7si+acOaeLP1rua/JeI+WXrq3nCGsWWt5PrdM9MeONcKLSba43PEPTtbElTUfL+7/mthEF5pzKY9ROc9HZueXUr+z9XVtTfwPpa91i6+l0B9YNnZ6e5k02BpDGe2huMl1MDSzvsDmmIel78dKF+8qsJOeULrW1PEOTGyuvcMaQmI+i42ErN9MteyNpfOUrvGtyaz0KzJvLMB3G2oqNNV9zj21rSdfxou0lHQ2WA3tOvXLhPt1Vn1tt+YDliFoOnmlGmg4IY8XFIWiPPVc5Ms+O6otO5MTraYyw/WRbhPMLLHfbw3QYO854ujRLvOE9NCsXgaUxqnLhPg9o0U/7G5vKk5+VZZIjTK32GAxjmS+6mhIJTXQV3FmbAvNKevHixTCv4eTkpPcZmlGJ6vJMLF+4j5Eqz0XbT9XKW9ViaKqMdeUTjeqUeLyqfKLYSHijAgceD9PNU2ljSmdo/vpp3h73qfQe2NOELV24j1Fl7smVNsPg3LloORimR44lFs/V9U6X2Rljm0U08i9fX32EseNMK6PlXKK+wNxhKmSAiVz+gE9sxPU3wdww+GVHmG+Jjn+JrTaPVJ1OO5XXAOeu0JjW5rzTauq6wCv/5e3btxsTt+II04d68uyu8TbiNgXmedEw05izs7P8Fjrdad3pzMSyT9ylsyYpjwipvI2p/fBe+djEq1evGofN8qLr7PLptJane13xzkgK3Gjx2bn2BQ58QJVXf0yN6t9CeeDReNpz4Ltqy9jyucT6jwjNrtBOz5g+5rtowtnmtPB6fLPznZEU2NjhOAtMyt15/eflyw268WCm3MIG+JRA5ZbxrsPgxv9fo+/dbTnmNzY28CJaq+no3AJzh2l4mUqBafXHxppGj/r/MWM5ZWr8NG25Tf/69WuYUb180k7fp1R+bKLxCD9f7pu9jzSeMf/HOC6tP8E2/CJakwhrCpzb4cgLzCc2aq4jl1tYvjKeTqguOnsUG2L5/WLDfP9AumCdn7f86EOnYTB2LvUR5q/VmP2Yb/x7vgsi3VizaIiLRyizn+7d3nfGVmBlXjqJAlMnp6enLUebcu4693JZbJfl1c4h76jKO4X0Af8eE8vyXu1Fynlv5ZNNlVlxrP258/byK/C67i9u9UjYssDcYf3x1djEllcOIzXnP9JVmbLD/LHX9AUq5YQ2Rs4hb7OMd/Hu3buuf6v82ESbS03xZvOMIIa72H+Ve5m0iNKqj39Gh2mIjr+1tbV179696LacdMSP6vcXsVLa/H+j4zXUfEfGOkTYqcA8aZnWLu3k5KTxvHz6tXJ4j+1p0Z2NabMY/xcBl3PRljPn2LPkoSyiKitKH5nf29srK1q0d0sfEG98usFuexj1dLScPKyrll8oFNvN4eFh4zmP+LWjo6NJnPQrP8Lb8qaL8tdmT8/ETz9+/Nh4K1n8WiyiqX9b+XAj4XRvrYw1nfajbVZ27ODzd9rXZBYPFVtPuq5Y+SBf/Cjdi9z761hj281PPZtE+dOuN0zm86XlW4sXn/cU7W+hTsd+Naey0q4qLZ9Kpekt1JyzyUeenT6DsqqY/zk4OFjG41Zm4XGYMfDtRen76svD+pHfQJhe5+35OHm/RZT+RxRr9r62rNqRkN+tXUS+3gJECCIcxpAXu8zucEw4x9nZ2fn5+TDnSK+urm7JV1YiwrYGvlR694+NKV8XQYR/WZubhsAxISBCMB39W9K9V6N6q+t3pwUibNjix3yPGJiOAiIEEQIiBBGCCC0CECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBDG6D8BBgBNIRvwsstoqwAAAABJRU5ErkJggg=='
}
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAIAAAD2HxkiAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAACvtJREFUeNrs3WtrFEkbgOGsCS4GBEVREBIUAgFBQfD//4EIQgQhoCQkICgGhUDEQGAfLN6i3p6ZPsXp6Z5c14dl3cQ5dPfdVX2Y2X8ODg42gNW5YxGACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhIAIQYSACEGEgAhBhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEJAhCBCQIQgQkCEIEIg21rS425ubm5vb4/qrV5fX19eXlrl3JYIo8D9/f1RvdWLi4ujoyOrHNNRQIQgQmDpx4QVqzoY29nZGdv5IVhNhBcXFyt5e9fX19YxpqOACEGEwIqPCee6f//+06dP45+bm5tx0Hh+fv79+3erBBEOZGdnJwosgwx379798uXLbVsH8ca3t7djT5T+GPujy8tLp5SydAtkLKX0x3T74apO9a1PhA8ePCgLDNHe169fx7nlxUuNXUZ+nY27ib29vXiD8S8/f/789OnTol/7999/nz179ujRo7k/jXlBPNHv3797vODYZF++fJn+/ezsLBbsop/GsxwfH7eP4fXr12ln0fIewPKvXF1dHR4e9pgrpYVZEZtKLN76RfTmzZu8a2sjHvP9+/e3JcLd3d3yjycnJ2OeiJYrMraJ2HDr28i/X7MFPH78OMKu+YVHf/RbMuXDzj5F+V9i+44/ttz3xWvutE2Hhw8f5r8S05yIqv0IVpkrzb6LWD7x+mMvs2gRdX21XX9/widmYgSI9VHu8id0KBjr6fnz5zd8kNia40HarPL4tfjlpb6dmg19dkbQY8pTeeN/pcDK6ljqIlrPkbAsMPz48WNyh3CxbcVcqPc+KE9u0xQopovxaOljVungJ6apOdHYyC7/WNLbiS24zXF4vOXKimtTSCXCuRPLxqOVmMfGK4whNE1AYhGlX8iLKJZn/ulc8QhtdvSrOhpaQYSVCckUj7BjOh0vu986KwOLjSOOrMqtJ/UWW8z+/n6+4S42suXd9xdpRYeN22iPYXB2gEpZNu6/yqOVWM5xXF0u6rSIYs+VF1E8bCyimsPvWMJjPuG3muuEZXgxMkwuwthwe2yU+Ugm/zG2m7n779jmPn/+XI69S11KT548aRy988nJ9vI7LROKo8TGs0p5yI2dVKXAyiLKP5r0x7VXE2EcTJcLfULLK6/1GNB6hFFuynkKumjnHUfLXSdy/ZQXABaN3j1m3XnNxjCb32mUWX8wXL7T+hPmsYiOj49jKX348GHSV7ZWE2GskryHK6dn41ee7u9xhqbc4zTOysqj5WXvqhrPQ/YeBjf+nHv79u1by8Gwsp+qf5b4heiw34Wc2x5hWnyHh4cxJEaQ/aZ2KxF73LxTT2doekfYuOmUk/auJ0VaivleHn8WDezl2ml/AJ8PCOMpYomVO5T6hVbukade19gj3PjficEYEqc1lyjn0ru7u52G8fKXGw9jBjhZVw7si+acOaeLP1rua/JeI+WXrq3nCGsWWt5PrdM9MeONcKLSba43PEPTtbElTUfL+7/mthEF5pzKY9ROc9HZueXUr+z9XVtTfwPpa91i6+l0B9YNnZ6e5k02BpDGe2huMl1MDSzvsDmmIel78dKF+8qsJOeULrW1PEOTGyuvcMaQmI+i42ErN9MteyNpfOUrvGtyaz0KzJvLMB3G2oqNNV9zj21rSdfxou0lHQ2WA3tOvXLhPt1Vn1tt+YDliFoOnmlGmg4IY8XFIWiPPVc5Ms+O6otO5MTraYyw/WRbhPMLLHfbw3QYO854ujRLvOE9NCsXgaUxqnLhPg9o0U/7G5vKk5+VZZIjTK32GAxjmS+6mhIJTXQV3FmbAvNKevHixTCv4eTkpPcZmlGJ6vJMLF+4j5Eqz0XbT9XKW9ViaKqMdeUTjeqUeLyqfKLYSHijAgceD9PNU2ljSmdo/vpp3h73qfQe2NOELV24j1Fl7smVNsPg3LloORimR44lFs/V9U6X2Rljm0U08i9fX32EseNMK6PlXKK+wNxhKmSAiVz+gE9sxPU3wdww+GVHmG+Jjn+JrTaPVJ1OO5XXAOeu0JjW5rzTauq6wCv/5e3btxsTt+II04d68uyu8TbiNgXmedEw05izs7P8Fjrdad3pzMSyT9ylsyYpjwipvI2p/fBe+djEq1evGofN8qLr7PLptJane13xzkgK3Gjx2bn2BQ58QJVXf0yN6t9CeeDReNpz4Ltqy9jyucT6jwjNrtBOz5g+5rtowtnmtPB6fLPznZEU2NjhOAtMyt15/eflyw268WCm3MIG+JRA5ZbxrsPgxv9fo+/dbTnmNzY28CJaq+no3AJzh2l4mUqBafXHxppGj/r/MWM5ZWr8NG25Tf/69WuYUb180k7fp1R+bKLxCD9f7pu9jzSeMf/HOC6tP8E2/CJakwhrCpzb4cgLzCc2aq4jl1tYvjKeTqguOnsUG2L5/WLDfP9AumCdn7f86EOnYTB2LvUR5q/VmP2Yb/x7vgsi3VizaIiLRyizn+7d3nfGVmBlXjqJAlMnp6enLUebcu4693JZbJfl1c4h76jKO4X0Af8eE8vyXu1Fynlv5ZNNlVlxrP258/byK/C67i9u9UjYssDcYf3x1djEllcOIzXnP9JVmbLD/LHX9AUq5YQ2Rs4hb7OMd/Hu3buuf6v82ESbS03xZvOMIIa72H+Ve5m0iNKqj39Gh2mIjr+1tbV179696LacdMSP6vcXsVLa/H+j4zXUfEfGOkTYqcA8aZnWLu3k5KTxvHz6tXJ4j+1p0Z2NabMY/xcBl3PRljPn2LPkoSyiKitKH5nf29srK1q0d0sfEG98usFuexj1dLScPKyrll8oFNvN4eFh4zmP+LWjo6NJnPQrP8Lb8qaL8tdmT8/ETz9+/Nh4K1n8WiyiqX9b+XAj4XRvrYw1nfajbVZ27ODzd9rXZBYPFVtPuq5Y+SBf/Cjdi9z761hj281PPZtE+dOuN0zm86XlW4sXn/cU7W+hTsd+Naey0q4qLZ9Kpekt1JyzyUeenT6DsqqY/zk4OFjG41Zm4XGYMfDtRen76svD+pHfQJhe5+35OHm/RZT+RxRr9r62rNqRkN+tXUS+3gJECCIcxpAXu8zucEw4x9nZ2fn5+TDnSK+urm7JV1YiwrYGvlR694+NKV8XQYR/WZubhsAxISBCMB39W9K9V6N6q+t3pwUibNjix3yPGJiOAiIEEQIiBBGCCC0CECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBAQIYgQECGIEBAhiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBBECIgQRAiIEEQIiBDG6D8BBgBNIRvwsstoqwAAAABJRU5ErkJggg==',
},
},
data() {
return {
loading: true,
originalSrc: this.src
originalSrc: this.src,
}
},
async mounted() {
@ -55,7 +55,7 @@ export default {
}
},
watch: {
src: async function(newSrc, _oldSrc) {
src: async function (newSrc, _oldSrc) {
this.originalSrc = newSrc
if (this.readExif) {
try {
@ -65,13 +65,13 @@ export default {
console.warn(err)
}
}
}
},
},
methods: {
failover() {
this.originalSrc = this.failoverSrc
}
}
},
},
}
</script>

View File

@ -3,20 +3,22 @@ export default {
name: 'reloadable',
methods: {
async reloadable() {
const account = await this.$store.dispatch('TimelineSpace/localAccount', this.$route.params.id).catch(err => {
this.$message({
message: this.$t('message.account_load_error'),
type: 'error'
const account = await this.$store
.dispatch('TimelineSpace/localAccount', this.$route.params.id)
.catch((err) => {
this.$message({
message: this.$t('message.account_load_error'),
type: 'error',
})
throw err
})
throw err
})
await this.$store.dispatch('GlobalHeader/stopUserStreamings')
await this.$store.dispatch('TimelineSpace/stopStreamings')
await this.$store.dispatch('TimelineSpace/fetchContentsTimelines')
await this.$store.dispatch('TimelineSpace/startStreamings')
this.$store.dispatch('GlobalHeader/startUserStreamings')
return account
}
}
},
},
}
</script>

View File

@ -15,8 +15,8 @@ export default {
props: {
tag: {
type: Object,
default: null
}
default: null,
},
},
methods: {
openTag(tag) {
@ -24,8 +24,8 @@ export default {
},
id() {
return this.$route.params.id
}
}
},
},
}
</script>

View File

@ -2,7 +2,7 @@
<div class="link" @click="openLink(url)">
<el-image :src="icon" class="icon" fit="cover" lazy>
<div class="image-slot" slot="error">
<i class="el-icon-link"></i>
<el-icon><el-icon-link /></el-icon>
</div>
</el-image>
<div class="contents">
@ -13,33 +13,37 @@
</template>
<script>
import { Link as ElIconLink } from '@element-plus/icons'
export default {
components: {
ElIconLink,
},
name: 'link-preview',
props: {
icon: {
type: String,
default: ''
default: '',
},
title: {
type: String,
default: ''
default: '',
},
description: {
type: String,
default: ''
default: '',
},
url: {
type: String,
default: null
}
default: null,
},
},
methods: {
openLink(link) {
if (link) {
return window.shell.openExternal(link)
}
}
}
},
},
}
</script>

View File

@ -3,25 +3,51 @@
<template v-if="poll">
<ul class="poll-list">
<template v-if="poll.voted">
<li class="voted" v-for="(option, id) in poll.options" v-bind:key="id">
<span class="progress-bar" :style="progress(percentage(option.votes_count, poll.votes_count))"></span>
<li
class="voted"
v-for="(option, id) in poll.options"
v-bind:key="id"
>
<span
class="progress-bar"
:style="
progress(percentage(option.votes_count, poll.votes_count))
"
></span>
<label class="text">
<span class="percentage">{{ percentage(option.votes_count, poll.votes_count) }}%</span>
<span class="percentage"
>{{ percentage(option.votes_count, poll.votes_count) }}%</span
>
<span>{{ option.title }}</span>
</label>
</li>
</template>
<template v-else>
<li class="not-voted" v-for="(option, id) in poll.options" v-bind:key="id">
<el-radio v-model="pollRadio" :label="id">{{ option.title }}</el-radio>
<li
class="not-voted"
v-for="(option, id) in poll.options"
v-bind:key="id"
>
<el-radio v-model="pollRadio" :label="id">{{
option.title
}}</el-radio>
</li>
</template>
</ul>
<el-button v-if="!poll.voted" type="success" size="small" @click="vote" :disabled="pollRadio === null">{{
$t('cards.toot.poll.vote')
<el-button
v-if="!poll.voted"
type="success"
size="small"
@click="vote"
:disabled="pollRadio === null"
>{{ $t('cards.toot.poll.vote') }}</el-button
>
<el-button v-else type="text" @click="refresh">{{
$t('cards.toot.poll.refresh')
}}</el-button>
<el-button v-else type="text" @click="refresh">{{ $t('cards.toot.poll.refresh') }}</el-button>
<span class="votes-count">{{ poll.votes_count }} {{ $t('cards.toot.poll.votes_count') }},</span>
<span class="votes-count"
>{{ poll.votes_count }} {{ $t('cards.toot.poll.votes_count') }},</span
>
<span class="until">{{ parseDatetime(poll.expires_at, now) }}</span>
</template>
</div>
@ -36,29 +62,33 @@ export default {
data() {
return {
pollRadio: null,
now: Date.now()
now: Date.now(),
}
},
props: {
poll: {
type: Object,
default: null
}
default: null,
},
},
computed: {
...mapState('App', {
timeFormat: state => state.timeFormat,
language: state => state.language
})
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
}),
},
methods: {
parseDatetime(datetime, epoch) {
switch (this.timeFormat) {
case TimeFormat.Absolute.value:
return this.$t('cards.toot.poll.until', { datetime: moment(datetime).format('YYYY-MM-DD HH:mm:ss') })
return this.$t('cards.toot.poll.until', {
datetime: moment(datetime).format('YYYY-MM-DD HH:mm:ss'),
})
case TimeFormat.Relative.value:
moment.locale(this.language)
return this.$t('cards.toot.poll.left', { datetime: moment(datetime).from(epoch) })
return this.$t('cards.toot.poll.left', {
datetime: moment(datetime).from(epoch),
})
}
},
percentage(option_votes, poll_votes) {
@ -77,8 +107,8 @@ export default {
},
refresh() {
this.$emit('refresh', this.poll.id)
}
}
},
},
}
</script>

View File

@ -18,24 +18,24 @@ export default {
props: {
icon: {
type: String,
default: ''
default: '',
},
username: {
type: String,
default: ''
default: '',
},
accountName: {
type: String,
default: ''
default: '',
},
body: {
type: String,
default: ''
}
default: '',
},
},
components: {
FailoverImg
}
FailoverImg,
},
}
</script>

View File

@ -24,7 +24,12 @@
>
<font-awesome-icon icon="user-xmark" />
</el-button>
<el-button v-else-if="relationship.requested" class="requested" type="text" :title="$t('side_bar.account_profile.follow_requested')">
<el-button
v-else-if="relationship.requested"
class="requested"
type="text"
:title="$t('side_bar.account_profile.follow_requested')"
>
<font-awesome-icon icon="hourglass" />
</el-button>
<el-button
@ -38,10 +43,20 @@
</el-button>
</div>
<div class="tool" v-else-if="request">
<el-button class="accept" type="text" @click.stop.prevent="acceptRequest(user)" :title="$t('follow_requests.accept')">
<el-button
class="accept"
type="text"
@click.stop.prevent="acceptRequest(user)"
:title="$t('follow_requests.accept')"
>
<font-awesome-icon icon="check" />
</el-button>
<el-button class="reject" type="text" @click.stop.prevent="rejectRequest(user)" :tilte="$t('follow_requests.reject')">
<el-button
class="reject"
type="text"
@click.stop.prevent="rejectRequest(user)"
:tilte="$t('follow_requests.reject')"
>
<font-awesome-icon icon="xmark" />
</el-button>
</div>
@ -55,25 +70,25 @@ import emojify from '~/src/renderer/utils/emojify'
export default {
name: 'user',
components: {
FailoverImg
FailoverImg,
},
props: {
user: {
type: Object,
default: {}
default: {},
},
remove: {
type: Boolean,
default: false
default: false,
},
relationship: {
type: Object,
default: null
default: null,
},
request: {
type: Boolean,
default: false
}
default: false,
},
},
methods: {
username(account) {
@ -84,9 +99,17 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
removeAccount(account) {
this.$emit('removeAccount', account)
@ -102,8 +125,8 @@ export default {
},
rejectRequest(account) {
this.$emit('rejectRequest', account)
}
}
},
},
}
</script>

View File

@ -140,29 +140,40 @@ export default {
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
defalt: false
defalt: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
components: {
Favourite,
Follow,
FollowRequest,
Mention,
Quote,
Reblog,
Reaction,
Status,
PollVote,
PollExpired,
},
components: { Favourite, Follow, FollowRequest, Mention, Quote, Reblog, Reaction, Status, PollVote, PollExpired },
methods: {
updateToot(message) {
return this.$emit('update', message)
},
deleteToot(message) {
return this.$emit('delete', message)
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,25 +32,34 @@
v-html="
$t('notification.favourite.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" role="presentation" />
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
role="presentation"
/>
</div>
<div class="detail">
<div class="toot-header">
<div class="user" @click="openUser(message.status.account)">
<span class="display-name"><bdi v-html="username(message.status.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(message.status.account)"></bdi
></span>
</div>
<div class="timestamp">
{{ parseDatetime(message.status.created_at) }}
@ -59,7 +79,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -72,7 +99,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -81,7 +110,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -89,10 +120,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -126,42 +175,42 @@ export default {
name: 'favourite',
components: {
FailoverImg,
LinkPreview
LinkPreview,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
displayNameStyle: state => state.displayNameStyle,
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
displayNameStyle: (state) => state.displayNameStyle,
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -179,7 +228,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -207,17 +256,31 @@ export default {
}
const parsedAccount = findAccount(e.target, 'favourite')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount.acct
}
@ -230,14 +293,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -252,7 +329,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -284,8 +364,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,11 @@
<div
class="relationship"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? { next: ['j'], prev: ['k'], right: ['l'], profile: ['p'] }
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -20,7 +24,7 @@
v-html="
$t('notification.follow.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
@ -43,26 +47,26 @@ import emojify from '~/src/renderer/utils/emojify'
export default {
name: 'follow',
components: {
FailoverImg
FailoverImg,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
computed: {
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -80,7 +84,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -91,9 +95,17 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
handleStatusControl(event) {
switch (event.srcKey) {
@ -110,8 +122,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,11 @@
<div
class="relationship"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? { next: ['j'], prev: ['k'], right: ['l'], profile: ['p'] }
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -20,7 +24,7 @@
v-html="
$t('notification.follow_request.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
@ -43,26 +47,26 @@ import emojify from '~/src/renderer/utils/emojify'
export default {
name: 'follow-request',
components: {
FailoverImg
FailoverImg,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
computed: {
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -80,7 +84,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -91,9 +95,17 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
handleStatusControl(event) {
switch (event.srcKey) {
@ -110,8 +122,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -24,20 +24,20 @@ export default {
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
components: { Toot },
methods: {
@ -46,7 +46,7 @@ export default {
},
deleteToot(message) {
return this.$emit('delete', message)
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,25 +32,34 @@
v-html="
$t('notification.poll_expired.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" role="presentation" />
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
role="presentation"
/>
</div>
<div class="detail">
<div class="toot-header">
<div class="user" @click="openUser(message.status.account)">
<span class="display-name"><bdi v-html="username(message.status.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(message.status.account)"></bdi
></span>
</div>
<div class="timestamp">
{{ parseDatetime(message.status.created_at) }}
@ -59,7 +79,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -72,7 +99,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -81,7 +110,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -89,10 +120,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -126,42 +175,42 @@ export default {
name: 'poll-expired',
components: {
FailoverImg,
LinkPreview
LinkPreview,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
displayNameStyle: state => state.displayNameStyle,
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
displayNameStyle: (state) => state.displayNameStyle,
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -179,7 +228,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -207,17 +256,31 @@ export default {
}
const parsedAccount = findAccount(e.target, 'poll-expired')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount.acct
}
@ -230,14 +293,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -252,7 +329,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -284,8 +364,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,25 +32,34 @@
v-html="
$t('notification.poll_vote.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" role="presentation" />
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
role="presentation"
/>
</div>
<div class="detail">
<div class="toot-header">
<div class="user" @click="openUser(message.status.account)">
<span class="display-name"><bdi v-html="username(message.status.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(message.status.account)"></bdi
></span>
</div>
<div class="timestamp">
{{ parseDatetime(message.status.created_at) }}
@ -59,7 +79,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -72,7 +99,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -81,7 +110,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -89,10 +120,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -126,42 +175,42 @@ export default {
name: 'poll-vote',
components: {
FailoverImg,
LinkPreview
LinkPreview,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
displayNameStyle: state => state.displayNameStyle,
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
displayNameStyle: (state) => state.displayNameStyle,
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -179,7 +228,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -207,17 +256,31 @@ export default {
}
const parsedAccount = findAccount(e.target, 'poll-vote')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount.acct
}
@ -230,14 +293,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -252,7 +329,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -284,8 +364,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,20 +32,30 @@
v-html="
$t('notification.quote.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)" role="presentation">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" />
<div
class="icon"
@click="openUser(message.status.account)"
role="presentation"
>
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
/>
</div>
<div class="detail">
<div class="toot-header">
@ -50,7 +71,11 @@
</div>
<div class="content-wrapper">
<div class="spoiler" v-show="spoilered(message.status)">
<span v-html="emojiText(message.status.spoiler_text, message.status.emojis)"></span>
<span
v-html="
emojiText(message.status.spoiler_text, message.status.emojis)
"
></span>
<el-button
v-if="!isShowContent(message.status)"
plain
@ -61,7 +86,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -74,7 +106,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -83,7 +117,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -91,10 +127,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -104,7 +158,12 @@
:icon="message.status.reblog.account.avatar"
:username="username(message.status.reblog.account)"
:accountName="''"
:body="emojiText(message.status.reblog.content, message.status.reblog.emojis)"
:body="
emojiText(
message.status.reblog.content,
message.status.reblog.emojis
)
"
@select="openDetail(message.status.reblog)"
/>
<LinkPreview
@ -138,41 +197,41 @@ export default {
components: {
FailoverImg,
LinkPreview,
Quote
Quote,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -190,7 +249,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -218,16 +277,30 @@ export default {
}
const parsedAccount = findAccount(e.target, 'quoted')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(() => {
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount
}
@ -240,14 +313,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -262,7 +349,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -291,8 +381,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,25 +32,34 @@
v-html="
$t('notification.reaction.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" role="presentation" />
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
role="presentation"
/>
</div>
<div class="detail">
<div class="toot-header">
<div class="user" @click="openUser(message.status.account)">
<span class="display-name"><bdi v-html="username(message.status.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(message.status.account)"></bdi
></span>
</div>
<div class="timestamp">
{{ parseDatetime(message.status.created_at) }}
@ -59,7 +79,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -72,7 +99,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -81,7 +110,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -89,10 +120,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -126,42 +175,42 @@ export default {
name: 'reaction',
components: {
FailoverImg,
LinkPreview
LinkPreview,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
displayNameStyle: state => state.displayNameStyle,
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
displayNameStyle: (state) => state.displayNameStyle,
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -179,7 +228,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -207,17 +256,31 @@ export default {
}
const parsedAccount = findAccount(e.target, 'favourite')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount.acct
}
@ -230,14 +293,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -252,7 +329,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -284,8 +364,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -2,7 +2,18 @@
<div
class="status"
tabIndex="0"
v-shortkey="shortcutEnabled ? { next: ['j'], prev: ['k'], right: ['l'], left: ['h'], open: ['o'], profile: ['p'] } : {}"
v-shortkey="
shortcutEnabled
? {
next: ['j'],
prev: ['k'],
right: ['l'],
left: ['h'],
open: ['o'],
profile: ['p'],
}
: {}
"
@shortkey="handleStatusControl"
ref="status"
@click="$emit('select')"
@ -21,20 +32,30 @@
v-html="
$t('notification.reblog.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
<div class="target" v-on:dblclick="openDetail(message.status)">
<div class="icon" @click="openUser(message.status.account)" role="presentation">
<FailoverImg :src="message.status.account.avatar" :alt="`Avatar of ${message.status.account.username}`" />
<div
class="icon"
@click="openUser(message.status.account)"
role="presentation"
>
<FailoverImg
:src="message.status.account.avatar"
:alt="`Avatar of ${message.status.account.username}`"
/>
</div>
<div class="detail">
<div class="toot-header">
@ -61,7 +82,14 @@
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else plain type="primary" size="medium" class="spoil-button" @click="showContent = false">
<el-button
v-else
plain
type="primary"
size="medium"
class="spoil-button"
@click="showContent = false"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -74,7 +102,9 @@
</div>
<div class="attachments">
<el-button
v-show="sensitive(message.status) && !isShowAttachments(message.status)"
v-show="
sensitive(message.status) && !isShowAttachments(message.status)
"
class="show-sensitive"
type="info"
@click="showAttachments = true"
@ -83,7 +113,9 @@
</el-button>
<div v-show="isShowAttachments(message.status)">
<el-button
v-show="sensitive(message.status) && isShowAttachments(message.status)"
v-show="
sensitive(message.status) && isShowAttachments(message.status)
"
class="hide-sensitive"
type="text"
@click="showAttachments = false"
@ -91,10 +123,28 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments(message.status)">
<FailoverImg :src="media.preview_url" :title="media.description" :readExif="true" />
<el-tag class="media-label" size="mini" v-if="media.type == 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type == 'video'">VIDEO</el-tag>
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments(message.status)"
>
<FailoverImg
:src="media.preview_url"
:title="media.description"
:readExif="true"
/>
<el-tag
class="media-label"
size="mini"
v-if="media.type == 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type == 'video'"
>VIDEO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -128,41 +178,41 @@ export default {
name: 'reblog',
components: {
FailoverImg,
LinkPreview
LinkPreview,
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
data() {
return {
showContent: false,
showAttachments: false
showAttachments: false,
}
},
computed: {
...mapState('App', {
timeFormat: state => state.timeFormat,
language: state => state.language,
hideAllAttachments: state => state.hideAllAttachments
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
hideAllAttachments: (state) => state.hideAllAttachments,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid
}
},
},
mounted() {
if (this.focused) {
@ -180,7 +230,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -208,16 +258,30 @@ export default {
}
const parsedAccount = findAccount(e.target, 'reblog')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', parsedAccount)
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
parsedAccount
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.catch(() => {
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount
}
@ -230,14 +294,28 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
mediaAttachments(message) {
return message.media_attachments
@ -252,7 +330,10 @@ export default {
return !this.spoilered(message) || this.showContent
},
sensitive(message) {
return (this.hideAllAttachments || message.sensitive) && this.mediaAttachments(message).length > 0
return (
(this.hideAllAttachments || message.sensitive) &&
this.mediaAttachments(message).length > 0
)
},
isShowAttachments(message) {
return !this.sensitive(message) || this.showAttachments
@ -284,8 +365,8 @@ export default {
this.openUser(this.message.account)
break
}
}
}
},
},
}
</script>

View File

@ -10,14 +10,17 @@
v-html="
$t('notification.status.body', {
username: username(message.account),
interpolation: { escapeValue: false }
interpolation: { escapeValue: false },
})
"
></bdi>
</span>
</div>
<div class="action-icon" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</div>
</div>
<div class="clearfix"></div>
@ -48,20 +51,20 @@ export default {
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
}
default: false,
},
},
components: { Toot, FailoverImg },
methods: {
@ -79,11 +82,19 @@ export default {
}
},
openUser(account) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
}
}
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
},
}
</script>

View File

@ -1,6 +1,10 @@
<template>
<div class="status-loading" tabIndex="0" @click="onClick">
<img v-if="loading" src="../../assets/images/loading-spinner-wide.svg" class="load-icon" />
<img
v-if="loading"
src="../../assets/images/loading-spinner-wide.svg"
class="load-icon"
/>
<p v-else class="load-text">{{ $t('cards.status_loading.message') }}</p>
<div class="fill-line"></div>
</div>
@ -12,16 +16,16 @@ export default {
props: {
max_id: {
type: String,
default: ''
default: '',
},
since_id: {
type: String,
default: ''
default: '',
},
loading: {
type: Boolean,
default: false
}
default: false,
},
},
methods: {
onClick() {
@ -33,8 +37,8 @@ export default {
} else if (this.max_id !== '') {
this.$emit('load_max', this.max_id)
}
}
}
},
},
}
</script>

View File

@ -15,7 +15,7 @@
open: ['o'],
profile: ['p'],
image: ['i'],
cw: ['x']
cw: ['x'],
}
: {}
"
@ -28,8 +28,15 @@
<div v-show="filtered" class="filtered">Filtered</div>
<div v-show="!filtered" class="toot">
<div class="reblogger" v-show="message.reblog && !message.quote">
<span class="reblogger-icon" @click="openUser(message.account)" role="presentation">
<FailoverImg :src="message.account.avatar" :alt="`Avatar of ${message.account.username}`" />
<span
class="reblogger-icon"
@click="openUser(message.account)"
role="presentation"
>
<FailoverImg
:src="message.account.avatar"
:alt="`Avatar of ${message.account.username}`"
/>
</span>
<font-awesome-icon icon="retweet" />
<span
@ -51,11 +58,17 @@
<div class="detail" v-on:dblclick="openDetail(message)">
<div class="toot-header">
<div class="user" @click="openUser(originalMessage.account)">
<span class="display-name"><bdi v-html="username(originalMessage.account)"></bdi></span>
<span class="display-name"
><bdi v-html="username(originalMessage.account)"></bdi
></span>
<span class="acct">{{ accountName(originalMessage.account) }}</span>
</div>
<div class="timestamp">
<time :datetime="originalMessage.created_at" :title="readableTimestamp" @click="openDetail(message)">
<time
:datetime="originalMessage.created_at"
:title="readableTimestamp"
@click="openDetail(message)"
>
{{ timestamp }}
</time>
</div>
@ -63,11 +76,28 @@
</div>
<div class="content-wrapper">
<div class="spoiler" v-show="spoilered">
<span v-html="emojiText(originalMessage.spoiler_text, originalMessage.emojis)"></span>
<el-button v-if="!isShowContent" plain type="primary" size="medium" class="spoil-button" @click="toggleSpoiler">
<span
v-html="
emojiText(originalMessage.spoiler_text, originalMessage.emojis)
"
></span>
<el-button
v-if="!isShowContent"
plain
type="primary"
size="medium"
class="spoil-button"
@click="toggleSpoiler"
>
{{ $t('cards.toot.show_more') }}
</el-button>
<el-button v-else type="primary" size="medium" class="spoil-button" @click="toggleSpoiler">
<el-button
v-else
type="primary"
size="medium"
class="spoil-button"
@click="toggleSpoiler"
>
{{ $t('cards.toot.hide') }}
</el-button>
</div>
@ -77,10 +107,21 @@
v-html="emojiText(originalMessage.content, originalMessage.emojis)"
@click.capture.prevent="tootClick"
></div>
<Poll v-show="isShowContent" v-if="poll" :poll="poll" @vote="vote" @refresh="refresh"></Poll>
<Poll
v-show="isShowContent"
v-if="poll"
:poll="poll"
@vote="vote"
@refresh="refresh"
></Poll>
</div>
<div class="attachments">
<el-button v-show="sensitive && !isShowAttachments" class="show-sensitive" type="info" @click="toggleCW()">
<el-button
v-show="sensitive && !isShowAttachments"
class="show-sensitive"
type="info"
@click="toggleCW()"
>
{{ $t('cards.toot.sensitive') }}
</el-button>
<div v-show="isShowAttachments">
@ -93,16 +134,39 @@
>
<font-awesome-icon icon="eye" class="hide" />
</el-button>
<div class="media" v-bind:key="media.preview_url" v-for="media in mediaAttachments">
<div
class="media"
v-bind:key="media.preview_url"
v-for="media in mediaAttachments"
>
<FailoverImg
:src="media.preview_url ? media.preview_url : originalMessage.account.avatar"
:src="
media.preview_url
? media.preview_url
: originalMessage.account.avatar
"
@click="openImage(media.url, mediaAttachments)"
:title="media.description"
:readExif="true"
/>
<el-tag class="media-label" size="mini" v-if="media.type === 'gifv'">GIF</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type === 'video'">VIDEO</el-tag>
<el-tag class="media-label" size="mini" v-else-if="media.type === 'audio'">AUDIO</el-tag>
<el-tag
class="media-label"
size="mini"
v-if="media.type === 'gifv'"
>GIF</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type === 'video'"
>VIDEO</el-tag
>
<el-tag
class="media-label"
size="mini"
v-else-if="media.type === 'audio'"
>AUDIO</el-tag
>
</div>
</div>
<div class="clearfix"></div>
@ -124,17 +188,33 @@
/>
<div class="emoji-reactions">
<template v-for="reaction in originalMessage.emoji_reactions">
<el-button v-if="reaction.me" type="success" size="medium" class="reaction" @click="removeReaction(reaction.name)"
<el-button
v-if="reaction.me"
type="success"
size="medium"
class="reaction"
@click="removeReaction(reaction.name)"
>{{ reaction.name }} {{ reaction.count }}</el-button
>
<el-button v-else type="text" size="medium" class="reaction" @click="addReaction(reaction.name)"
<el-button
v-else
type="text"
size="medium"
class="reaction"
@click="addReaction(reaction.name)"
>{{ reaction.name }} {{ reaction.count }}</el-button
>
</template>
</div>
<div class="toot-footer">
<div class="tool-box">
<el-button type="text" @click="openReply()" class="reply" :title="$t('cards.toot.reply')" :aria-label="$t('cards.toot.reply')">
<el-button
type="text"
@click="openReply()"
class="reply"
:title="$t('cards.toot.reply')"
:aria-label="$t('cards.toot.reply')"
>
<font-awesome-icon icon="reply" size="sm" />
</el-button>
<el-button v-show="locked" type="text" class="locked">
@ -158,7 +238,11 @@
<el-button
type="text"
@click="changeFavourite(originalMessage)"
:class="originalMessage.favourited ? 'favourited animated bounceIn' : 'favourite'"
:class="
originalMessage.favourited
? 'favourited animated bounceIn'
: 'favourite'
"
:title="$t('cards.toot.fav')"
:aria-label="$t('cards.toot.fav')"
>
@ -177,7 +261,12 @@
>
<font-awesome-icon icon="bookmark" size="sm" />
</el-button>
<el-button type="text" class="quote-btn" v-if="quoteSupported" @click="openQuote()">
<el-button
type="text"
class="quote-btn"
v-if="quoteSupported"
@click="openQuote()"
>
<font-awesome-icon icon="quote-right" size="sm" />
</el-button>
<template v-if="sns !== 'mastodon'">
@ -206,7 +295,13 @@
</el-button>
</el-popover>
</template>
<el-button class="pinned" type="text" :title="$t('cards.toot.pinned')" :aria-label="$t('cards.toot.pinned')" v-show="pinned">
<el-button
class="pinned"
type="text"
:title="$t('cards.toot.pinned')"
:aria-label="$t('cards.toot.pinned')"
v-show="pinned"
>
<font-awesome-icon icon="thumbtack" size="sm" />
</el-button>
<el-popover
@ -219,7 +314,11 @@
@hide="hideMenu"
>
<ul class="menu-list" v-if="openToolMenu">
<li role="button" @click="openDetail(message)" v-show="!detailed">
<li
role="button"
@click="openDetail(message)"
v-show="!detailed"
>
{{ $t('cards.toot.view_toot_detail') }}
</li>
<li role="button" @click="openBrowser(originalMessage)">
@ -237,11 +336,20 @@
<li role="button" @click="reportUser()" v-if="!isMyMessage">
{{ $t('cards.toot.report') }}
</li>
<li role="button" class="separate" @click="deleteToot(message)" v-if="isMyMessage">
<li
role="button"
class="separate"
@click="deleteToot(message)"
v-if="isMyMessage"
>
{{ $t('cards.toot.delete') }}
</li>
</ul>
<el-button slot="reference" type="text" :title="$t('cards.toot.detail')">
<el-button
slot="reference"
type="text"
:title="$t('cards.toot.detail')"
>
<font-awesome-icon icon="ellipsis" size="sm" />
</el-button>
</el-popover>
@ -277,14 +385,14 @@ import Filtered from '@/utils/filter'
export default {
name: 'toot',
directives: {
ClickOutside
ClickOutside,
},
components: {
FailoverImg,
Poll,
Picker,
LinkPreview,
Quote
Quote,
},
data() {
return {
@ -293,47 +401,47 @@ export default {
hideAllAttachments: this.$store.state.App.hideAllAttachments,
now: Date.now(),
openEmojiPicker: false,
openToolMenu: false
openToolMenu: false,
}
},
props: {
message: {
type: Object,
default: {}
default: {},
},
filters: {
type: Array,
default: []
default: [],
},
focused: {
type: Boolean,
default: false
default: false,
},
overlaid: {
type: Boolean,
default: false
default: false,
},
pinned: {
type: Boolean,
default: false
default: false,
},
detailed: {
type: Boolean,
default: false
}
default: false,
},
},
computed: {
...mapState('App', {
displayNameStyle: state => state.displayNameStyle,
timeFormat: state => state.timeFormat,
language: state => state.language
displayNameStyle: (state) => state.displayNameStyle,
timeFormat: (state) => state.timeFormat,
language: (state) => state.language,
}),
...mapState('TimelineSpace', {
sns: state => state.sns,
account: state => state.account
sns: (state) => state.sns,
account: (state) => state.account,
}),
...mapState('TimelineSpace/SideMenu', {
bookmarkSupported: state => state.enabledTimelines.bookmark
bookmarkSupported: (state) => state.enabledTimelines.bookmark,
}),
shortcutEnabled: function () {
return this.focused && !this.overlaid && !this.openEmojiPicker
@ -368,7 +476,10 @@ export default {
return null
},
isMyMessage: function () {
return this.$store.state.TimelineSpace.account.accountId === this.originalMessage.account.id
return (
this.$store.state.TimelineSpace.account.accountId ===
this.originalMessage.account.id
)
},
application: function () {
const msg = this.originalMessage
@ -387,7 +498,10 @@ export default {
return this.originalMessage.poll
},
sensitive: function () {
return (this.hideAllAttachments || this.originalMessage.sensitive) && this.mediaAttachments.length > 0
return (
(this.hideAllAttachments || this.originalMessage.sensitive) &&
this.mediaAttachments.length > 0
)
},
isShowAttachments: function () {
return !this.sensitive || this.showAttachments
@ -403,7 +517,7 @@ export default {
},
quoteSupported: function () {
return QuoteSupported(this.sns, this.account.domain)
}
},
},
mounted() {
if (this.focused) {
@ -427,7 +541,7 @@ export default {
this.$refs.status.blur()
})
}
}
},
},
methods: {
username(account) {
@ -475,20 +589,34 @@ export default {
}
const parsedAccount = findAccount(e.target, 'toot')
if (parsedAccount !== null) {
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$store
.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/searchAccount', {
parsedAccount: parsedAccount,
status: this.originalMessage
.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/searchAccount',
{
parsedAccount: parsedAccount,
status: this.originalMessage,
}
)
.then((account) => {
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
})
.then(account => {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.openLink(e)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', false)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
false
)
})
return parsedAccount.acct
}
@ -501,12 +629,21 @@ export default {
}
},
openReply() {
this.$store.dispatch('TimelineSpace/Modals/NewToot/openReply', this.originalMessage)
this.$store.dispatch(
'TimelineSpace/Modals/NewToot/openReply',
this.originalMessage
)
},
openDetail(message) {
this.$store.dispatch('TimelineSpace/Contents/SideBar/openTootComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/TootDetail/changeToot', message)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/TootDetail/changeToot',
message
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
this.$refs.status_menu_popper.doClose()
},
openBrowser(message) {
@ -518,11 +655,17 @@ export default {
this.$refs.status_menu_popper.doClose()
},
reportUser() {
this.$store.dispatch('TimelineSpace/Modals/Report/openReport', this.originalMessage)
this.$store.dispatch(
'TimelineSpace/Modals/Report/openReport',
this.originalMessage
)
this.$refs.status_menu_popper.doClose()
},
confirmMute() {
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeAccount', this.originalMessage.account)
this.$store.dispatch(
'TimelineSpace/Modals/MuteConfirm/changeAccount',
this.originalMessage.account
)
this.$store.dispatch('TimelineSpace/Modals/MuteConfirm/changeModal', true)
this.$refs.status_menu_popper.doClose()
},
@ -534,27 +677,27 @@ export default {
if (message.reblogged) {
this.$store
.dispatch('organisms/Toot/unreblog', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.unreblog_error'),
type: 'error'
type: 'error',
})
})
} else {
this.$store
.dispatch('organisms/Toot/reblog', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.reblog_error'),
type: 'error'
type: 'error',
})
})
}
@ -563,27 +706,27 @@ export default {
if (message.favourited) {
this.$store
.dispatch('organisms/Toot/removeFavourite', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.unfavourite_error'),
type: 'error'
type: 'error',
})
})
} else {
this.$store
.dispatch('organisms/Toot/addFavourite', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.favourite_error'),
type: 'error'
type: 'error',
})
})
}
@ -592,57 +735,65 @@ export default {
if (message.bookmarked) {
this.$store
.dispatch('organisms/Toot/removeBookmark', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.unbookmark_error'),
type: 'error'
type: 'error',
})
})
} else {
this.$store
.dispatch('organisms/Toot/addBookmark', message)
.then(data => {
.then((data) => {
this.$emit('update', data)
})
.catch(err => {
.catch((err) => {
console.error(err)
this.$message({
message: this.$t('message.bookmark_error'),
type: 'error'
type: 'error',
})
})
}
},
openImage(url, rawMediaList) {
const mediaList = rawMediaList.map(media => {
const mediaList = rawMediaList.map((media) => {
return media.url
})
const currentIndex = mediaList.indexOf(url)
this.$store.dispatch('TimelineSpace/Modals/ImageViewer/openModal', {
currentIndex: currentIndex,
mediaList: rawMediaList
mediaList: rawMediaList,
})
},
openUser(account) {
console.log(account)
this.$store.dispatch('TimelineSpace/Contents/SideBar/openAccountComponent')
this.$store.dispatch('TimelineSpace/Contents/SideBar/AccountProfile/changeAccount', account)
this.$store.commit('TimelineSpace/Contents/SideBar/changeOpenSideBar', true)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/openAccountComponent'
)
this.$store.dispatch(
'TimelineSpace/Contents/SideBar/AccountProfile/changeAccount',
account
)
this.$store.commit(
'TimelineSpace/Contents/SideBar/changeOpenSideBar',
true
)
},
deleteToot(message) {
this.$store
.dispatch('organisms/Toot/deleteToot', message)
.then(message => {
.then((message) => {
this.$emit('delete', message)
})
.catch(() => {
this.$message({
message: this.$t('message.delete_error'),
type: 'error'
type: 'error',
})
})
},
@ -695,24 +846,24 @@ export default {
async vote(choices) {
const res = await this.$store.dispatch('organisms/Toot/vote', {
id: this.poll.id,
choices: choices
choices: choices,
})
const status = Object.assign({}, this.originalMessage, {
poll: res
poll: res,
})
this.$emit('update', status)
},
async refresh(id) {
const res = await this.$store.dispatch('organisms/Toot/refresh', id)
const status = Object.assign({}, this.originalMessage, {
poll: res
poll: res,
})
this.$emit('update', status)
},
async selectEmoji(emoji) {
const status = await this.$store.dispatch('organisms/Toot/sendReaction', {
status_id: this.originalMessage.id,
native: emoji.native
native: emoji.native,
})
this.$emit('update', status)
this.$refs.status_emoji_picker.doClose()
@ -720,19 +871,25 @@ export default {
async addReaction(native) {
const status = await this.$store.dispatch('organisms/Toot/sendReaction', {
status_id: this.originalMessage.id,
native: native
native: native,
})
this.$emit('update', status)
},
async removeReaction(native) {
const status = await this.$store.dispatch('organisms/Toot/deleteReaction', {
status_id: this.originalMessage.id,
native: native
})
const status = await this.$store.dispatch(
'organisms/Toot/deleteReaction',
{
status_id: this.originalMessage.id,
native: native,
}
)
this.$emit('update', status)
},
openQuote() {
this.$store.dispatch('TimelineSpace/Modals/NewToot/openQuote', this.originalMessage)
this.$store.dispatch(
'TimelineSpace/Modals/NewToot/openQuote',
this.originalMessage
)
},
openPicker() {
this.openEmojiPicker = true
@ -753,8 +910,8 @@ export default {
toggleCW() {
this.showAttachments = !this.showAttachments
this.$emit('sizeChanged', true)
}
}
},
},
}
</script>

View File

@ -1,10 +1,15 @@
import loadImage from 'blueimp-load-image'
const parseExtension = url => {
const parseExtension = (url) => {
if (!url) {
return null
}
if (url.match(/\.jpg$/) || url.match(/\.jpeg$/) || url.match(/\.JPG$/) || url.match(/\.JPEG$/)) {
if (
url.match(/\.jpg$/) ||
url.match(/\.jpeg$/) ||
url.match(/\.JPG$/) ||
url.match(/\.JPEG$/)
) {
return 'image/jpeg'
} else if (url.match(/\.png$/) || url.match(/\.PNG$/)) {
return 'image/png'
@ -18,7 +23,7 @@ const parseExtension = url => {
return null
}
const exifImageUrl = url => {
const exifImageUrl = (url) => {
return new Promise((resolve, reject) => {
const extension = parseExtension(url)
if (!extension) {
@ -26,7 +31,7 @@ const exifImageUrl = url => {
}
loadImage(
url,
canvas => {
(canvas) => {
if (canvas.type === 'error') {
return reject(Error(`can not load image: ${url}`))
}
@ -36,7 +41,7 @@ const exifImageUrl = url => {
{
canvas: true,
meta: true,
orientation: true
orientation: true,
}
)
})

View File

@ -46,10 +46,12 @@ export class ScrollPosition {
}
prepare() {
this.previousScrollHeightMinusTop = this.node.scrollHeight - this.node.scrollTop
this.previousScrollHeightMinusTop =
this.node.scrollHeight - this.node.scrollTop
}
restore() {
this.node.scrollTop = this.node.scrollHeight - this.previousScrollHeightMinusTop
this.node.scrollTop =
this.node.scrollHeight - this.previousScrollHeightMinusTop
}
}