* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update default.vue

* wip
This commit is contained in:
syuilo 2021-09-17 22:39:15 +09:00 committed by GitHub
parent d252514a39
commit 361069314f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 517 additions and 386 deletions

View file

@ -73,6 +73,22 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <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 { .eiwwqkts {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -132,6 +148,16 @@ export default defineComponent({
border-radius: 75% 0 75% 75%; border-radius: 75% 0 75% 75%;
transform: rotate(-37.5deg) skew(-30deg); transform: rotate(-37.5deg) skew(-30deg);
} }
&:hover {
&:before {
animation: earwiggleleft 1s infinite;
}
&:after {
animation: earwiggleright 1s infinite;
}
}
} }
} }
</style> </style>

View file

@ -99,7 +99,8 @@ export default defineComponent({
z-index: 10; z-index: 10;
position: sticky; position: sticky;
top: var(--stickyTop, 0px); top: var(--stickyTop, 0px);
background: var(--panel); padding: var(--x-padding);
background: var(--x-header, var(--panel));
/* TODO panel /* TODO panel
background: var(--X17); background: var(--X17);
-webkit-backdrop-filter: var(--blur, blur(8px)); -webkit-backdrop-filter: var(--blur, blur(8px));

View file

@ -245,7 +245,7 @@ export default defineComponent({
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
background: var(--panel); background: var(--panel);
border: solid 1px var(--inputBorder); border: solid 0.5px var(--inputBorder);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;

View file

@ -212,7 +212,7 @@ export default defineComponent({
font-size: 1em; font-size: 1em;
color: var(--fg); color: var(--fg);
background: var(--panel); background: var(--panel);
border: solid 1px var(--inputBorder); border: solid 0.5px var(--inputBorder);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
box-shadow: none; box-shadow: none;

View file

@ -15,7 +15,7 @@ if (localStorage.getItem('accounts') != null) {
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { Integrations } from '@sentry/tracing'; 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 compareVersions from 'compare-versions';
import widgets from '@client/widgets'; import widgets from '@client/widgets';
@ -47,6 +47,8 @@ window.onunhandledrejection = null;
if (_DEV_) { if (_DEV_) {
console.warn('Development mode!!!'); console.warn('Development mode!!!');
console.info(`vue ${vueVersion}`);
(window as any).$i = $i; (window as any).$i = $i;
(window as any).$store = defaultStore; (window as any).$store = defaultStore;

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

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

View file

@ -1,151 +1,30 @@
<template> <template>
<div class="driuhtrh"> <XCategory v-if="tab === 'category'"/>
<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>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; 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 * as os from '@client/os'; import * as os from '@client/os';
import * as symbols from '@client/symbols'; import * as symbols from '@client/symbols';
import { emojiCategories } from '@client/instance'; import XCategory from './emojis.category.vue';
import copyToClipboard from '@client/scripts/copy-to-clipboard';
export default defineComponent({ export default defineComponent({
components: { components: {
MkButton, XCategory,
MkInput,
MkSelect,
MkFolder,
}, },
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.customEmojis, title: this.$ts.customEmojis,
icon: 'fas fa-laugh' icon: 'fas fa-laugh',
}, bg: 'var(--bg)',
q: '', })),
customEmojiCategories: emojiCategories, tab: 'category',
customEmojis: this.$instance.emojis,
searchEmojis: null,
} }
}, },
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> </script>
<style lang="scss" scoped> <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> </style>

View file

@ -22,7 +22,8 @@ export default defineComponent({
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.favorites, title: this.$ts.favorites,
icon: 'fas fa-star' icon: 'fas fa-star',
bg: 'var(--bg)',
}, },
pagination: { pagination: {
endpoint: 'i/favorites', endpoint: 'i/favorites',

View file

@ -1,37 +1,39 @@
<template> <template>
<div class="fcuexfpr _root"> <div class="fcuexfpr">
<transition name="fade" mode="out-in"> <div class="_root">
<div v-if="note" class="note"> <transition name="fade" mode="out-in">
<div class="_gap" v-if="showNext"> <div v-if="note" class="note">
<XNotes class="_content" :pagination="next" :no-gap="true"/> <div class="_gap" v-if="showNext">
</div> <XNotes class="_content" :pagination="next" :no-gap="true"/>
<div class="main _gap">
<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
<div class="note _gap">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
</div> </div>
<div class="_content clips _gap" v-if="clips && clips.length > 0">
<div class="title">{{ $ts.clip }}</div>
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user">
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
</div>
</MkA>
</div>
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
</div>
<div class="_gap" v-if="showPrev"> <div class="main _gap">
<XNotes class="_content" :pagination="prev" :no-gap="true"/> <MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="fas fa-chevron-up"></i></MkButton>
<div class="note _gap">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_isolated"/>
<XNoteDetailed v-model:note="note" :key="note.id" class="_isolated note"/>
</div>
<div class="_content clips _gap" v-if="clips && clips.length > 0">
<div class="title">{{ $ts.clip }}</div>
<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _gap">
<b>{{ item.name }}</b>
<div v-if="item.description" class="description">{{ item.description }}</div>
<div class="user">
<MkAvatar :user="item.user" class="avatar" :show-indicator="true"/> <MkUserName :user="item.user" :nowrap="false"/>
</div>
</MkA>
</div>
<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="fas fa-chevron-down"></i></MkButton>
</div>
<div class="_gap" v-if="showPrev">
<XNotes class="_content" :pagination="prev" :no-gap="true"/>
</div>
</div> </div>
</div> <MkError v-else-if="error" @retry="fetch()"/>
<MkError v-else-if="error" @retry="fetch()"/> <MkLoading v-else/>
<MkLoading v-else/> </transition>
</transition> </div>
</div> </div>
</template> </template>
@ -63,12 +65,14 @@ export default defineComponent({
return { return {
[symbols.PAGE_INFO]: computed(() => this.note ? { [symbols.PAGE_INFO]: computed(() => this.note ? {
title: this.$ts.note, title: this.$ts.note,
subtitle: new Date(this.note.createdAt).toLocaleString(),
avatar: this.note.user, avatar: this.note.user,
path: `/notes/${this.note.id}`, path: `/notes/${this.note.id}`,
share: { share: {
title: this.$t('noteOf', { user: this.note.user.name }), title: this.$t('noteOf', { user: this.note.user.name }),
text: this.note.text, text: this.note.text,
}, },
bg: 'var(--bg)',
} : null), } : null),
note: null, note: null,
clips: null, clips: null,
@ -149,52 +153,54 @@ export default defineComponent({
.fcuexfpr { .fcuexfpr {
background: var(--bg); background: var(--bg);
> .note { > ._root {
> .main { > .note {
> .load { > .main {
min-width: 0; > .load {
margin: 0 auto; min-width: 0;
border-radius: 999px; margin: 0 auto;
border-radius: 999px;
&.next { &.next {
margin-bottom: var(--margin); margin-bottom: var(--margin);
}
&.prev {
margin-top: var(--margin);
}
}
> .note {
> .note {
border-radius: var(--radius);
background: var(--panel);
}
}
> .clips {
> .title {
font-weight: bold;
padding: 12px;
}
> .item {
display: block;
padding: 16px;
> .description {
padding: 8px 0;
} }
> .user { &.prev {
$height: 32px; margin-top: var(--margin);
padding-top: 16px; }
border-top: solid 0.5px var(--divider); }
line-height: $height;
> .avatar { > .note {
width: $height; > .note {
height: $height; border-radius: var(--radius);
background: var(--panel);
}
}
> .clips {
> .title {
font-weight: bold;
padding: 12px;
}
> .item {
display: block;
padding: 16px;
> .description {
padding: 8px 0;
}
> .user {
$height: 32px;
padding-top: 16px;
border-top: solid 0.5px var(--divider);
line-height: $height;
> .avatar {
width: $height;
height: $height;
}
} }
} }
} }

View file

@ -21,6 +21,7 @@ export default defineComponent({
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: {
title: this.$ts.notifications, title: this.$ts.notifications,
icon: 'fas fa-bell', icon: 'fas fa-bell',
bg: 'var(--bg)',
actions: [{ actions: [{
text: this.$ts.markAllAsRead, text: this.$ts.markAllAsRead,
icon: 'fas fa-check', icon: 'fas fa-check',

View file

@ -86,7 +86,8 @@ export default defineComponent({
setup(props, context) { setup(props, context) {
const indexInfo = { const indexInfo = {
title: i18n.locale.settings, title: i18n.locale.settings,
icon: 'fas fa-cog' icon: 'fas fa-cog',
bg: 'var(--bg)',
}; };
const INFO = ref(indexInfo); const INFO = ref(indexInfo);
const page = ref(props.initialPage); const page = ref(props.initialPage);

View file

@ -1,25 +1,10 @@
<template> <template>
<div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }"> <div class="cmuxhskf" v-hotkey.global="keymap" v-size="{ min: [800] }">
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/> <XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/> <XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" 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>
<div class="new" v-if="queue > 0"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div> <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" <XTimeline ref="tl" class="tl"
:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src" :key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src"
:src="src" :src="src"
@ -63,12 +48,37 @@ export default defineComponent({
queue: 0, queue: 0,
[symbols.PAGE_INFO]: computed(() => ({ [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.timeline, 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', 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: [{ actions: [{
icon: 'fas fa-calendar-alt', icon: 'fas fa-calendar-alt',
text: this.$ts.jumpToSpecifiedDate, text: this.$ts.jumpToSpecifiedDate,
handler: this.timetravel 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> <style lang="scss" scoped>
.cmuxhskf { .cmuxhskf {
padding: var(--margin);
> .new { > .new {
position: sticky; position: sticky;
top: calc(var(--stickyTop, 0px) + 16px); top: calc(var(--stickyTop, 0px) + 16px);
@ -227,79 +239,15 @@ export default defineComponent({
} }
} }
> .tabs { > .tl {
display: flex; background: var(--bg);
box-sizing: border-box; border-radius: var(--radius);
padding: 0 8px; overflow: clip;
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);
}
}
} }
&.min-width_800px { &.min-width_800px {
> .tl { max-width: 800px;
background: var(--bg); margin: 0 auto;
padding: 32px 0;
> .tl {
max-width: 800px;
margin: 0 auto;
}
}
} }
} }
</style> </style>

View file

@ -60,23 +60,9 @@
<XPhotos :user="user" :key="user.id" class="_gap"/> <XPhotos :user="user" :key="user.id" class="_gap"/>
</div> </div>
<div class="main"> <div class="main">
<div class="nav _gap"> <div class="actions">
<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link"> <button @click="menu" class="menu _button"><i class="fas fa-ellipsis-h"></i></button>
<i class="fas fa-comment-alt icon"></i> <MkFollowButton v-if="!$i || $i.id != user.id" :user="user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
<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> </div>
<template v-if="page === 'index'"> <template v-if="page === 'index'">
<div v-if="user.pinnedNotes.length > 0" class="_gap"> <div v-if="user.pinnedNotes.length > 0" class="_gap">
@ -178,25 +164,6 @@
</div> </div>
<div class="contents"> <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'"> <template v-if="page === 'index'">
<div> <div>
<div v-if="user.pinnedNotes.length > 0" class="_gap"> <div v-if="user.pinnedNotes.length > 0" class="_gap">
@ -283,6 +250,27 @@ export default defineComponent({
share: { share: {
title: this.user.name, 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), } : null),
user: null, user: null,
error: null, error: null,
@ -314,7 +302,7 @@ export default defineComponent({
mounted() { mounted() {
window.requestAnimationFrame(this.parallaxLoop); window.requestAnimationFrame(this.parallaxLoop);
this.narrow = this.$el.clientWidth < 1000; this.narrow = true//this.$el.clientWidth < 1000;
}, },
beforeUnmount() { beforeUnmount() {
@ -772,37 +760,6 @@ export default defineComponent({
} }
> .contents { > .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 { > .content {
margin-bottom: var(--margin); margin-bottom: var(--margin);
} }

View file

@ -245,7 +245,6 @@ hr {
._panel { ._panel {
background: var(--panel); background: var(--panel);
border-radius: var(--radius); border-radius: var(--radius);
border: var(--panelBorder);
overflow: clip; overflow: clip;
} }

View file

@ -36,7 +36,7 @@
navFg: '@fg', navFg: '@fg',
navHoverFg: ':lighten<17<@fg', navHoverFg: ':lighten<17<@fg',
navActive: '@accent', navActive: '@accent',
navIndicator: '@accent', navIndicator: '@indicator',
link: '#44a4c1', link: '#44a4c1',
hashtag: '#ff9156', hashtag: '#ff9156',
mention: '@accent', mention: '@accent',

View file

@ -36,7 +36,7 @@
navFg: '@fg', navFg: '@fg',
navHoverFg: ':darken<17<@fg', navHoverFg: ':darken<17<@fg',
navActive: '@accent', navActive: '@accent',
navIndicator: '@accent', navIndicator: '@indicator',
link: '#44a4c1', link: '#44a4c1',
hashtag: '#ff9156', hashtag: '#ff9156',
mention: '@accent', mention: '@accent',

View file

@ -1,25 +1,35 @@
<template> <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> <transition :name="$store.state.animation ? 'header' : ''" mode="out-in" appear>
<div class="buttons left" v-if="backButton"> <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> <button class="_button button back" @click.stop="$emit('back')" @touchstart="preventDrag" v-tooltip="$ts.goBack"><i class="fas fa-chevron-left"></i></button>
</div> </div>
</transition> </transition>
<template v-if="info"> <template v-if="info">
<div class="titleContainer"> <div class="titleContainer" @click="showTabsPopup">
<i v-if="info.icon" class="icon" :class="info.icon"></i> <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"/> <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/>
<div class="title"> <div class="title">
<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" 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 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 }} {{ info.subtitle }}
</div> </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> </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"> <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> <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> </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> <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, required: false,
default: false, default: false,
}, },
center: { titleOnly: {
type: Boolean, type: Boolean,
required: false, required: false,
default: true, default: false,
}, },
}, },
data() { data() {
return { return {
showActions: false, narrow: false,
height: 0, height: 0,
key: 0, key: 0,
}; };
}, },
computed: { computed: {
hasTabs(): boolean {
return this.info.tabs && this.info.tabs.length > 0;
},
shouldShowMenu() { 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.menu != null) return true;
if (this.info.share != null) return true; if (this.info.share != null) return true;
if (this.menu != null) return true; if (this.menu != null) return true;
@ -85,10 +99,10 @@ export default defineComponent({
mounted() { mounted() {
this.height = this.$el.parentElement.offsetHeight + 'px'; 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) => { new ResizeObserver((entries, observer) => {
this.height = this.$el.parentElement.offsetHeight + 'px'; 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); }).observe(this.$el);
}, },
@ -102,7 +116,7 @@ export default defineComponent({
showMenu(ev) { showMenu(ev) {
let menu = this.info.menu ? this.info.menu() : []; 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 => ({ menu = [...this.info.actions.map(x => ({
text: x.text, text: x.text,
icon: x.icon, icon: x.icon,
@ -124,6 +138,18 @@ export default defineComponent({
popupMenu(menu, ev.currentTarget || ev.target); 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) { preventDrag(ev) {
ev.stopPropagation(); ev.stopPropagation();
} }
@ -135,7 +161,7 @@ export default defineComponent({
.fdidabkb { .fdidabkb {
display: flex; display: flex;
&.center { &.slim {
text-align: center; text-align: center;
> .titleContainer { > .titleContainer {
@ -190,6 +216,7 @@ export default defineComponent({
overflow: auto; overflow: auto;
white-space: nowrap; white-space: nowrap;
text-align: left; text-align: left;
font-weight: bold;
> .avatar { > .avatar {
$size: 32px; $size: 32px;
@ -219,6 +246,54 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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;
} }
} }
} }

View file

@ -12,7 +12,7 @@
</div> </div>
</template> </template>
<main class="main" @contextmenu.stop="onContextmenu"> <main class="main" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
<header class="header" @click="onHeaderClick"> <header class="header" @click="onHeaderClick">
<XHeader :info="pageInfo" :back-button="true" @back="back()"/> <XHeader :info="pageInfo" :back-button="true" @back="back()"/>
</header> </header>
@ -145,6 +145,15 @@ export default defineComponent({
} }
}, '*'); }, '*');
}, { passive: true }); }, { 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 });
} }
}, },

View file

@ -2,8 +2,8 @@
<div class="mk-app" :class="{ wallpaper }"> <div class="mk-app" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/> <XSidebar ref="nav" class="sidebar"/>
<div class="contents" ref="contents" @contextmenu.stop="onContextmenu"> <div class="contents" ref="contents" @contextmenu.stop="onContextmenu" :style="{ background: pageInfo?.bg }">
<header class="header" ref="header" @click="onHeaderClick"> <header class="header" ref="header" @click="onHeaderClick" :style="{ background: pageInfo?.bg }">
<XHeader :info="pageInfo" :back-button="true" @back="back()"/> <XHeader :info="pageInfo" :back-button="true" @back="back()"/>
</header> </header>
<main ref="main"> <main ref="main">
@ -258,7 +258,6 @@ export default defineComponent({
} }
> .sidebar { > .sidebar {
border-right: solid 0.5px var(--divider);
} }
> .contents { > .contents {
@ -313,7 +312,8 @@ export default defineComponent({
> .widgets { > .widgets {
padding: 0 var(--margin); 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) { @media (max-width: $widgets-hide-threshold) {
display: none; display: none;