parent
b77788b947
commit
4a08d5295e
7 changed files with 283 additions and 93 deletions
|
@ -498,6 +498,9 @@ removeAllFollowing: "フォローを全解除"
|
|||
removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
|
||||
userSuspended: "このユーザーは凍結されています。"
|
||||
userSilenced: "このユーザーはサイレンスされています。"
|
||||
sidebar: "サイドバー"
|
||||
divider: "分割線"
|
||||
addItem: "項目を追加"
|
||||
|
||||
_theme:
|
||||
explore: "テーマを探す"
|
||||
|
|
109
src/client/app.ts
Normal file
109
src/client/app.ts
Normal file
|
@ -0,0 +1,109 @@
|
|||
import { faTerminal, faHashtag, faBroadcastTower, faFireAlt, faSearch, faStar, faAt, faListUl, faUserClock, faUsers, faCloud, faGamepad, faFileAlt, faSatellite } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export function createMenuDef(actions) {
|
||||
return {
|
||||
notifications: {
|
||||
title: 'notifications',
|
||||
icon: faBell,
|
||||
show: store => store.getters.isSignedIn,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasUnreadNotification,
|
||||
to: '/my/notifications',
|
||||
},
|
||||
messaging: {
|
||||
title: 'messaging',
|
||||
icon: faComments,
|
||||
show: store => store.getters.isSignedIn,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasUnreadMessagingMessage,
|
||||
to: '/my/messaging',
|
||||
},
|
||||
drive: {
|
||||
title: 'drive',
|
||||
icon: faCloud,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/drive',
|
||||
},
|
||||
followRequests: {
|
||||
title: 'followRequests',
|
||||
icon: faUserClock,
|
||||
show: store => store.getters.isSignedIn && store.state.i.isLocked,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasPendingReceivedFollowRequest,
|
||||
to: '/my/follow-requests',
|
||||
},
|
||||
featured: {
|
||||
title: 'featured',
|
||||
icon: faFireAlt,
|
||||
to: '/featured',
|
||||
},
|
||||
explore: {
|
||||
title: 'explore',
|
||||
icon: faHashtag,
|
||||
to: '/explore',
|
||||
},
|
||||
announcements: {
|
||||
title: 'announcements',
|
||||
icon: faBroadcastTower,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasUnreadAnnouncement,
|
||||
to: '/announcements',
|
||||
},
|
||||
search: {
|
||||
title: 'search',
|
||||
icon: faSearch,
|
||||
action: () => actions.search(),
|
||||
},
|
||||
lists: {
|
||||
title: 'lists',
|
||||
icon: faListUl,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/lists',
|
||||
},
|
||||
groups: {
|
||||
title: 'groups',
|
||||
icon: faUsers,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/groups',
|
||||
},
|
||||
antennas: {
|
||||
title: 'antennas',
|
||||
icon: faSatellite,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/antennas',
|
||||
},
|
||||
mentions: {
|
||||
title: 'mentions',
|
||||
icon: faAt,
|
||||
show: store => store.getters.isSignedIn,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasUnreadMentions,
|
||||
to: '/my/mentions',
|
||||
},
|
||||
messages: {
|
||||
title: 'directNotes',
|
||||
icon: faEnvelope,
|
||||
show: store => store.getters.isSignedIn,
|
||||
indicate: store => store.getters.isSignedIn && store.state.i.hasUnreadSpecifiedNotes,
|
||||
to: '/my/messages',
|
||||
},
|
||||
favorites: {
|
||||
title: 'favorites',
|
||||
icon: faStar,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/favorites',
|
||||
},
|
||||
pages: {
|
||||
title: 'pages',
|
||||
icon: faFileAlt,
|
||||
show: store => store.getters.isSignedIn,
|
||||
to: '/my/pages',
|
||||
},
|
||||
games: {
|
||||
title: 'games',
|
||||
icon: faGamepad,
|
||||
to: '/games',
|
||||
},
|
||||
scratchpad: {
|
||||
title: 'scratchpad',
|
||||
icon: faTerminal,
|
||||
to: '/scratchpad',
|
||||
},
|
||||
};
|
||||
}
|
|
@ -49,44 +49,20 @@
|
|||
<router-link class="item index" active-class="active" to="/" exact v-else>
|
||||
<fa :icon="faHome" fixed-width/><span class="text">{{ $store.getters.isSignedIn ? $t('timeline') : $t('home') }}</span>
|
||||
</router-link>
|
||||
<template v-if="$store.getters.isSignedIn">
|
||||
<router-link class="item notifications" active-class="active" to="/my/notifications" ref="notificationButton">
|
||||
<fa :icon="faBell" fixed-width/><span class="text">{{ $t('notifications') }}</span>
|
||||
<i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<router-link class="item" active-class="active" to="/my/messaging">
|
||||
<fa :icon="faComments" fixed-width/><span class="text">{{ $t('messaging') }}</span>
|
||||
<i v-if="$store.state.i.hasUnreadMessagingMessage"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<router-link class="item" active-class="active" to="/my/drive">
|
||||
<fa :icon="faCloud" fixed-width/><span class="text">{{ $t('drive') }}</span>
|
||||
</router-link>
|
||||
<router-link class="item" active-class="active" to="/my/follow-requests" v-if="$store.state.i.isLocked">
|
||||
<fa :icon="faUserClock" fixed-width/><span class="text">{{ $t('followRequests') }}</span>
|
||||
<i v-if="$store.state.i.hasPendingReceivedFollowRequest"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<template v-for="item in menu">
|
||||
<div v-if="item === '-'" class="divider"></div>
|
||||
<component v-else-if="menuDef[item].display !== false" :is="menuDef[item].to ? 'router-link' : 'button'" class="item _button" :class="item" active-class="active" @click="() => { if (menuDef[item].action) menuDef[item].action() }" :to="menuDef[item].to">
|
||||
<fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $t(menuDef[item].title) }}</span>
|
||||
<i v-if="menuDef[item].indicated"><fa :icon="faCircle"/></i>
|
||||
</component>
|
||||
</template>
|
||||
<div class="divider"></div>
|
||||
<router-link class="item" active-class="active" to="/featured">
|
||||
<fa :icon="faFireAlt" fixed-width/><span class="text">{{ $t('featured') }}</span>
|
||||
</router-link>
|
||||
<router-link class="item" active-class="active" to="/explore">
|
||||
<fa :icon="faHashtag" fixed-width/><span class="text">{{ $t('explore') }}</span>
|
||||
</router-link>
|
||||
<router-link class="item" active-class="active" to="/announcements">
|
||||
<fa :icon="faBroadcastTower" fixed-width/><span class="text">{{ $t('announcements') }}</span>
|
||||
<i v-if="$store.getters.isSignedIn && $store.state.i.hasUnreadAnnouncement"><fa :icon="faCircle"/></i>
|
||||
</router-link>
|
||||
<button class="item _button" @click="search()">
|
||||
<fa :icon="faSearch" fixed-width/><span class="text">{{ $t('search') }}</span>
|
||||
</button>
|
||||
<div class="divider"></div>
|
||||
<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$store.getters.isSignedIn && ($store.state.i.isAdmin || $store.state.i.isModerator)" @click="oepnInstanceMenu">
|
||||
<fa :icon="faServer" fixed-width/><span class="text">{{ $t('instance') }}</span>
|
||||
</button>
|
||||
<button class="item _button" @click="more">
|
||||
<fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $t('more') }}</span>
|
||||
<i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadMentions || $store.state.i.hasUnreadSpecifiedNotes)"><fa :icon="faCircle"/></i>
|
||||
<i v-if="otherNavItemIndicated"><fa :icon="faCircle"/></i>
|
||||
</button>
|
||||
<router-link class="item" active-class="active" to="/preferences">
|
||||
<fa :icon="faCog" fixed-width/><span class="text">{{ $t('settings') }}</span>
|
||||
|
@ -141,10 +117,10 @@
|
|||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="$store.getters.isSignedIn && ($store.state.i.hasUnreadSpecifiedNotes || $store.state.i.hasPendingReceivedFollowRequest || $store.state.i.hasUnreadMessagingMessage || $store.state.i.hasUnreadAnnouncement)"><fa :icon="faCircle"/></i></button>
|
||||
<button class="button nav _button" @click="showNav = true" ref="navButton"><fa :icon="faBars"/><i v-if="navIndicated"><fa :icon="faCircle"/></i></button>
|
||||
<button v-if="$route.name === 'index'" class="button home _button" @click="top()"><fa :icon="faHome"/></button>
|
||||
<button v-else class="button home _button" @click="$router.push('/')"><fa :icon="faHome"/></button>
|
||||
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="$router.push('/my/notifications')" ref="notificationButton2"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
|
||||
<button v-if="$store.getters.isSignedIn" class="button notifications _button" @click="$router.push('/my/notifications')"><fa :icon="faBell"/><i v-if="$store.state.i.hasUnreadNotification"><fa :icon="faCircle"/></i></button>
|
||||
<button v-if="$store.getters.isSignedIn" class="button post _buttonPrimary" @click="post()"><fa :icon="faPencilAlt"/></button>
|
||||
</div>
|
||||
|
||||
|
@ -156,13 +132,14 @@
|
|||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faTerminal, faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faGamepad, faServer, faFileAlt, faSatellite, faInfoCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import i18n from './i18n';
|
||||
import { host, instanceName } from './config';
|
||||
import { search } from './scripts/search';
|
||||
import { createMenuDef } from './app';
|
||||
|
||||
const DESKTOP_THRESHOLD = 1100;
|
||||
|
||||
|
@ -187,6 +164,9 @@ export default Vue.extend({
|
|||
searchQuery: '',
|
||||
searchWait: false,
|
||||
widgetsEditMode: false,
|
||||
menuDef: createMenuDef({
|
||||
search: this.search
|
||||
}),
|
||||
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
|
||||
canBack: false,
|
||||
wallpaper: localStorage.getItem('wallpaper') != null,
|
||||
|
@ -206,6 +186,29 @@ export default Vue.extend({
|
|||
|
||||
widgets(): any[] {
|
||||
return this.$store.state.deviceUser.widgets;
|
||||
},
|
||||
|
||||
menu(): string[] {
|
||||
return this.$store.state.deviceUser.menu;
|
||||
},
|
||||
|
||||
otherNavItemIndicated(): boolean {
|
||||
if (!this.$store.getters.isSignedIn) return false;
|
||||
for (const def in this.menuDef) {
|
||||
if (this.menu.includes(def)) continue;
|
||||
if (this.menuDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
navIndicated(): boolean {
|
||||
if (!this.$store.getters.isSignedIn) return false;
|
||||
for (const def in this.menuDef) {
|
||||
if (def === 'timeline') continue;
|
||||
if (def === 'notifications') continue;
|
||||
if (this.menuDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -238,6 +241,23 @@ export default Vue.extend({
|
|||
id: 'c', data: {}
|
||||
}]);
|
||||
}
|
||||
|
||||
this.$store.watch(state => state.i, i => {
|
||||
for (const def in this.menuDef) {
|
||||
if (this.menuDef[def].indicate) {
|
||||
Vue.set(this.menuDef[def], 'indicated', this.menuDef[def].indicate(this.$store));
|
||||
}
|
||||
if (this.menuDef[def].show) {
|
||||
Vue.set(this.menuDef[def], 'display', this.menuDef[def].show(this.$store));
|
||||
}
|
||||
}
|
||||
}, { immediate: true, deep: true });
|
||||
} else {
|
||||
for (const def in this.menuDef) {
|
||||
if (this.menuDef[def].show) {
|
||||
Vue.set(this.menuDef[def], 'display', this.menuDef[def].show(this.$store));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -425,55 +445,16 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
more(ev) {
|
||||
const items = Object.keys(this.menuDef).filter(k => !this.menu.includes(k)).map(k => this.menuDef[k]).filter(def => def.show ? def.show(this.$store) : true).map(def => ({
|
||||
type: def.to ? 'link' : 'button',
|
||||
text: this.$t(def.title),
|
||||
icon: def.icon,
|
||||
to: def.to,
|
||||
action: def.action,
|
||||
indicate: def.indicate ? def.indicate(this.$store) : false,
|
||||
}));
|
||||
this.$root.menu({
|
||||
items: [...(this.$store.getters.isSignedIn ? [{
|
||||
type: 'link',
|
||||
text: this.$t('lists'),
|
||||
to: '/my/lists',
|
||||
icon: faListUl,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('groups'),
|
||||
to: '/my/groups',
|
||||
icon: faUsers,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('antennas'),
|
||||
to: '/my/antennas',
|
||||
icon: faSatellite,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('mentions'),
|
||||
to: '/my/mentions',
|
||||
icon: faAt,
|
||||
indicate: this.$store.state.i.hasUnreadMentions
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('directNotes'),
|
||||
to: '/my/messages',
|
||||
icon: faEnvelope,
|
||||
indicate: this.$store.state.i.hasUnreadSpecifiedNotes
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('favorites'),
|
||||
to: '/my/favorites',
|
||||
icon: faStar,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('pages'),
|
||||
to: '/my/pages',
|
||||
icon: faFileAlt,
|
||||
}, {
|
||||
type: 'link',
|
||||
text: this.$t('games'),
|
||||
to: '/games',
|
||||
icon: faGamepad,
|
||||
}, null] : []), {
|
||||
type: 'link',
|
||||
text: this.$t('scratchpad'),
|
||||
to: '/scratchpad',
|
||||
icon: faTerminal,
|
||||
}, null, {
|
||||
items: [...items, null, {
|
||||
type: 'link',
|
||||
text: this.$t('help'),
|
||||
to: '/docs',
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
<x-theme/>
|
||||
|
||||
<x-sidebar/>
|
||||
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faMusic"/> {{ $t('sounds') }}</div>
|
||||
<div class="_content">
|
||||
|
@ -90,13 +92,13 @@
|
|||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faImage, faCog, faMusic, faPlay, faVolumeUp, faVolumeMute } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkInput from '../../components/ui/input.vue';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkSwitch from '../../components/ui/switch.vue';
|
||||
import MkSelect from '../../components/ui/select.vue';
|
||||
import MkRadio from '../../components/ui/radio.vue';
|
||||
import MkRange from '../../components/ui/range.vue';
|
||||
import XTheme from './theme.vue';
|
||||
import XSidebar from './sidebar.vue';
|
||||
import i18n from '../../i18n';
|
||||
import { langs } from '../../config';
|
||||
|
||||
|
@ -128,7 +130,7 @@ export default Vue.extend({
|
|||
|
||||
components: {
|
||||
XTheme,
|
||||
MkInput,
|
||||
XSidebar,
|
||||
MkButton,
|
||||
MkSwitch,
|
||||
MkSelect,
|
||||
|
|
86
src/client/pages/preferences/sidebar.vue
Normal file
86
src/client/pages/preferences/sidebar.vue
Normal file
|
@ -0,0 +1,86 @@
|
|||
<template>
|
||||
<section class="_card">
|
||||
<div class="_title"><fa :icon="faListUl"/> {{ $t('sidebar') }}</div>
|
||||
<div class="_content">
|
||||
<mk-textarea v-model="items" tall>
|
||||
<span>{{ $t('sidebar') }}</span>
|
||||
<template #desc><button class="_textButton" @click="addItem">{{ $t('addItem') }}</button></template>
|
||||
</mk-textarea>
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<mk-button inline @click="save()" primary><fa :icon="faSave"/> {{ $t('save') }}</mk-button>
|
||||
<mk-button inline @click="reset()"><fa :icon="faRedo"/> {{ $t('default') }}</mk-button>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { faListUl, faSave, faRedo } from '@fortawesome/free-solid-svg-icons';
|
||||
import MkButton from '../../components/ui/button.vue';
|
||||
import MkTextarea from '../../components/ui/textarea.vue';
|
||||
import i18n from '../../i18n';
|
||||
import { defaultDeviceUserSettings } from '../../store';
|
||||
import { createMenuDef } from '../../app';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n,
|
||||
|
||||
components: {
|
||||
MkButton,
|
||||
MkTextarea,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
menuDef: createMenuDef({}),
|
||||
items: '',
|
||||
faListUl, faSave, faRedo
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
splited(): string[] {
|
||||
return this.items.trim().split('\n').filter(x => x.trim() !== '');
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.items = this.$store.state.deviceUser.menu.join('\n');
|
||||
},
|
||||
|
||||
methods: {
|
||||
async addItem() {
|
||||
const menu = Object.keys(this.menuDef).filter(k => !this.$store.state.deviceUser.menu.includes(k));
|
||||
const { canceled, result: item } = await this.$root.dialog({
|
||||
type: null,
|
||||
title: this.$t('addItem'),
|
||||
select: {
|
||||
items: [...menu.map(k => ({
|
||||
value: k, text: this.$t(this.menuDef[k].title)
|
||||
})), ...[{
|
||||
value: '-', text: this.$t('divider')
|
||||
}]]
|
||||
},
|
||||
showCancelButton: true
|
||||
});
|
||||
if (canceled) return;
|
||||
this.items = [...this.splited, item].join('\n');
|
||||
this.save();
|
||||
},
|
||||
|
||||
save() {
|
||||
this.$store.commit('deviceUser/setMenu', this.splited);
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.$store.commit('deviceUser/setMenu', defaultDeviceUserSettings.menu);
|
||||
this.items = this.$store.state.deviceUser.menu.join('\n');
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
</style>
|
|
@ -16,16 +16,27 @@ export const defaultSettings = {
|
|||
reactions: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
|
||||
};
|
||||
|
||||
const defaultDeviceUserSettings = {
|
||||
export const defaultDeviceUserSettings = {
|
||||
visibility: 'public',
|
||||
localOnly: false,
|
||||
widgets: [],
|
||||
tl: {
|
||||
src: 'home'
|
||||
},
|
||||
menu: [
|
||||
'notifications',
|
||||
'messaging',
|
||||
'drive',
|
||||
'-',
|
||||
'followRequests',
|
||||
'featured',
|
||||
'explore',
|
||||
'announcements',
|
||||
'search',
|
||||
],
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
export const defaultDeviceSettings = {
|
||||
lang: null,
|
||||
loadRawImages: false,
|
||||
alwaysShowNsfw: false,
|
||||
|
@ -237,6 +248,10 @@ export default () => new Vuex.Store({
|
|||
};
|
||||
},
|
||||
|
||||
setMenu(state, menu) {
|
||||
state.menu = menu;
|
||||
},
|
||||
|
||||
setVisibility(state, visibility) {
|
||||
state.visibility = visibility;
|
||||
},
|
||||
|
|
|
@ -146,13 +146,7 @@ module.exports = {
|
|||
resolveLoader: {
|
||||
modules: ['node_modules']
|
||||
},
|
||||
cache: {
|
||||
type: 'filesystem',
|
||||
|
||||
buildDependencies: {
|
||||
config: [__filename]
|
||||
}
|
||||
},
|
||||
cache: false,
|
||||
devtool: false, //'source-map',
|
||||
mode: isProduction ? 'production' : 'development'
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue