Skip to content

Commit 3b3f683

Browse files
authored
feat(#8149): respect nsfw settings on gallery list (#10481)
* feat(#8149): respect nsfw settings on gallery list * ci(#10336): use pull_request * test(#8149): add interaction tests * test(#10336): use `waitFor` * chore: transition
1 parent 516a791 commit 3b3f683

8 files changed

+254
-76
lines changed

packages/frontend/.storybook/fakes.ts

+113-51
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,116 @@
11
import type { entities } from 'misskey-js'
22

3-
export const userDetailed = {
4-
id: 'someuserid',
5-
username: 'miskist',
6-
host: 'misskey-hub.net',
7-
name: 'Misskey User',
8-
onlineStatus: 'unknown',
9-
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
10-
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
11-
emojis: [],
12-
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
13-
bannerColor: '#000000',
14-
bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
15-
birthday: '2014-06-20',
16-
createdAt: '2016-12-28T22:49:51.000Z',
17-
description: 'I am a cool user!',
18-
ffVisibility: 'public',
19-
fields: [
20-
{
21-
name: 'Website',
22-
value: 'https://misskey-hub.net',
3+
export function abuseUserReport() {
4+
return {
5+
id: 'someabusereportid',
6+
createdAt: '2016-12-28T22:49:51.000Z',
7+
comment: 'This user is a spammer!',
8+
resolved: false,
9+
reporterId: 'reporterid',
10+
targetUserId: 'targetuserid',
11+
assigneeId: 'assigneeid',
12+
reporter: userDetailed('reporterid', 'reporter', 'misskey-hub.net', 'Reporter'),
13+
targetUser: userDetailed('targetuserid', 'target', 'misskey-hub.net', 'Target'),
14+
assignee: userDetailed('assigneeid', 'assignee', 'misskey-hub.net', 'Assignee'),
15+
me: null,
16+
forwarded: false,
17+
};
18+
}
19+
20+
export function galleryPost(isSensitive = false) {
21+
return {
22+
id: 'somepostid',
23+
createdAt: '2016-12-28T22:49:51.000Z',
24+
updatedAt: '2016-12-28T22:49:51.000Z',
25+
userid: 'someuserid',
26+
user: userDetailed(),
27+
title: 'Some post title',
28+
description: 'Some post description',
29+
fileIds: ['somefileid'],
30+
files: [
31+
file(isSensitive),
32+
],
33+
isSensitive,
34+
likedCount: 0,
35+
isLiked: false,
36+
}
37+
}
38+
39+
export function file(isSensitive = false) {
40+
return {
41+
id: 'somefileid',
42+
createdAt: '2016-12-28T22:49:51.000Z',
43+
name: 'somefile.jpg',
44+
type: 'image/jpeg',
45+
md5: 'f6fc51c73dc21b1fb85ead2cdf57530a',
46+
size: 77752,
47+
isSensitive,
48+
blurhash: 'eQAmoa^-MH8w9ZIvNLSvo^$*MwRPbwtSxutRozjEiwR.RjWBoeozog',
49+
properties: {
50+
width: 1024,
51+
height: 270
2352
},
24-
],
25-
followersCount: 1024,
26-
followingCount: 16,
27-
hasPendingFollowRequestFromYou: false,
28-
hasPendingFollowRequestToYou: false,
29-
isAdmin: false,
30-
isBlocked: false,
31-
isBlocking: false,
32-
isBot: false,
33-
isCat: false,
34-
isFollowed: false,
35-
isFollowing: false,
36-
isLocked: false,
37-
isModerator: false,
38-
isMuted: false,
39-
isSilenced: false,
40-
isSuspended: false,
41-
lang: 'en',
42-
location: 'Fediverse',
43-
notesCount: 65536,
44-
pinnedNoteIds: [],
45-
pinnedNotes: [],
46-
pinnedPage: null,
47-
pinnedPageId: null,
48-
publicReactions: false,
49-
securityKeys: false,
50-
twoFactorEnabled: false,
51-
updatedAt: null,
52-
uri: null,
53-
url: null,
54-
} satisfies entities.UserDetailed
53+
url: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
54+
thumbnailUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
55+
comment: null,
56+
folderId: null,
57+
folder: null,
58+
userId: null,
59+
user: null,
60+
};
61+
}
62+
63+
export function userDetailed(id = 'someuserid', username = 'miskist', host = 'misskey-hub.net', name = 'Misskey User'): entities.UserDetailed {
64+
return {
65+
id,
66+
username,
67+
host,
68+
name,
69+
onlineStatus: 'unknown',
70+
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
71+
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
72+
emojis: [],
73+
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
74+
bannerColor: '#000000',
75+
bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
76+
birthday: '2014-06-20',
77+
createdAt: '2016-12-28T22:49:51.000Z',
78+
description: 'I am a cool user!',
79+
ffVisibility: 'public',
80+
fields: [
81+
{
82+
name: 'Website',
83+
value: 'https://misskey-hub.net',
84+
},
85+
],
86+
followersCount: 1024,
87+
followingCount: 16,
88+
hasPendingFollowRequestFromYou: false,
89+
hasPendingFollowRequestToYou: false,
90+
isAdmin: false,
91+
isBlocked: false,
92+
isBlocking: false,
93+
isBot: false,
94+
isCat: false,
95+
isFollowed: false,
96+
isFollowing: false,
97+
isLocked: false,
98+
isModerator: false,
99+
isMuted: false,
100+
isSilenced: false,
101+
isSuspended: false,
102+
lang: 'en',
103+
location: 'Fediverse',
104+
notesCount: 65536,
105+
pinnedNoteIds: [],
106+
pinnedNotes: [],
107+
pinnedPage: null,
108+
pinnedPageId: null,
109+
publicReactions: false,
110+
securityKeys: false,
111+
twoFactorEnabled: false,
112+
updatedAt: null,
113+
uri: null,
114+
url: null,
115+
};
116+
}

packages/frontend/.storybook/generate.tsx

+10-10
Original file line numberDiff line numberDiff line change
@@ -394,13 +394,13 @@ function toStories(component: string): string {
394394
);
395395
}
396396

397-
// glob('src/{components,pages,ui,widgets}/**/*.vue').then(
398-
glob('src/components/global/**/*.vue').then(
399-
(components) =>
400-
Promise.all(
401-
components.map((component) => {
402-
const stories = component.replace(/\.vue$/, '.stories.ts');
403-
return writeFile(stories, toStories(component));
404-
})
405-
)
406-
);
397+
// glob('src/{components,pages,ui,widgets}/**/*.vue')
398+
Promise.all([
399+
glob('src/components/global/*.vue'),
400+
glob('src/components/MkGalleryPostPreview.vue'),
401+
])
402+
.then((globs) => globs.flat())
403+
.then((components) => Promise.all(components.map((component) => {
404+
const stories = component.replace(/\.vue$/, '.stories.ts');
405+
return writeFile(stories, toStories(component));
406+
})));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/* eslint-disable @typescript-eslint/explicit-function-return-type */
2+
import { expect } from '@storybook/jest';
3+
import { userEvent, waitFor, within } from '@storybook/testing-library';
4+
import { StoryObj } from '@storybook/vue3';
5+
import { galleryPost } from '../../.storybook/fakes';
6+
import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
7+
export const Default = {
8+
render(args) {
9+
return {
10+
components: {
11+
MkGalleryPostPreview,
12+
},
13+
setup() {
14+
return {
15+
args,
16+
};
17+
},
18+
computed: {
19+
props() {
20+
return {
21+
...this.args,
22+
};
23+
},
24+
},
25+
template: '<MkGalleryPostPreview v-bind="props" />',
26+
};
27+
},
28+
async play({ canvasElement }) {
29+
const canvas = within(canvasElement);
30+
const links = canvas.getAllByRole('link');
31+
await expect(links).toHaveLength(2);
32+
await expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`);
33+
await expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`);
34+
},
35+
args: {
36+
post: galleryPost(),
37+
},
38+
decorators: [
39+
() => ({
40+
template: '<div style="width:260px"><story /></div>',
41+
}),
42+
],
43+
parameters: {
44+
layout: 'centered',
45+
},
46+
} satisfies StoryObj<typeof MkGalleryPostPreview>;
47+
export const Hover = {
48+
...Default,
49+
async play(context) {
50+
await Default.play(context);
51+
const canvas = within(context.canvasElement);
52+
const links = canvas.getAllByRole('link');
53+
await waitFor(() => userEvent.hover(links[0]));
54+
},
55+
} satisfies StoryObj<typeof MkGalleryPostPreview>;
56+
export const HoverThenUnhover = {
57+
...Default,
58+
async play(context) {
59+
await Hover.play(context);
60+
const canvas = within(context.canvasElement);
61+
const links = canvas.getAllByRole('link');
62+
await waitFor(() => userEvent.unhover(links[0]));
63+
},
64+
} satisfies StoryObj<typeof MkGalleryPostPreview>;
65+
export const Sensitive = {
66+
...Default,
67+
args: {
68+
...Default.args,
69+
post: galleryPost(true),
70+
},
71+
} satisfies StoryObj<typeof MkGalleryPostPreview>;
72+
export const SensitiveHover = {
73+
...Hover,
74+
args: {
75+
...Hover.args,
76+
post: galleryPost(true),
77+
},
78+
} satisfies StoryObj<typeof MkGalleryPostPreview>;
79+
export const SensitiveHoverThenUnhover = {
80+
...HoverThenUnhover,
81+
args: {
82+
...HoverThenUnhover.args,
83+
post: galleryPost(true),
84+
},
85+
} satisfies StoryObj<typeof MkGalleryPostPreview>;

packages/frontend/src/components/MkGalleryPostPreview.vue

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
<template>
2-
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1">
2+
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover">
33
<div class="thumbnail">
4-
<ImgWithBlurhash class="img" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
4+
<ImgWithBlurhash class="img" :hash="post.files[0].blurhash"/>
5+
<Transition>
6+
<ImgWithBlurhash v-if="show" class="img layered" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
7+
</Transition>
58
</div>
69
<article>
710
<header>
@@ -15,12 +18,25 @@
1518
</template>
1619

1720
<script lang="ts" setup>
18-
import { } from 'vue';
21+
import * as misskey from 'misskey-js';
22+
import { computed, ref } from 'vue';
1923
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
24+
import { defaultStore } from '@/store';
2025
2126
const props = defineProps<{
22-
post: any;
27+
post: misskey.entities.GalleryPost;
2328
}>();
29+
30+
const hover = ref(false);
31+
const show = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive || hover.value);
32+
33+
function enterHover(): void {
34+
hover.value = true;
35+
}
36+
37+
function leaveHover(): void {
38+
hover.value = false;
39+
}
2440
</script>
2541

