Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enhance(frontend): ユーザーページに「ファイル」タブを新設 #15130

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8458e23
投稿したファイルの一覧をプロフィールページ内のタブで見れるようにしてみた (Otaku-Social#14)
tmorio Nov 20, 2023
3b6a218
ギャラリー(ノート)の取得方法を変更、ページネーションに対応
tmorio Dec 23, 2023
def3130
ギャラリー(ノート)が動作しない問題を修正
tmorio Jan 3, 2024
af45c2b
ギャラリー(ノート)の名称変更
tmorio Jul 25, 2024
bd6ef35
styles
kakkokari-gtyih Dec 14, 2024
ca38bce
GalleryFromPosts -> Files
kakkokari-gtyih Dec 14, 2024
68d4a25
fix
kakkokari-gtyih Dec 14, 2024
abde486
enhance: 既存のファイルコンテナの「もっと見る」をクリックしたらファイルタブに飛べるように
kakkokari-gtyih Dec 14, 2024
5dba738
Update Changelog
kakkokari-gtyih Dec 14, 2024
c580f5e
共通化
kakkokari-gtyih Dec 14, 2024
562d3c2
spdx
kakkokari-gtyih Dec 14, 2024
abd08e7
その他のメディアがちゃんとプレビューされるように
kakkokari-gtyih Dec 14, 2024
8204d68
fix(frontend): リストがセンシティブ設定を考慮するように
kakkokari-gtyih Dec 14, 2024
c293cdb
arrayをsetに変更
kakkokari-gtyih Dec 14, 2024
e4ec2cc
remove unused imports
kakkokari-gtyih Dec 14, 2024
d9e6c4c
:art:
kakkokari-gtyih Dec 14, 2024
8a46482
:art:
kakkokari-gtyih Dec 14, 2024
33051f0
画像以外のファイルのプレビューに対応したのでコメントを削除
kakkokari-gtyih Dec 14, 2024
a01d75b
サムネイルをMkDriveFileThumbnailに統一
kakkokari-gtyih Dec 16, 2024
57f2b7a
Merge branch 'develop' into enh-13702
kakkokari-gtyih Dec 16, 2024
0921a67
v-panelに置き換え
kakkokari-gtyih Dec 16, 2024
b197379
Merge branch 'enh-13702' of https://github.com/kakkokari-gtyih/misske…
kakkokari-gtyih Dec 16, 2024
5a854f1
lint
kakkokari-gtyih Dec 16, 2024
e87c18b
Merge branch 'develop' into enh-13702
kakkokari-gtyih Dec 24, 2024
138b421
Merge branch 'develop' into enh-13702
kakkokari-gtyih Jan 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
- Enhance: 照会に失敗した場合、その理由を表示するように
- Enhance: AiScriptのセーブデータを明示的に削除する関数`Mk:remove`を追加
- Enhance: ノートの添付ファイルを一覧で遡れる「ファイル」タブを追加
(Based on https://github.com/Otaku-Social/maniakey/pull/14)
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend/src/components/MkContainer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]">
<slot></slot>
<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
<button v-if="omitted" :class="$style.fade" class="_button" @click="showMore">
<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
</button>
</div>
Expand All @@ -48,6 +48,7 @@ const props = withDefaults(defineProps<{
thin?: boolean;
naked?: boolean;
foldable?: boolean;
onUnfold?: () => boolean; // return false to prevent unfolding
scrollable?: boolean;
expanded?: boolean;
maxHeight?: number | null;
Expand Down Expand Up @@ -101,6 +102,13 @@ const omitObserver = new ResizeObserver((entries, observer) => {
calcOmit();
});

function showMore() {
if (props.onUnfold && !props.onUnfold()) return;

ignoreOmit.value = true;
omitted.value = false;
}

onMounted(() => {
watch(showBody, v => {
if (!rootEl.value) return;
Expand Down
28 changes: 21 additions & 7 deletions packages/frontend/src/components/MkDriveFileThumbnail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,21 @@ SPDX-License-Identifier: AGPL-3.0-only

<template>
<div
ref="thumbnail"
:class="[
$style.root,
{ [$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive },
]"
v-panel
:class="[$style.root, {
[$style.sensitiveHighlight]: highlightWhenSensitive && file.isSensitive,
[$style.large]: large,
}]"
>
<ImgWithBlurhash v-if="isThumbnailAvailable" :hash="file.blurhash" :src="file.thumbnailUrl" :alt="file.name" :title="file.name" :cover="fit !== 'contain'"/>
<ImgWithBlurhash
v-if="isThumbnailAvailable"
:hash="file.blurhash"
:src="file.thumbnailUrl"
:alt="file.name"
:title="file.name"
:cover="fit !== 'contain'"
:forceBlurHash="forceBlurhash"
/>
<i v-else-if="is === 'image'" class="ti ti-photo" :class="$style.icon"></i>
<i v-else-if="is === 'video'" class="ti ti-video" :class="$style.icon"></i>
<i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music" :class="$style.icon"></i>
Expand All @@ -34,6 +42,8 @@ const props = defineProps<{
file: Misskey.entities.DriveFile;
fit: 'cover' | 'contain';
highlightWhenSensitive?: boolean;
forceBlurhash?: boolean;
large?: boolean;
}>();

const is = computed(() => {
Expand All @@ -60,7 +70,7 @@ const is = computed(() => {

const isThumbnailAvailable = computed(() => {
return props.file.thumbnailUrl
? (is.value === 'image' as const || is.value === 'video')
? (is.value === 'image' || is.value === 'video')
: false;
});
</script>
Expand Down Expand Up @@ -101,4 +111,8 @@ const isThumbnailAvailable = computed(() => {
font-size: 32px;
color: #777;
}

.large .icon {
font-size: 40px;
}
</style>
99 changes: 99 additions & 0 deletions packages/frontend/src/components/MkNoteMediaGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<template v-for="file in note.files">
<div
v-if="(defaultStore.state.nsfw === 'force' || file.isSensitive) && defaultStore.state.nsfw !== 'ignore' && !showingFiles.has(file.id)"
:class="[$style.filePreview, { [$style.square]: square }]"
@click="showingFiles.add(file.id)"
>
<MkDriveFileThumbnail
:file="file"
fit="cover"
:highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
:forceBlurhash="true"
:large="true"
:class="$style.file"
/>
<div :class="$style.sensitive">
<div>
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
<div>{{ i18n.ts.clickToShow }}</div>
</div>
</div>
</div>
<MkA v-else :class="[$style.filePreview, { [$style.square]: square }]" :to="notePage(note)">
<MkDriveFileThumbnail
:file="file"
fit="cover"
:highlightWhenSensitive="defaultStore.state.highlightSensitiveMedia"
:large="true"
:class="$style.file"
/>
</MkA>
</template>
</template>

<script lang="ts" setup>
import { ref } from 'vue';
import { notePage } from '@/filters/note.js';
import { i18n } from '@/i18n.js';
import * as Misskey from 'misskey-js';
import { defaultStore } from '@/store.js';

import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';

defineProps<{
note: Misskey.entities.Note;
square?: boolean;
}>();

const showingFiles = ref<Set<string>>(new Set());
</script>

<style lang="scss" module>
.square {
width: 100%;
height: auto;
aspect-ratio: 1;
}

.filePreview {
position: relative;
height: 128px;
border-radius: calc(var(--MI-radius) / 2);
overflow: clip;

&:hover {
text-decoration: none;
}

&.square {
height: 100%;
}
}

.file {
width: 100%;
height: 100%;
border-radius: calc(var(--MI-radius) / 2);
}

.sensitive {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
place-items: center;
font-size: 0.8em;
color: #fff;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(5px);
cursor: pointer;
}
</style>
56 changes: 56 additions & 0 deletions packages/frontend/src/pages/user/files.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkSpacer :contentMax="1100">
<div :class="$style.root">
<MkPagination v-slot="{items}" :pagination="pagination">
<div :class="$style.stream">
<MkNoteMediaGrid v-for="note in items" :note="note" square/>
</div>
</MkPagination>
</div>
</MkSpacer>
</template>

<script lang="ts" setup>
import { computed } from 'vue';
import * as Misskey from 'misskey-js';

import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';
import MkPagination from '@/components/MkPagination.vue';

const props = defineProps<{
user: Misskey.entities.UserDetailed;
}>();

const pagination = {
endpoint: 'users/notes' as const,
limit: 15,
params: computed(() => ({
userId: props.user.id,
withFiles: true,
})),
};
</script>

<style lang="scss" module>
.root {
padding: 8px;
}

.stream {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: var(--MI-marginHalf);
}

@media screen and (min-width: 600px) {
.stream {
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}

}
</style>
8 changes: 6 additions & 2 deletions packages/frontend/src/pages/user/home.vue
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
<template v-if="narrow">
<MkLazy>
<XFiles :key="user.id" :user="user"/>
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
</MkLazy>
<MkLazy>
<XActivity :key="user.id" :user="user"/>
Expand All @@ -150,7 +150,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
<XFiles :key="user.id" :user="user"/>
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
<XActivity :key="user.id" :user="user"/>
</div>
</div>
Expand Down Expand Up @@ -212,6 +212,10 @@ const props = withDefaults(defineProps<{
disableNotes: false,
});

const emit = defineEmits<{
(ev: 'unfoldFiles'): void;
}>();

const router = useRouter();

const user = ref(props.user);
Expand Down
58 changes: 16 additions & 42 deletions packages/frontend/src/pages/user/index.files.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,50 @@ SPDX-License-Identifier: AGPL-3.0-only
-->

<template>
<MkContainer :max-height="300" :foldable="true">
<MkContainer :max-height="300" :foldable="true" :onUnfold="unfoldContainer">
<template #icon><i class="ti ti-photo"></i></template>
<template #header>{{ i18n.ts.files }}</template>
<div :class="$style.root">
<MkLoading v-if="fetching"/>
<div v-if="!fetching && files.length > 0" :class="$style.stream">
<template v-for="file in files" :key="file.note.id + file.file.id">
<div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.img" @click="showingFiles.push(file.file.id)">
<!-- TODO: 画像以外のファイルに対応 -->
<ImgWithBlurhash :class="$style.sensitiveImg" :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name" :forceBlurhash="true"/>
<div :class="$style.sensitive">
<div>
<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
<div>{{ i18n.ts.clickToShow }}</div>
</div>
</div>
</div>
<MkA v-else :class="$style.img" :to="notePage(file.note)">
<!-- TODO: 画像以外のファイルに対応 -->
<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
</MkA>
</template>
<div v-if="!fetching && notes.length > 0" :class="$style.stream">
<MkNoteMediaGrid v-for="note in notes" :note="note"/>
</div>
<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
<p v-if="!fetching && notes.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
</div>
</MkContainer>
</template>

<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import * as Misskey from 'misskey-js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import { notePage } from '@/filters/note.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import MkContainer from '@/components/MkContainer.vue';
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js';
import MkNoteMediaGrid from '@/components/MkNoteMediaGrid.vue';

const props = defineProps<{
user: Misskey.entities.UserDetailed;
}>();

const emit = defineEmits<{
(ev: 'unfold'): void;
}>();

const fetching = ref(true);
const files = ref<{
note: Misskey.entities.Note;
file: Misskey.entities.DriveFile;
}[]>([]);
const showingFiles = ref<string[]>([]);
const notes = ref<Misskey.entities.Note[]>([]);

function thumbnail(image: Misskey.entities.DriveFile): string {
return defaultStore.state.disableShowingAnimatedImages
? getStaticImageUrl(image.url)
: image.thumbnailUrl;
function unfoldContainer(): boolean {
emit('unfold');
return false;
}

onMounted(() => {
misskeyApi('users/notes', {
userId: props.user.id,
withFiles: true,
limit: 15,
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {
files.value.push({
note,
file,
});
}
}
limit: 10,
}).then(_notes => {
notes.value = _notes;
fetching.value = false;
});
});
Expand Down
Loading
Loading