diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index eda28c5f77..9659757c49 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -5,7 +5,9 @@ on:
branches:
- master
- develop
- pull_request_target:
+ pull_request:
+ branches-ignore:
+ - l10n_develop
jobs:
build:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3add38674..d031d4f85e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -21,6 +21,7 @@
### Client
- 検索ページでURLを入力した際に照会したときと同等の挙動をするように
- ノートのリアクションを大きく表示するオプションを追加
+- ギャラリー一覧にメディア表示と同じように NSFW 設定を反映するように(ホバーで表示)
- オブジェクトストレージの設定画面を分かりやすく
- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
- 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります
diff --git a/README.md b/README.md
index c12882ca32..fdc8c1d8d3 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,17 @@ With Misskey's built in drive, you get cloud storage right in your social media,
Misskey Documentation can be found at [Misskey Hub](https://misskey-hub.net/), some of the links and graphics above also lead to specific portions of it.
## Sponsors
+
+
+## Thanks
+
+
+
+Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
+
+
+
+Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.
diff --git a/package.json b/package.json
index 8506d8aa29..52f0c50a52 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "misskey",
- "version": "13.10.3",
+ "version": "13.11.0.beta-1",
"codename": "nasubi",
"repository": {
"type": "git",
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 366dc08c02..c44dddea41 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -43,6 +43,7 @@ export class NotificationService implements OnApplicationShutdown {
@bindThis
public async readAllNotification(
userId: User['id'],
+ force = false,
) {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
@@ -57,7 +58,7 @@ export class NotificationService implements OnApplicationShutdown {
this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId);
- if (latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
+ if (force || latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
return this.postReadAllNotifications(userId);
}
}
@@ -95,7 +96,7 @@ export class NotificationService implements OnApplicationShutdown {
...data,
} as Notification;
- this.redisClient.xadd(
+ const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`,
'MAXLEN', '~', '300',
`${this.idService.parse(notification.id).date.getTime()}-*`,
@@ -109,7 +110,7 @@ export class NotificationService implements OnApplicationShutdown {
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
- if (latestReadNotificationId && (latestReadNotificationId >= notification.id)) return;
+ if (latestReadNotificationId && (latestReadNotificationId >= await redisIdPromise)) return;
this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index ed96e9a525..cf78d8330c 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -86,6 +86,10 @@ export class ImportCustomEmojisProcessorService {
continue;
}
const emojiInfo = record.emoji;
+ if (!/^[a-zA-Z0-9_]+$/.test(emojiInfo.name)) {
+ this.logger.error(`invalid emojiname: ${emojiInfo.name}`);
+ continue;
+ }
const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({
name: emojiInfo.name,
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index 9ba6079189..e601bf9d5b 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -24,7 +24,7 @@ export default class extends Endpoint {
private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, me) => {
- this.notificationService.readAllNotification(me.id);
+ this.notificationService.readAllNotification(me.id, true);
});
}
}
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index b620cf68a3..23b82a8ac5 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -1,54 +1,116 @@
import type { entities } from 'misskey-js'
-export const userDetailed = {
- id: 'someuserid',
- username: 'miskist',
- host: 'misskey-hub.net',
- name: 'Misskey User',
- onlineStatus: 'unknown',
- avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
- avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
- emojis: [],
- bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
- bannerColor: '#000000',
- bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
- birthday: '2014-06-20',
- createdAt: '2016-12-28T22:49:51.000Z',
- description: 'I am a cool user!',
- ffVisibility: 'public',
- fields: [
- {
- name: 'Website',
- value: 'https://misskey-hub.net',
+export function abuseUserReport() {
+ return {
+ id: 'someabusereportid',
+ createdAt: '2016-12-28T22:49:51.000Z',
+ comment: 'This user is a spammer!',
+ resolved: false,
+ reporterId: 'reporterid',
+ targetUserId: 'targetuserid',
+ assigneeId: 'assigneeid',
+ reporter: userDetailed('reporterid', 'reporter', 'misskey-hub.net', 'Reporter'),
+ targetUser: userDetailed('targetuserid', 'target', 'misskey-hub.net', 'Target'),
+ assignee: userDetailed('assigneeid', 'assignee', 'misskey-hub.net', 'Assignee'),
+ me: null,
+ forwarded: false,
+ };
+}
+
+export function galleryPost(isSensitive = false) {
+ return {
+ id: 'somepostid',
+ createdAt: '2016-12-28T22:49:51.000Z',
+ updatedAt: '2016-12-28T22:49:51.000Z',
+ userid: 'someuserid',
+ user: userDetailed(),
+ title: 'Some post title',
+ description: 'Some post description',
+ fileIds: ['somefileid'],
+ files: [
+ file(isSensitive),
+ ],
+ isSensitive,
+ likedCount: 0,
+ isLiked: false,
+ }
+}
+
+export function file(isSensitive = false) {
+ return {
+ id: 'somefileid',
+ createdAt: '2016-12-28T22:49:51.000Z',
+ name: 'somefile.jpg',
+ type: 'image/jpeg',
+ md5: 'f6fc51c73dc21b1fb85ead2cdf57530a',
+ size: 77752,
+ isSensitive,
+ blurhash: 'eQAmoa^-MH8w9ZIvNLSvo^$*MwRPbwtSxutRozjEiwR.RjWBoeozog',
+ properties: {
+ width: 1024,
+ height: 270
},
- ],
- followersCount: 1024,
- followingCount: 16,
- hasPendingFollowRequestFromYou: false,
- hasPendingFollowRequestToYou: false,
- isAdmin: false,
- isBlocked: false,
- isBlocking: false,
- isBot: false,
- isCat: false,
- isFollowed: false,
- isFollowing: false,
- isLocked: false,
- isModerator: false,
- isMuted: false,
- isSilenced: false,
- isSuspended: false,
- lang: 'en',
- location: 'Fediverse',
- notesCount: 65536,
- pinnedNoteIds: [],
- pinnedNotes: [],
- pinnedPage: null,
- pinnedPageId: null,
- publicReactions: false,
- securityKeys: false,
- twoFactorEnabled: false,
- updatedAt: null,
- uri: null,
- url: null,
-} satisfies entities.UserDetailed
+ url: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
+ thumbnailUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
+ comment: null,
+ folderId: null,
+ folder: null,
+ userId: null,
+ user: null,
+ };
+}
+
+export function userDetailed(id = 'someuserid', username = 'miskist', host = 'misskey-hub.net', name = 'Misskey User'): entities.UserDetailed {
+ return {
+ id,
+ username,
+ host,
+ name,
+ onlineStatus: 'unknown',
+ avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
+ avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
+ emojis: [],
+ bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
+ bannerColor: '#000000',
+ bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
+ birthday: '2014-06-20',
+ createdAt: '2016-12-28T22:49:51.000Z',
+ description: 'I am a cool user!',
+ ffVisibility: 'public',
+ fields: [
+ {
+ name: 'Website',
+ value: 'https://misskey-hub.net',
+ },
+ ],
+ followersCount: 1024,
+ followingCount: 16,
+ hasPendingFollowRequestFromYou: false,
+ hasPendingFollowRequestToYou: false,
+ isAdmin: false,
+ isBlocked: false,
+ isBlocking: false,
+ isBot: false,
+ isCat: false,
+ isFollowed: false,
+ isFollowing: false,
+ isLocked: false,
+ isModerator: false,
+ isMuted: false,
+ isSilenced: false,
+ isSuspended: false,
+ lang: 'en',
+ location: 'Fediverse',
+ notesCount: 65536,
+ pinnedNoteIds: [],
+ pinnedNotes: [],
+ pinnedPage: null,
+ pinnedPageId: null,
+ publicReactions: false,
+ securityKeys: false,
+ twoFactorEnabled: false,
+ updatedAt: null,
+ uri: null,
+ url: null,
+ };
+}
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index f0865fcc24..b3bbeeb51c 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -394,13 +394,13 @@ function toStories(component: string): string {
);
}
-// glob('src/{components,pages,ui,widgets}/**/*.vue').then(
-glob('src/components/global/**/*.vue').then(
- (components) =>
- Promise.all(
- components.map((component) => {
- const stories = component.replace(/\.vue$/, '.stories.ts');
- return writeFile(stories, toStories(component));
- })
- )
-);
+// glob('src/{components,pages,ui,widgets}/**/*.vue')
+Promise.all([
+ glob('src/components/global/*.vue'),
+ glob('src/components/MkGalleryPostPreview.vue'),
+])
+ .then((globs) => globs.flat())
+ .then((components) => Promise.all(components.map((component) => {
+ const stories = component.replace(/\.vue$/, '.stories.ts');
+ return writeFile(stories, toStories(component));
+ })));
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
new file mode 100644
index 0000000000..e46a708192
--- /dev/null
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -0,0 +1,85 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { expect } from '@storybook/jest';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { StoryObj } from '@storybook/vue3';
+import { galleryPost } from '../../.storybook/fakes';
+import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
+export const Default = {
+ render(args) {
+ return {
+ components: {
+ MkGalleryPostPreview,
+ },
+ setup() {
+ return {
+ args,
+ };
+ },
+ computed: {
+ props() {
+ return {
+ ...this.args,
+ };
+ },
+ },
+ template: '',
+ };
+ },
+ async play({ canvasElement }) {
+ const canvas = within(canvasElement);
+ const links = canvas.getAllByRole('link');
+ await expect(links).toHaveLength(2);
+ await expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`);
+ await expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`);
+ },
+ args: {
+ post: galleryPost(),
+ },
+ decorators: [
+ () => ({
+ template: '
',
+ }),
+ ],
+ parameters: {
+ layout: 'centered',
+ },
+} satisfies StoryObj;
+export const Hover = {
+ ...Default,
+ async play(context) {
+ await Default.play(context);
+ const canvas = within(context.canvasElement);
+ const links = canvas.getAllByRole('link');
+ await waitFor(() => userEvent.hover(links[0]));
+ },
+} satisfies StoryObj;
+export const HoverThenUnhover = {
+ ...Default,
+ async play(context) {
+ await Hover.play(context);
+ const canvas = within(context.canvasElement);
+ const links = canvas.getAllByRole('link');
+ await waitFor(() => userEvent.unhover(links[0]));
+ },
+} satisfies StoryObj;
+export const Sensitive = {
+ ...Default,
+ args: {
+ ...Default.args,
+ post: galleryPost(true),
+ },
+} satisfies StoryObj;
+export const SensitiveHover = {
+ ...Hover,
+ args: {
+ ...Hover.args,
+ post: galleryPost(true),
+ },
+} satisfies StoryObj;
+export const SensitiveHoverThenUnhover = {
+ ...HoverThenUnhover,
+ args: {
+ ...HoverThenUnhover.args,
+ post: galleryPost(true),
+ },
+} satisfies StoryObj;
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 2c5032119f..944f5ad97b 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -1,7 +1,10 @@
-
+