2642
<style lang="scss" scoped>
@@ -56,6 +72,21 @@ const props = defineProps<{
5672
width: 100%;
5773
height: 100%;
5874
object-fit: cover;
75+
76+
&.layered {
77+
position: absolute;
78+
top: 0;
79+
80+
&.v-enter-active,
81+
&.v-leave-active {
82+
transition: opacity 0.5s ease;
83+
}
84+
85+
&.v-enter-from,
86+
&.v-leave-to {
87+
opacity: 0;
88+
}
89+
}
5990
}
6091
}
6192

packages/frontend/src/components/global/MkAcct.stories.impl.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const Default = {
2525
},
2626
args: {
2727
user: {
28-
...userDetailed,
28+
...userDetailed(),
2929
host: null,
3030
},
3131
},
@@ -37,7 +37,7 @@ export const Detail = {
3737
...Default,
3838
args: {
3939
...Default.args,
40-
user: userDetailed,
40+
user: userDetailed(),
4141
detail: true,
4242
},
4343
} satisfies StoryObj<typeof MkAcct>;

packages/frontend/src/components/global/MkAvatar.stories.impl.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const common = {
2424
};
2525
},
2626
args: {
27-
user: userDetailed,
27+
user: userDetailed(),
2828
},
2929
decorators: [
3030
(Story, context) => ({
@@ -49,7 +49,7 @@ export const ProfilePageCat = {
4949
args: {
5050
...ProfilePage.args,
5151
user: {
52-
...userDetailed,
52+
...userDetailed(),
5353
isCat: true,
5454
},
5555
},

0 commit comments

Comments
 (0)