enhance(frontend): ウェルカムタイムラインのデザインを調整 (#14156)
* enhance(frontend): 非ログイン時のハイライトTLのデザイン調整 * Update Changelog * fix cw handling * ホバーしてたらスクロールを止めるように * fix * lint
This commit is contained in:
parent
f8ac3fe343
commit
6b876da44a
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
||||||
|
- Enhance: 非ログイン時のハイライトTLのデザインを改善
|
||||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||||
|
|
109
packages/frontend/src/pages/welcome.timeline.note.vue
Normal file
109
packages/frontend/src/pages/welcome.timeline.note.vue
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div :key="note.id" :class="$style.note">
|
||||||
|
<div class="_panel _gaps_s" :class="$style.content">
|
||||||
|
<div v-if="note.cw != null" :class="$style.richcontent">
|
||||||
|
<div><Mfm :text="note.cw" :author="note.user"/></div>
|
||||||
|
<MkCwButton v-model="showContent" :text="note.text" :renote="note.renote" :files="note.files" :poll="note.poll" style="margin: 4px 0;"/>
|
||||||
|
<div v-if="showContent">
|
||||||
|
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
|
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
|
||||||
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else ref="noteTextEl" :class="[$style.text, { [$style.collapsed]: shouldCollapse }]">
|
||||||
|
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
|
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
|
||||||
|
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
||||||
|
</div>
|
||||||
|
<div v-if="note.files && note.files.length > 0" :class="$style.richcontent">
|
||||||
|
<MkMediaList :mediaList="note.files.slice(0, 4)"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="note.poll">
|
||||||
|
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="note.reactionCount > 0" :class="$style.reactions">
|
||||||
|
<MkReactionsViewer :note="note" :maxNumber="16"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, shallowRef, onUpdated, onMounted } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
|
||||||
|
import MkMediaList from '@/components/MkMediaList.vue';
|
||||||
|
import MkPoll from '@/components/MkPoll.vue';
|
||||||
|
import MkCwButton from '@/components/MkCwButton.vue';
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
note: Misskey.entities.Note;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const noteTextEl = shallowRef<HTMLDivElement>();
|
||||||
|
const shouldCollapse = ref(false);
|
||||||
|
const showContent = ref(false);
|
||||||
|
|
||||||
|
function calcCollapse() {
|
||||||
|
if (noteTextEl.value) {
|
||||||
|
const height = noteTextEl.value.scrollHeight;
|
||||||
|
if (height > 200) {
|
||||||
|
shouldCollapse.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
calcCollapse();
|
||||||
|
});
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
calcCollapse();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.note {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text {
|
||||||
|
position: relative;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.collapsed::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 64px;
|
||||||
|
background: linear-gradient(0deg, var(--panel), var(--X15));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 16px;
|
||||||
|
margin: 0 0 0 auto;
|
||||||
|
max-width: max-content;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 8px -16px -8px;
|
||||||
|
padding: 8px 16px 0;
|
||||||
|
width: calc(100% + 32px);
|
||||||
|
border-top: 1px solid var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.richcontent {
|
||||||
|
min-width: 250px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -4,24 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="$style.root">
|
<div :class="$style.root" class="_gaps">
|
||||||
<div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]">
|
<div
|
||||||
<div v-for="note in notes" :key="note.id" :class="$style.note">
|
ref="notesMainContainerEl"
|
||||||
<div class="_panel" :class="$style.content">
|
class="_gaps"
|
||||||
<div>
|
:class="[$style.scrollBoxMain, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]"
|
||||||
<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
@animationend="changeScrollState"
|
||||||
<Mfm v-if="note.text" :text="note.text" :author="note.user"/>
|
>
|
||||||
<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
|
<XNote v-for="note in notes" :key="`${note.id}_1`" :class="$style.note" :note="note"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="note.files.length > 0" :class="$style.richcontent">
|
<div v-if="isScrolling" class="_gaps" :class="[$style.scrollBoxSub, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]">
|
||||||
<MkMediaList :mediaList="note.files"/>
|
<XNote v-for="note in notes" :key="`${note.id}_2`" :class="$style.note" :note="note"/>
|
||||||
</div>
|
|
||||||
<div v-if="note.poll">
|
|
||||||
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<MkReactionsViewer ref="reactionsViewer" :note="note"/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -29,43 +22,54 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { onUpdated, ref, shallowRef } from 'vue';
|
import { onUpdated, ref, shallowRef } from 'vue';
|
||||||
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
|
import XNote from '@/pages/welcome.timeline.note.vue';
|
||||||
import MkMediaList from '@/components/MkMediaList.vue';
|
|
||||||
import MkPoll from '@/components/MkPoll.vue';
|
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import { getScrollContainer } from '@/scripts/scroll.js';
|
import { getScrollContainer } from '@/scripts/scroll.js';
|
||||||
|
|
||||||
const notes = ref<Misskey.entities.Note[]>([]);
|
const notes = ref<Misskey.entities.Note[]>([]);
|
||||||
const isScrolling = ref(false);
|
const isScrolling = ref(false);
|
||||||
const scrollEl = shallowRef<HTMLElement>();
|
const scrollState = ref<null | 'intro' | 'loop'>(null);
|
||||||
|
const notesMainContainerEl = shallowRef<HTMLElement>();
|
||||||
|
|
||||||
misskeyApiGet('notes/featured').then(_notes => {
|
misskeyApiGet('notes/featured').then(_notes => {
|
||||||
notes.value = _notes;
|
notes.value = _notes;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function changeScrollState() {
|
||||||
|
if (scrollState.value !== 'loop') {
|
||||||
|
scrollState.value = 'loop';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onUpdated(() => {
|
onUpdated(() => {
|
||||||
if (!scrollEl.value) return;
|
if (!notesMainContainerEl.value) return;
|
||||||
const container = getScrollContainer(scrollEl.value);
|
const container = getScrollContainer(notesMainContainerEl.value);
|
||||||
const containerHeight = container ? container.clientHeight : window.innerHeight;
|
const containerHeight = container ? container.clientHeight : window.innerHeight;
|
||||||
if (scrollEl.value.offsetHeight > containerHeight) {
|
if (notesMainContainerEl.value.offsetHeight > containerHeight) {
|
||||||
|
if (scrollState.value === null) {
|
||||||
|
scrollState.value = 'intro';
|
||||||
|
}
|
||||||
isScrolling.value = true;
|
isScrolling.value = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
@keyframes scroll {
|
@keyframes scrollIntro {
|
||||||
0% {
|
0% {
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, 0, 0);
|
||||||
}
|
}
|
||||||
5% {
|
100% {
|
||||||
transform: translate3d(0, 0, 0);
|
transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0);
|
||||||
}
|
}
|
||||||
75% {
|
}
|
||||||
transform: translate3d(0, calc(-100% + 90vh), 0);
|
|
||||||
|
@keyframes scrollConstant {
|
||||||
|
0% {
|
||||||
|
transform: translate3d(0, -128px, 0);
|
||||||
}
|
}
|
||||||
90% {
|
100% {
|
||||||
transform: translate3d(0, calc(-100% + 90vh), 0);
|
transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,24 +77,26 @@ onUpdated(() => {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scrollbox {
|
.scrollBoxMain {
|
||||||
&.scroll {
|
&.scrollIntro {
|
||||||
animation: scroll 45s linear infinite;
|
animation: scrollIntro 30s linear forwards;
|
||||||
|
}
|
||||||
|
&.scrollLoop {
|
||||||
|
animation: scrollConstant 30s linear infinite;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.note {
|
.scrollBoxSub {
|
||||||
margin: 16px 0 16px auto;
|
&.scrollIntro {
|
||||||
|
animation: scrollIntro 30s linear forwards;
|
||||||
|
}
|
||||||
|
&.scrollLoop {
|
||||||
|
animation: scrollConstant 30s linear infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.root:has(.note:hover) .scrollBoxMain,
|
||||||
padding: 16px;
|
.root:has(.note:hover) .scrollBoxSub {
|
||||||
margin: 0 0 0 auto;
|
animation-play-state: paused;
|
||||||
max-width: max-content;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.richcontent {
|
|
||||||
min-width: 250px;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue