mirror of
https://activitypub.software/TransFem-org/Sharkey
synced 2024-11-25 15:35:12 +00:00
Refine UI (#7806)
* wip * wip * wip * wip * wip * wip * wip * wip * Update default.vue * wip
This commit is contained in:
parent
d252514a39
commit
361069314f
20 changed files with 517 additions and 386 deletions
|
@ -73,6 +73,22 @@ export default defineComponent({
|
|||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes earwiggleleft {
|
||||
from { transform: rotate(37.6deg) skew(30deg); }
|
||||
25% { transform: rotate(10deg) skew(30deg); }
|
||||
50% { transform: rotate(20deg) skew(30deg); }
|
||||
75% { transform: rotate(0deg) skew(30deg); }
|
||||
to { transform: rotate(37.6deg) skew(30deg); }
|
||||
}
|
||||
|
||||
@keyframes earwiggleright {
|
||||
from { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
30% { transform: rotate(-10deg) skew(-30deg); }
|
||||
55% { transform: rotate(-20deg) skew(-30deg); }
|
||||
75% { transform: rotate(0deg) skew(-30deg); }
|
||||
to { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
}
|
||||
|
||||
.eiwwqkts {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
@ -132,6 +148,16 @@ export default defineComponent({
|
|||
border-radius: 75% 0 75% 75%;
|
||||
transform: rotate(-37.5deg) skew(-30deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:before {
|
||||
animation: earwiggleleft 1s infinite;
|
||||
}
|
||||
|
||||
&:after {
|
||||
animation: earwiggleright 1s infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -99,7 +99,8 @@ export default defineComponent({
|
|||
z-index: 10;
|
||||
position: sticky;
|
||||
top: var(--stickyTop, 0px);
|
||||
background: var(--panel);
|
||||
padding: var(--x-padding);
|
||||
background: var(--x-header, var(--panel));
|
||||
/* TODO panelの半透明バージョンをプログラマティックに作りたい
|
||||
background: var(--X17);
|
||||
-webkit-backdrop-filter: var(--blur, blur(8px));
|
||||
|
|
|
@ -245,7 +245,7 @@ export default defineComponent({
|
|||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--inputBorder);
|
||||
border: solid 0.5px var(--inputBorder);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
|
|
|
@ -212,7 +212,7 @@ export default defineComponent({
|
|||
font-size: 1em;
|
||||
color: var(--fg);
|
||||
background: var(--panel);
|
||||
border: solid 1px var(--inputBorder);
|
||||
border: solid 0.5px var(--inputBorder);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
|
|
|
@ -15,7 +15,7 @@ if (localStorage.getItem('accounts') != null) {
|
|||
|
||||
import * as Sentry from '@sentry/browser';
|
||||
import { Integrations } from '@sentry/tracing';
|
||||
import { computed, createApp, watch, markRaw } from 'vue';
|
||||
import { computed, createApp, watch, markRaw, version as vueVersion } from 'vue';
|
||||
import compareVersions from 'compare-versions';
|
||||
|
||||
import widgets from '@client/widgets';
|
||||
|
@ -47,6 +47,8 @@ window.onunhandledrejection = null;
|
|||
if (_DEV_) {
|
||||
console.warn('Development mode!!!');
|
||||
|
||||
console.info(`vue ${vueVersion}`);
|
||||
|
||||
(window as any).$i = $i;
|
||||
(window as any).$store = defaultStore;
|
||||
|
||||
|
|
134
src/client/pages/emojis.category.vue
Normal file
134
src/client/pages/emojis.category.vue
Normal file
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div class="driuhtrh">
|
||||
<div class="query">
|
||||
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
|
||||
<template #prefix><i class="fas fa-search"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<div class="tags">
|
||||
<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkFolder class="emojis" v-if="searchEmojis">
|
||||
<template #header>{{ $ts.searchResult }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder class="emojis" v-for="category in customEmojiCategories" :key="category">
|
||||
<template #header>{{ category || $ts.other }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
import MkFolder from '@client/components/ui/folder.vue';
|
||||
import MkTab from '@client/components/tab.vue';
|
||||
import * as os from '@client/os';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { emojiCategories, emojiTags } from '@client/instance';
|
||||
import XEmoji from './emojis.emoji.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkFolder,
|
||||
MkTab,
|
||||
XEmoji,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
q: '',
|
||||
customEmojiCategories: emojiCategories,
|
||||
customEmojis: this.$instance.emojis,
|
||||
tags: emojiTags,
|
||||
selectedTags: new Set(),
|
||||
searchEmojis: null,
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
q() { this.search(); },
|
||||
selectedTags: {
|
||||
handler() {
|
||||
this.search();
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
search() {
|
||||
if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
|
||||
this.searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedTags.size === 0) {
|
||||
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
|
||||
} else {
|
||||
this.searchEmojis = this.customEmojis.filter(e => (e.name.includes(this.q) || e.aliases.includes(this.q)) && [...this.selectedTags].every(t => e.aliases.includes(t)));
|
||||
}
|
||||
},
|
||||
|
||||
toggleTag(tag) {
|
||||
if (this.selectedTags.has(tag)) {
|
||||
this.selectedTags.delete(tag);
|
||||
} else {
|
||||
this.selectedTags.add(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driuhtrh {
|
||||
background: var(--bg);
|
||||
|
||||
> .query {
|
||||
background: var(--bg);
|
||||
padding: 16px;
|
||||
|
||||
> .tags {
|
||||
> .tag {
|
||||
display: inline-block;
|
||||
margin: 8px 8px 0 0;
|
||||
padding: 4px 8px;
|
||||
font-size: 0.9em;
|
||||
background: var(--panel);
|
||||
border: solid 0.5px var(--divider);
|
||||
border-radius: 5px;
|
||||
|
||||
&.active {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .emojis {
|
||||
--x-header: var(--bg);
|
||||
--x-padding: 0 16px;
|
||||
|
||||
.zuvgdzyt {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
grid-gap: 12px;
|
||||
margin: 0 var(--margin) var(--margin) var(--margin);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
92
src/client/pages/emojis.emoji.vue
Normal file
92
src/client/pages/emojis.emoji.vue
Normal file
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<button class="zuvgdzyu _button" @click="menu">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import * as os from '@client/os';
|
||||
import copyToClipboard from '@client/scripts/copy-to-clipboard';
|
||||
import VanillaTilt from 'vanilla-tilt';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
emoji: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
VanillaTilt.init(this.$el, {
|
||||
reverse: true,
|
||||
gyroscope: false,
|
||||
scale: 1.1,
|
||||
speed: 500,
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
menu(ev) {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + this.emoji.name + ':',
|
||||
}, {
|
||||
text: this.$ts.copy,
|
||||
icon: 'fas fa-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${this.emoji.name}:`);
|
||||
os.success();
|
||||
}
|
||||
}], ev.currentTarget || ev.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.zuvgdzyu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
background: var(--panel);
|
||||
border-radius: 8px;
|
||||
transform-style: preserve-3d;
|
||||
transform: perspective(1000px);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
transform: translateZ(20px);
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
transform: translateZ(10px);
|
||||
|
||||
> .name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .info {
|
||||
opacity: 0.5;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,151 +1,30 @@
|
|||
<template>
|
||||
<div class="driuhtrh">
|
||||
<div class="query">
|
||||
<MkInput v-model="q" class="_inputNoTopMargin _inputNoBottomMargin" :placeholder="$ts.search">
|
||||
<template #prefix><i class="fas fa-search"></i></template>
|
||||
</MkInput>
|
||||
</div>
|
||||
|
||||
<div class="emojis">
|
||||
<MkFolder v-if="searchEmojis">
|
||||
<template #header>{{ $ts.searchResult }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<button v-for="emoji in searchEmojis" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</MkFolder>
|
||||
<MkFolder v-for="category in customEmojiCategories" :key="category">
|
||||
<template #header>{{ category || $ts.other }}</template>
|
||||
<div class="zuvgdzyt">
|
||||
<button v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji _button" @click="menu(emoji, $event)">
|
||||
<img :src="emoji.url" class="img" :alt="emoji.name"/>
|
||||
<div class="body">
|
||||
<div class="name _monospace">{{ emoji.name }}</div>
|
||||
<div class="info">{{ emoji.aliases.join(' ') }}</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</div>
|
||||
</div>
|
||||
<XCategory v-if="tab === 'category'"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import MkButton from '@client/components/ui/button.vue';
|
||||
import MkInput from '@client/components/ui/input.vue';
|
||||
import MkSelect from '@client/components/ui/select.vue';
|
||||
import MkFolder from '@client/components/ui/folder.vue';
|
||||
import { defineComponent, computed } from 'vue';
|
||||
import * as os from '@client/os';
|
||||
import * as symbols from '@client/symbols';
|
||||
import { emojiCategories } from '@client/instance';
|
||||
import copyToClipboard from '@client/scripts/copy-to-clipboard';
|
||||
import XCategory from './emojis.category.vue';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
MkInput,
|
||||
MkSelect,
|
||||
MkFolder,
|
||||
XCategory,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
[symbols.PAGE_INFO]: computed(() => ({
|
||||
title: this.$ts.customEmojis,
|
||||
icon: 'fas fa-laugh'
|
||||
},
|
||||
q: '',
|
||||
customEmojiCategories: emojiCategories,
|
||||
customEmojis: this.$instance.emojis,
|
||||
searchEmojis: null,
|
||||
icon: 'fas fa-laugh',
|
||||
bg: 'var(--bg)',
|
||||
})),
|
||||
tab: 'category',
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
q() {
|
||||
if (this.q === '' || this.q == null) {
|
||||
this.searchEmojis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.searchEmojis = this.customEmojis.filter(e => e.name.includes(this.q) || e.aliases.includes(this.q));
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
menu(emoji, ev) {
|
||||
os.popupMenu([{
|
||||
type: 'label',
|
||||
text: ':' + emoji.name + ':',
|
||||
}, {
|
||||
text: this.$ts.copy,
|
||||
icon: 'fas fa-copy',
|
||||
action: () => {
|
||||
copyToClipboard(`:${emoji.name}:`);
|
||||
os.success();
|
||||
}
|
||||
}], ev.currentTarget || ev.target);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.driuhtrh {
|
||||
> .query {
|
||||
background: var(--bg);
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
> .emojis {
|
||||
.zuvgdzyt {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
grid-gap: 12px;
|
||||
margin: 0 var(--margin) var(--margin) var(--margin);
|
||||
|
||||
> .emoji {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border: solid 1px var(--divider);
|
||||
border-radius: 8px;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
> .img {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
> .body {
|
||||
padding: 0 0 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
|
||||
> .name {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
> .info {
|
||||
opacity: 0.5;
|
||||
font-size: 0.9em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -22,7 +22,8 @@ export default defineComponent({
|
|||
return {
|
||||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.favorites,
|
||||
icon: 'fas fa-star'
|
||||
icon: 'fas fa-star',
|
||||
bg: 'var(--bg)',
|
||||
},
|
||||
pagination: {
|
||||
endpoint: 'i/favorites',
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<template>
|
||||
<div class="fcuexfpr _root">
|
||||
<div class="fcuexfpr">
|
||||
<div class="_root">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="note" class="note">
|
||||
<div class="_gap" v-if="showNext">
|
||||
|
@ -33,6 +34,7 @@
|
|||
<MkLoading v-else/>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -63,12 +65,14 @@ export default defineComponent({
|
|||
return {
|
||||
[symbols.PAGE_INFO]: computed(() => this.note ? {
|
||||
title: this.$ts.note,
|
||||
subtitle: new Date(this.note.createdAt).toLocaleString(),
|
||||
avatar: this.note.user,
|
||||
path: `/notes/${this.note.id}`,
|
||||
share: {
|
||||
title: this.$t('noteOf', { user: this.note.user.name }),
|
||||
text: this.note.text,
|
||||
},
|
||||
bg: 'var(--bg)',
|
||||
} : null),
|
||||
note: null,
|
||||
clips: null,
|
||||
|
@ -149,6 +153,7 @@ export default defineComponent({
|
|||
.fcuexfpr {
|
||||
background: var(--bg);
|
||||
|
||||
> ._root {
|
||||
> .note {
|
||||
> .main {
|
||||
> .load {
|
||||
|
@ -202,4 +207,5 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -21,6 +21,7 @@ export default defineComponent({
|
|||
[symbols.PAGE_INFO]: {
|
||||
title: this.$ts.notifications,
|
||||
icon: 'fas fa-bell',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
text: this.$ts.markAllAsRead,
|
||||
icon: 'fas fa-check',
|
||||
|
|
|
@ -86,7 +86,8 @@ export default defineComponent({
|
|||
setup(props, context) {
|
||||
const indexInfo = {
|
||||
title: i18n.locale.settings,
|
||||
icon: 'fas fa-cog'
|
||||
icon: 'fas fa-cog',
|
||||
bg: 'var(--bg)',
|
||||
};
|
||||
const INFO = ref(indexInfo);
|
||||
const page = ref(props.initialPage);
|
||||
|
|
|
@ -1,25 +1,10 @@
|
|||
<template>
|
||||
<div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/>
|
||||
<div class="tabs">
|
||||
<div class="left">
|
||||
<button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><i class="fas fa-home"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><i class="fas fa-comments"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><i class="fas fa-share-alt"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><i class="fas fa-globe"></i></button>
|
||||
<span class="divider"></span>
|
||||
<button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><i class="fas fa-at"></i><i v-if="$i.hasUnreadMentions" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><i class="fas fa-envelope"></i><i v-if="$i.hasUnreadSpecifiedNotes" class="fas fa-circle i"></i></button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><i class="fas fa-satellite-dish"></i><i v-if="$i.hasUnreadChannel" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><i class="fas fa-satellite"></i><i v-if="$i.hasUnreadAntenna" class="fas fa-circle i"></i></button>
|
||||
<button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><i class="fas fa-list-ul"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
|
||||
|
||||
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
|
||||
<div class="tl">
|
||||
<div class="tl _block">
|
||||
<XTimeline ref="tl" class="tl"
|
||||
:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src"
|
||||
:src="src"
|
||||
|
@ -63,12 +48,37 @@ export default defineComponent({
|
|||
queue: 0,
|
||||
[symbols.PAGE_INFO]: computed(() => ({
|
||||
title: this.$ts.timeline,
|
||||
subtitle: this.src === 'local' ? this.$ts._timelines.local : this.src === 'social' ? this.$ts._timelines.social : this.src === 'global' ? this.$ts._timelines.global : this.$ts._timelines.home,
|
||||
icon: this.src === 'local' ? 'fas fa-comments' : this.src === 'social' ? 'fas fa-share-alt' : this.src === 'global' ? 'fas fa-globe' : 'fas fa-home',
|
||||
bg: 'var(--bg)',
|
||||
actions: [{
|
||||
icon: 'fas fa-calendar-alt',
|
||||
text: this.$ts.jumpToSpecifiedDate,
|
||||
handler: this.timetravel
|
||||
}],
|
||||
tabs: [{
|
||||
active: this.src === 'home',
|
||||
title: this.$ts._timelines.home,
|
||||
icon: 'fas fa-home',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'home'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'local',
|
||||
title: this.$ts._timelines.local,
|
||||
icon: 'fas fa-comments',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'local'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'social',
|
||||
title: this.$ts._timelines.social,
|
||||
icon: 'fas fa-share-alt',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'social'; this.saveSrc(); },
|
||||
}, {
|
||||
active: this.src === 'global',
|
||||
title: this.$ts._timelines.global,
|
||||
icon: 'fas fa-globe',
|
||||
iconOnly: true,
|
||||
onClick: () => { this.src = 'global'; this.saveSrc(); },
|
||||
}]
|
||||
})),
|
||||
};
|
||||
|
@ -213,6 +223,8 @@ export default defineComponent({
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.cmuxhskf {
|
||||
padding: var(--margin);
|
||||
|
||||
> .new {
|
||||
position: sticky;
|
||||
top: calc(var(--stickyTop, 0px) + 16px);
|
||||
|
@ -227,79 +239,15 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
padding: 0 8px;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
border-bottom: solid 0.5px var(--divider);
|
||||
|
||||
// 影の都合上
|
||||
position: relative;
|
||||
|
||||
> .right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
> .left, > .right {
|
||||
> .tab {
|
||||
position: relative;
|
||||
height: 50px;
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--fgHighlighted);
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .i {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 8px;
|
||||
color: var(--indicator);
|
||||
font-size: 8px;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
> .divider {
|
||||
display: inline-block;
|
||||
width: 1px;
|
||||
height: 28px;
|
||||
vertical-align: middle;
|
||||
margin: 0 8px;
|
||||
background: var(--divider);
|
||||
}
|
||||
}
|
||||
> .tl {
|
||||
background: var(--bg);
|
||||
border-radius: var(--radius);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
&.min-width_800px {
|
||||
> .tl {
|
||||
background: var(--bg);
|
||||
padding: 32px 0;
|
||||
|
||||
> .tl {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -60,24 +60,10 @@
|
|||
<XPhotos :user="user" :key="user.id" class="_gap"/>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="nav _gap">
|
||||
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link">
|
||||
<i class="fas fa-comment-alt icon"></i>
|
||||
<span>{{ $ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link">
|
||||
<i class="fas fa-paperclip icon"></i>
|
||||
<span>{{ $ts.clips }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link">
|
||||
<i class="fas fa-file-alt icon"></i>
|
||||
<span>{{ $ts.pages }}</span>
|
||||
</MkA>
|
||||
<div class="actions">
|
||||
<button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
|
||||
<MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="page === 'index'">
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gap">
|
||||
<XNote v-for="note in user.pinnedNotes" class="note _gap" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
|
||||
|
@ -178,25 +164,6 @@
|
|||
</div>
|
||||
|
||||
<div class="contents">
|
||||
<div class="nav _gap">
|
||||
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link" v-click-anime>
|
||||
<i class="fas fa-comment-alt icon"></i>
|
||||
<span>{{ $ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link" v-click-anime>
|
||||
<i class="fas fa-paperclip icon"></i>
|
||||
<span>{{ $ts.clips }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link" v-click-anime>
|
||||
<i class="fas fa-file-alt icon"></i>
|
||||
<span>{{ $ts.pages }}</span>
|
||||
</MkA>
|
||||
<MkA :to="userPage(user, 'gallery')" :class="{ active: page === 'gallery' }" class="link" v-click-anime>
|
||||
<i class="fas fa-icons icon"></i>
|
||||
<span>{{ $ts.gallery }}</span>
|
||||
</MkA>
|
||||
</div>
|
||||
|
||||
<template v-if="page === 'index'">
|
||||
<div>
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gap">
|
||||
|
@ -283,6 +250,27 @@ export default defineComponent({
|
|||
share: {
|
||||
title: this.user.name,
|
||||
},
|
||||
bg: 'var(--bg)',
|
||||
tabs: [{
|
||||
active: this.page === 'index',
|
||||
title: this.$ts.overview,
|
||||
icon: 'fas fa-home',
|
||||
}, {
|
||||
active: this.page === 'clips',
|
||||
title: this.$ts.clips,
|
||||
icon: 'fas fa-paperclip',
|
||||
onClick: () => { this.page = 'clips'; },
|
||||
}, {
|
||||
active: this.page === 'pages',
|
||||
title: this.$ts.pages,
|
||||
icon: 'fas fa-file-alt',
|
||||
onClick: () => { this.page = 'pages'; },
|
||||
}, {
|
||||
active: this.page === 'gallery',
|
||||
title: this.$ts.gallery,
|
||||
icon: 'fas fa-icons',
|
||||
onClick: () => { this.page = 'gallery'; },
|
||||
}]
|
||||
} : null),
|
||||
user: null,
|
||||
error: null,
|
||||
|
@ -314,7 +302,7 @@ export default defineComponent({
|
|||
|
||||
mounted() {
|
||||
window.requestAnimationFrame(this.parallaxLoop);
|
||||
this.narrow = this.$el.clientWidth < 1000;
|
||||
this.narrow = true//this.$el.clientWidth < 1000;
|
||||
},
|
||||
|
||||
beforeUnmount() {
|
||||
|
@ -772,37 +760,6 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
> .contents {
|
||||
> .nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 90%;
|
||||
|
||||
> .link {
|
||||
flex: 1;
|
||||
display: inline-block;
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
border-bottom: solid 3px transparent;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
color: var(--fgHighlighted);
|
||||
}
|
||||
|
||||
> .icon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .content {
|
||||
margin-bottom: var(--margin);
|
||||
}
|
||||
|
|
|
@ -245,7 +245,6 @@ hr {
|
|||
._panel {
|
||||
background: var(--panel);
|
||||
border-radius: var(--radius);
|
||||
border: var(--panelBorder);
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
navFg: '@fg',
|
||||
navHoverFg: ':lighten<17<@fg',
|
||||
navActive: '@accent',
|
||||
navIndicator: '@accent',
|
||||
navIndicator: '@indicator',
|
||||
link: '#44a4c1',
|
||||
hashtag: '#ff9156',
|
||||
mention: '@accent',
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
navFg: '@fg',
|
||||
navHoverFg: ':darken<17<@fg',
|
||||
navActive: '@accent',
|
||||
navIndicator: '@accent',
|
||||
navIndicator: '@indicator',
|
||||
link: '#44a4c1',
|
||||
hashtag: '#ff9156',
|
||||
mention: '@accent',
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
<template>
|
||||
<div class="fdidabkb" :class="{ center }" :style="`--height:${height};`" :key="key">
|
||||
<div class="fdidabkb" :class="{ slim: titleOnly || narrow }" :style="`--height:${height};`" :key="key">
|
||||
<transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear>
|
||||
<div class="buttons left" v-if="backButton">
|
||||
<button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button>
|
||||
</div>
|
||||
</transition>
|
||||
<template v-if="info">
|
||||
<div class="titleContainer">
|
||||
<div class="titleContainer" @click="showTabsPopup">
|
||||
<i v-if="info.icon" class="icon" :class="info.icon"></i>
|
||||
<MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
|
||||
|
||||
<div class="title">
|
||||
<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/>
|
||||
<div v-else-if="info.title" class="title">{{ info.title }}</div>
|
||||
<div class="subtitle" v-if="info.subtitle">
|
||||
<div class="subtitle" v-if="!narrow && info.subtitle">
|
||||
{{ info.subtitle }}
|
||||
</div>
|
||||
<div class="subtitle activeTab" v-if="narrow && hasTabs">
|
||||
{{ info.tabs.find(tab => tab.active)?.title }}
|
||||
<i class="chevron fas fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tabs" v-if="!narrow">
|
||||
<button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title">
|
||||
<i v-if="tab.icon" class="icon" :class="tab.icon"></i>
|
||||
<span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons right">
|
||||
<template v-if="info.actions && showActions">
|
||||
<template v-if="info.actions && !narrow">
|
||||
<button v-for="action in info.actions" class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button>
|
||||
</template>
|
||||
<button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button>
|
||||
|
@ -52,24 +62,28 @@ export default defineComponent({
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
center: {
|
||||
titleOnly: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showActions: false,
|
||||
narrow: false,
|
||||
height: 0,
|
||||
key: 0,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
hasTabs(): boolean {
|
||||
return this.info.tabs && this.info.tabs.length > 0;
|
||||
},
|
||||
|
||||
shouldShowMenu() {
|
||||
if (this.info.actions != null && !this.showActions) return true;
|
||||
if (this.info.actions != null && this.narrow) return true;
|
||||
if (this.info.menu != null) return true;
|
||||
if (this.info.share != null) return true;
|
||||
if (this.menu != null) return true;
|
||||
|
@ -85,10 +99,10 @@ export default defineComponent({
|
|||
|
||||
mounted() {
|
||||
this.height = this.$el.parentElement.offsetHeight + 'px';
|
||||
this.showActions = this.$el.parentElement.offsetWidth >= 500;
|
||||
this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
|
||||
new ResizeObserver((entries, observer) => {
|
||||
this.height = this.$el.parentElement.offsetHeight + 'px';
|
||||
this.showActions = this.$el.parentElement.offsetWidth >= 500;
|
||||
this.narrow = this.titleOnly || this.$el.parentElement.offsetWidth < 500;
|
||||
}).observe(this.$el);
|
||||
},
|
||||
|
||||
|
@ -102,7 +116,7 @@ export default defineComponent({
|
|||
|
||||
showMenu(ev) {
|
||||
let menu = this.info.menu ? this.info.menu() : [];
|
||||
if (!this.showActions && this.info.actions) {
|
||||
if (this.narrow && this.info.actions) {
|
||||
menu = [...this.info.actions.map(x => ({
|
||||
text: x.text,
|
||||
icon: x.icon,
|
||||
|
@ -124,6 +138,18 @@ export default defineComponent({
|
|||
popupMenu(menu, ev.currentTarget || ev.target);
|
||||
},
|
||||
|
||||
showTabsPopup(ev) {
|
||||
if (!this.hasTabs) return;
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
const menu = this.info.tabs.map(tab => ({
|
||||
text: tab.title,
|
||||
icon: tab.icon,
|
||||
action: tab.onClick,
|
||||
}));
|
||||
popupMenu(menu, ev.currentTarget || ev.target);
|
||||
},
|
||||
|
||||
preventDrag(ev) {
|
||||
ev.stopPropagation();
|
||||
}
|
||||
|
@ -135,7 +161,7 @@ export default defineComponent({
|
|||
.fdidabkb {
|
||||
display: flex;
|
||||
|
||||
&.center {
|
||||
&.slim {
|
||||
text-align: center;
|
||||
|
||||
> .titleContainer {
|
||||
|
@ -190,6 +216,7 @@ export default defineComponent({
|
|||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
|
||||
> .avatar {
|
||||
$size: 32px;
|
||||
|
@ -219,6 +246,54 @@ export default defineComponent({
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.activeTab {
|
||||
text-align: center;
|
||||
|
||||
> .chevron {
|
||||
display: inline-block;
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tabs {
|
||||
margin-left: 16px;
|
||||
font-size: 0.8em;
|
||||
|
||||
> .tab {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0 10px;
|
||||
height: 100%;
|
||||
font-weight: normal;
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
background: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
> .icon + .title {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<main class="main" @contextmenu.stop="onContextmenu">
|
||||
<main class="main" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
|
||||
<header class="header" @click="onHeaderClick">
|
||||
<XHeader :info="pageInfo" :back-button="true" @back="back()"/>
|
||||
</header>
|
||||
|
@ -145,6 +145,15 @@ export default defineComponent({
|
|||
}
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
window.addEventListener('touchmove', ev => {
|
||||
this.$refs.live2d.contentWindow.postMessage({
|
||||
type: 'moveCursor',
|
||||
body: {
|
||||
x: ev.touches[0].clientX - iframeRect.left,
|
||||
y: ev.touches[0].clientY - iframeRect.top,
|
||||
}
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="mk-app" :class="{ wallpaper }">
|
||||
<XSidebar ref="nav" class="sidebar"/>
|
||||
|
||||
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu">
|
||||
<header class="header" ref="header" @click="onHeaderClick">
|
||||
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
|
||||
<header class="header" ref="header" @click="onHeaderClick" :style="{ background: pageInfo?.bg }">
|
||||
<XHeader :info="pageInfo" :back-button="true" @back="back()"/>
|
||||
</header>
|
||||
<main ref="main">
|
||||
|
@ -258,7 +258,6 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
> .sidebar {
|
||||
border-right: solid 0.5px var(--divider);
|
||||
}
|
||||
|
||||
> .contents {
|
||||
|
@ -313,7 +312,8 @@ export default defineComponent({
|
|||
|
||||
> .widgets {
|
||||
padding: 0 var(--margin);
|
||||
border-left: solid 0.5px var(--divider);
|
||||
//border-left: solid 0.5px var(--divider);
|
||||
background: var(--navBg);
|
||||
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
|
|
Loading…
Reference in a new issue