Merge branch 'develop' into feat-1714

This commit is contained in:
かっこかり
2024-07-12 16:38:38 +09:00
committed by GitHub
69 changed files with 1096 additions and 461 deletions

View File

@@ -234,6 +234,7 @@ onMounted(async () => {
background-color: var(--accentedBg);
color: var(--accent);
text-decoration: none;
outline: none;
}
&.danger {

View File

@@ -8,12 +8,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><MkPageHeader/></template>
<MkSpacer :contentMax="800">
<div class="_gaps">
<div class="_panel">
<div class="_panel" :class="$style.link">
<MkA to="/bubble-game">
<img src="/client-assets/drop-and-fusion/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
</MkA>
</div>
<div class="_panel">
<div class="_panel" :class="$style.link">
<MkA to="/reversi">
<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
</MkA>
@@ -32,3 +32,10 @@ definePageMetadata(() => ({
icon: 'ti ti-device-gamepad',
}));
</script>
<style module>
.link:focus-within {
outline: 2px solid var(--focus);
outline-offset: -2px;
}
</style>

View File

@@ -286,6 +286,7 @@ definePageMetadata(() => ({
background-color: var(--accentedBg);
color: var(--accent);
text-decoration: none;
outline: none;
}
}

View File

@@ -113,8 +113,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'sound_note',
'sound_noteMy',
'sound_notification',
'sound_antenna',
'sound_channel',
];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
'lightTheme',

View File

@@ -342,6 +342,7 @@ definePageMetadata(() => ({
&:hover, &:focus {
opacity: .7;
}
&:active {
cursor: pointer;
}

View File

@@ -54,8 +54,6 @@ const sounds = ref<Record<OperationType, Ref<SoundStore>>>({
note: defaultStore.reactiveState.sound_note,
noteMy: defaultStore.reactiveState.sound_noteMy,
notification: defaultStore.reactiveState.sound_notification,
antenna: defaultStore.reactiveState.sound_antenna,
channel: defaultStore.reactiveState.sound_channel,
reaction: defaultStore.reactiveState.sound_reaction,
});

View File

@@ -213,12 +213,18 @@ definePageMetadata(() => ({
}
}
.dn:focus-visible ~ .toggle {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.toggle {
cursor: pointer;
display: inline-block;
position: relative;
width: 90px;
height: 50px;
margin: 4px; // focus用のアウトライン
background-color: #83D8FF;
border-radius: 90px - 6;
transition: background-color 200ms cubic-bezier(0.445, 0.05, 0.55, 0.95) !important;

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

View File

@@ -4,24 +4,17 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]">
<div v-for="note in notes" :key="note.id" :class="$style.note">
<div class="_panel" :class="$style.content">
<div>
<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.length > 0" :class="$style.richcontent">
<MkMediaList :mediaList="note.files"/>
</div>
<div v-if="note.poll">
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
</div>
</div>
<MkReactionsViewer ref="reactionsViewer" :note="note"/>
</div>
<div :class="$style.root" class="_gaps">
<div
ref="notesMainContainerEl"
class="_gaps"
:class="[$style.scrollBoxMain, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]"
@animationend="changeScrollState"
>
<XNote v-for="note in notes" :key="`${note.id}_1`" :class="$style.note" :note="note"/>
</div>
<div v-if="isScrolling" class="_gaps" :class="[$style.scrollBoxSub, { [$style.scrollIntro]: (scrollState === 'intro'), [$style.scrollLoop]: (scrollState === 'loop') }]">
<XNote v-for="note in notes" :key="`${note.id}_2`" :class="$style.note" :note="note"/>
</div>
</div>
</template>
@@ -29,43 +22,54 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { onUpdated, ref, shallowRef } from 'vue';
import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
import MkMediaList from '@/components/MkMediaList.vue';
import MkPoll from '@/components/MkPoll.vue';
import XNote from '@/pages/welcome.timeline.note.vue';
import { misskeyApiGet } from '@/scripts/misskey-api.js';
import { getScrollContainer } from '@/scripts/scroll.js';
const notes = ref<Misskey.entities.Note[]>([]);
const isScrolling = ref(false);
const scrollEl = shallowRef<HTMLElement>();
const scrollState = ref<null | 'intro' | 'loop'>(null);
const notesMainContainerEl = shallowRef<HTMLElement>();
misskeyApiGet('notes/featured').then(_notes => {
notes.value = _notes;
});
function changeScrollState() {
if (scrollState.value !== 'loop') {
scrollState.value = 'loop';
}
}
onUpdated(() => {
if (!scrollEl.value) return;
const container = getScrollContainer(scrollEl.value);
if (!notesMainContainerEl.value) return;
const container = getScrollContainer(notesMainContainerEl.value);
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;
}
});
</script>
<style lang="scss" module>
@keyframes scroll {
@keyframes scrollIntro {
0% {
transform: translate3d(0, 0, 0);
}
5% {
transform: translate3d(0, 0, 0);
100% {
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% {
transform: translate3d(0, calc(-100% + 90vh), 0);
100% {
transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0);
}
}
@@ -73,24 +77,26 @@ onUpdated(() => {
text-align: right;
}
.scrollbox {
&.scroll {
animation: scroll 45s linear infinite;
.scrollBoxMain {
&.scrollIntro {
animation: scrollIntro 30s linear forwards;
}
&.scrollLoop {
animation: scrollConstant 30s linear infinite;
}
}
.note {
margin: 16px 0 16px auto;
.scrollBoxSub {
&.scrollIntro {
animation: scrollIntro 30s linear forwards;
}
&.scrollLoop {
animation: scrollConstant 30s linear infinite;
}
}
.content {
padding: 16px;
margin: 0 0 0 auto;
max-width: max-content;
border-radius: 16px;
}
.richcontent {
min-width: 250px;
.root:has(.note:hover) .scrollBoxMain,
.root:has(.note:hover) .scrollBoxSub {
animation-play-state: paused;
}
</style>