summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-09-17 22:39:15 +0900
committerGitHub <noreply@github.com>2021-09-17 22:39:15 +0900
commit361069314ffaa61a81b2189c2eec000a3d1d9c35 (patch)
tree195271c503ef67b245503ca6fe945bd97a8ea83b
parentpopupで設定ページを表示すると、アカウントの削除ページ... (diff)
downloadsharkey-361069314ffaa61a81b2189c2eec000a3d1d9c35.tar.gz
sharkey-361069314ffaa61a81b2189c2eec000a3d1d9c35.tar.bz2
sharkey-361069314ffaa61a81b2189c2eec000a3d1d9c35.zip
Refine UI (#7806)
* wip * wip * wip * wip * wip * wip * wip * wip * Update default.vue * wip
-rw-r--r--src/client/components/global/avatar.vue26
-rw-r--r--src/client/components/ui/folder.vue3
-rw-r--r--src/client/components/ui/input.vue2
-rw-r--r--src/client/components/ui/textarea.vue2
-rw-r--r--src/client/init.ts4
-rw-r--r--src/client/pages/emojis.category.vue134
-rw-r--r--src/client/pages/emojis.emoji.vue92
-rw-r--r--src/client/pages/emojis.vue139
-rw-r--r--src/client/pages/favorites.vue3
-rw-r--r--src/client/pages/note.vue134
-rw-r--r--src/client/pages/notifications.vue1
-rw-r--r--src/client/pages/settings/index.vue3
-rw-r--r--src/client/pages/timeline.vue128
-rw-r--r--src/client/pages/user/index.vue93
-rw-r--r--src/client/style.scss1
-rw-r--r--src/client/themes/_dark.json52
-rw-r--r--src/client/themes/_light.json52
-rw-r--r--src/client/ui/_common_/header.vue99
-rw-r--r--src/client/ui/default.vue11
-rw-r--r--src/client/ui/universal.vue8
20 files changed, 509 insertions, 378 deletions
diff --git a/src/client/components/global/avatar.vue b/src/client/components/global/avatar.vue
index eea970ec9a..395ed5d8ce 100644
--- a/src/client/components/global/avatar.vue
+++ b/src/client/components/global/avatar.vue
@@ -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>
diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue
index 1f3593a74a..eecf1d8be1 100644
--- a/src/client/components/ui/folder.vue
+++ b/src/client/components/ui/folder.vue
@@ -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));
diff --git a/src/client/components/ui/input.vue b/src/client/components/ui/input.vue
index 05ce5d3e15..a916a0b035 100644
--- a/src/client/components/ui/input.vue
+++ b/src/client/components/ui/input.vue
@@ -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;
diff --git a/src/client/components/ui/textarea.vue b/src/client/components/ui/textarea.vue
index 53a141f011..08ac3182a9 100644
--- a/src/client/components/ui/textarea.vue
+++ b/src/client/components/ui/textarea.vue
@@ -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;
diff --git a/src/client/init.ts b/src/client/init.ts
index 4d2170e03f..aa9cd817c4 100644
--- a/src/client/init.ts
+++ b/src/client/init.ts
@@ -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;
diff --git a/src/client/pages/emojis.category.vue b/src/client/pages/emojis.category.vue
new file mode 100644
index 0000000000..0c24b06d16
--- /dev/null
+++ b/src/client/pages/emojis.category.vue
@@ -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>
diff --git a/src/client/pages/emojis.emoji.vue b/src/client/pages/emojis.emoji.vue
new file mode 100644
index 0000000000..3c9bb4debe
--- /dev/null
+++ b/src/client/pages/emojis.emoji.vue
@@ -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>
diff --git a/src/client/pages/emojis.vue b/src/client/pages/emojis.vue
index 391aff8297..c1f87047d3 100644
--- a/src/client/pages/emojis.vue
+++ b/src/client/pages/emojis.vue
@@ -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,
- }
- },
-
- 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));
+ icon: 'fas fa-laugh',
+ bg: 'var(--bg)',
+ })),
+ tab: 'category',
}
},
-
- 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>
diff --git a/src/client/pages/favorites.vue b/src/client/pages/favorites.vue
index a2d61b98d9..f13723c2d1 100644
--- a/src/client/pages/favorites.vue
+++ b/src/client/pages/favorites.vue
@@ -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',
diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue
index 7725ca14b4..fe85d7364e 100644
--- a/src/client/pages/note.vue
+++ b/src/client/pages/note.vue
@@ -1,37 +1,39 @@
<template>
-<div class="fcuexfpr _root">
- <transition name="fade" mode="out-in">
- <div v-if="note" class="note">
- <div class="_gap" v-if="showNext">
- <XNotes class="_content" :pagination="next" :no-gap="true"/>
- </div>
-
- <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 class="fcuexfpr">
+ <div class="_root">
+ <transition name="fade" mode="out-in">
+ <div v-if="note" class="note">
+ <div class="_gap" v-if="showNext">
+ <XNotes class="_content" :pagination="next" :no-gap="true"/>
</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 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 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>
- <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 class="_gap" v-if="showPrev">
+ <XNotes class="_content" :pagination="prev" :no-gap="true"/>
+ </div>
</div>
- </div>
- <MkError v-else-if="error" @retry="fetch()"/>
- <MkLoading v-else/>
- </transition>
+ <MkError v-else-if="error" @retry="fetch()"/>
+ <MkLoading v-else/>
+ </transition>
+ </div>
</div>
</template>
@@ -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,52 +153,54 @@ export default defineComponent({
.fcuexfpr {
background: var(--bg);
- > .note {
- > .main {
- > .load {
- min-width: 0;
- margin: 0 auto;
- border-radius: 999px;
+ > ._root {
+ > .note {
+ > .main {
+ > .load {
+ min-width: 0;
+ margin: 0 auto;
+ border-radius: 999px;
- &.next {
- margin-bottom: var(--margin);
- }
+ &.next {
+ margin-bottom: var(--margin);
+ }
- &.prev {
- margin-top: var(--margin);
+ &.prev {
+ margin-top: var(--margin);
+ }
}
- }
- > .note {
> .note {
- border-radius: var(--radius);
- background: var(--panel);
+ > .note {
+ border-radius: var(--radius);
+ background: var(--panel);
+ }
}
- }
- > .clips {
- > .title {
- font-weight: bold;
- padding: 12px;
- }
+ > .clips {
+ > .title {
+ font-weight: bold;
+ padding: 12px;
+ }
- > .item {
- display: block;
- padding: 16px;
+ > .item {
+ display: block;
+ padding: 16px;
- > .description {
- padding: 8px 0;
- }
+ > .description {
+ padding: 8px 0;
+ }
- > .user {
- $height: 32px;
- padding-top: 16px;
- border-top: solid 0.5px var(--divider);
- line-height: $height;
+ > .user {
+ $height: 32px;
+ padding-top: 16px;
+ border-top: solid 0.5px var(--divider);
+ line-height: $height;
- > .avatar {
- width: $height;
- height: $height;
+ > .avatar {
+ width: $height;
+ height: $height;
+ }
}
}
}
diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue
index 633718a90b..06f8ad3cba 100644
--- a/src/client/pages/notifications.vue
+++ b/src/client/pages/notifications.vue
@@ -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',
diff --git a/src/client/pages/settings/index.vue b/src/client/pages/settings/index.vue
index e7e2506020..3fb5f5f1e6 100644
--- a/src/client/pages/settings/index.vue
+++ b/src/client/pages/settings/index.vue
@@ -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);
diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue
index f54549b982..125191223c 100644
--- a/src/client/pages/timeline.vue
+++ b/src/client/pages/timeline.vue
@@ -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;
- }
- }
+ max-width: 800px;
+ margin: 0 auto;
}
}
</style>
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
index 4145c86d56..86dc7361b5 100644
--- a/src/client/pages/user/index.vue
+++ b/src/client/pages/user/index.vue
@@ -60,23 +60,9 @@
<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 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>
<template v-if="page === 'index'">
<div v-if="user.pinnedNotes.length > 0" class="_gap">
@@ -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);
}
diff --git a/src/client/style.scss b/src/client/style.scss
index 6ab5e796bd..0318013f60 100644
--- a/src/client/style.scss
+++ b/src/client/style.scss
@@ -245,7 +245,6 @@ hr {
._panel {
background: var(--panel);
border-radius: var(--radius);
- border: var(--panelBorder);
overflow: clip;
}
diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5
index ca9994d5e9..b4553ee812 100644
--- a/src/client/themes/_dark.json5
+++ b/src/client/themes/_dark.json5
@@ -36,7 +36,7 @@
navFg: '@fg',
navHoverFg: ':lighten<17<@fg',
navActive: '@accent',
- navIndicator: '@accent',
+ navIndicator: '@indicator',
link: '#44a4c1',
hashtag: '#ff9156',
mention: '@accent',
diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5
index 973a6251f0..104f5a85af 100644
--- a/src/client/themes/_light.json5
+++ b/src/client/themes/_light.json5
@@ -36,7 +36,7 @@
navFg: '@fg',
navHoverFg: ':darken<17<@fg',
navActive: '@accent',
- navIndicator: '@accent',
+ navIndicator: '@indicator',
link: '#44a4c1',
hashtag: '#ff9156',
mention: '@accent',
diff --git a/src/client/ui/_common_/header.vue b/src/client/ui/_common_/header.vue
index 115f70a540..eb8a1b9c0a 100644
--- a/src/client/ui/_common_/header.vue
+++ b/src/client/ui/_common_/header.vue
@@ -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;
}
}
}
diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue
index eef693faef..a5ec243e9e 100644
--- a/src/client/ui/default.vue
+++ b/src/client/ui/default.vue
@@ -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 });
}
},
diff --git a/src/client/ui/universal.vue b/src/client/ui/universal.vue
index d6cace0f41..cc754cba70 100644
--- a/src/client/ui/universal.vue
+++ b/src/client/ui/universal.vue
@@ -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;