Merge branch 'misskey-dev:develop' into notes-embed
This commit is contained in:
commit
bb06f64fe2
|
@ -5,7 +5,9 @@ on:
|
|||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request_target:
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- l10n_develop
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
### Client
|
||||
- 検索ページでURLを入力した際に照会したときと同等の挙動をするように
|
||||
- ノートのリアクションを大きく表示するオプションを追加
|
||||
- ギャラリー一覧にメディア表示と同じように NSFW 設定を反映するように(ホバーで表示)
|
||||
- オブジェクトストレージの設定画面を分かりやすく
|
||||
- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
|
||||
- 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります
|
||||
|
|
11
README.md
11
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
|
||||
|
||||
<div align="center">
|
||||
<a class="rss3" title="RSS3" href="https://rss3.io/" target="_blank"><img src="https://rss3.mypinata.cloud/ipfs/QmUG6H3Z7D5P511shn7sB4CPmpjH5uZWu4m5mWX7U3Gqbu" alt="RSS3" height="60"></a>
|
||||
</div>
|
||||
|
||||
## Thanks
|
||||
|
||||
<a href="https://www.chromatic.com/"><img src="https://user-images.githubusercontent.com/321738/84662277-e3db4f80-af1b-11ea-88f5-91d67a5e59f6.png" width="153" height="30" alt="Chromatic" /></a>
|
||||
|
||||
Thanks to [Chromatic](https://www.chromatic.com/) for providing the visual testing platform that helps us review UI changes and catch visual regressions.
|
||||
|
||||
<a href="https://hub.docker.com/"><img src="https://user-images.githubusercontent.com/20679825/230148221-f8e73a32-a49b-47c3-9029-9a15c3824f92.png" width="117" height="30" alt="Docker" /></a>
|
||||
|
||||
Thanks to [Docker](https://hub.docker.com/) for providing the container platform that helps us run Misskey in production.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "13.10.3",
|
||||
"version": "13.11.0.beta-1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -24,7 +24,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|||
private notificationService: NotificationService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
this.notificationService.readAllNotification(me.id);
|
||||
this.notificationService.readAllNotification(me.id, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
})));
|
||||
|
|
|
@ -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: '<MkGalleryPostPreview v-bind="props" />',
|
||||
};
|
||||
},
|
||||
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: '<div style="width:260px"><story /></div>',
|
||||
}),
|
||||
],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies StoryObj<typeof MkGalleryPostPreview>;
|
||||
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<typeof MkGalleryPostPreview>;
|
||||
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<typeof MkGalleryPostPreview>;
|
||||
export const Sensitive = {
|
||||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
post: galleryPost(true),
|
||||
},
|
||||
} satisfies StoryObj<typeof MkGalleryPostPreview>;
|
||||
export const SensitiveHover = {
|
||||
...Hover,
|
||||
args: {
|
||||
...Hover.args,
|
||||
post: galleryPost(true),
|
||||
},
|
||||
} satisfies StoryObj<typeof MkGalleryPostPreview>;
|
||||
export const SensitiveHoverThenUnhover = {
|
||||
...HoverThenUnhover,
|
||||
args: {
|
||||
...HoverThenUnhover.args,
|
||||
post: galleryPost(true),
|
||||
},
|
||||
} satisfies StoryObj<typeof MkGalleryPostPreview>;
|
|
@ -1,7 +1,10 @@
|
|||
<template>
|
||||
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1">
|
||||
<MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover">
|
||||
<div class="thumbnail">
|
||||
<ImgWithBlurhash class="img" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
|
||||
<ImgWithBlurhash class="img" :hash="post.files[0].blurhash"/>
|
||||
<Transition>
|
||||
<ImgWithBlurhash v-if="show" class="img layered" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
|
||||
</Transition>
|
||||
</div>
|
||||
<article>
|
||||
<header>
|
||||
|
@ -15,12 +18,25 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { computed, ref } from 'vue';
|
||||
import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = defineProps<{
|
||||
post: any;
|
||||
post: misskey.entities.GalleryPost;
|
||||
}>();
|
||||
|
||||
const hover = ref(false);
|
||||
const show = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive || hover.value);
|
||||
|
||||
function enterHover(): void {
|
||||
hover.value = true;
|
||||
}
|
||||
|
||||
function leaveHover(): void {
|
||||
hover.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@ -56,6 +72,21 @@ const props = defineProps<{
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
&.layered {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
&.v-enter-active,
|
||||
&.v-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
&.v-enter-from,
|
||||
&.v-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -964,7 +964,7 @@ defineExpose({
|
|||
padding: 0 12px;
|
||||
line-height: 34px;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
min-width: 90px;
|
||||
box-sizing: border-box;
|
||||
color: var(--fgOnAccent);
|
||||
|
|
|
@ -25,7 +25,7 @@ export const Default = {
|
|||
},
|
||||
args: {
|
||||
user: {
|
||||
...userDetailed,
|
||||
...userDetailed(),
|
||||
host: null,
|
||||
},
|
||||
},
|
||||
|
@ -37,7 +37,7 @@ export const Detail = {
|
|||
...Default,
|
||||
args: {
|
||||
...Default.args,
|
||||
user: userDetailed,
|
||||
user: userDetailed(),
|
||||
detail: true,
|
||||
},
|
||||
} satisfies StoryObj<typeof MkAcct>;
|
||||
|
|
|
@ -24,7 +24,7 @@ const common = {
|
|||
};
|
||||
},
|
||||
args: {
|
||||
user: userDetailed,
|
||||
user: userDetailed(),
|
||||
},
|
||||
decorators: [
|
||||
(Story, context) => ({
|
||||
|
@ -49,7 +49,7 @@ export const ProfilePageCat = {
|
|||
args: {
|
||||
...ProfilePage.args,
|
||||
user: {
|
||||
...userDetailed,
|
||||
...userDetailed(),
|
||||
isCat: true,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { expect } from '@storybook/jest';
|
||||
import { waitFor } from '@storybook/testing-library';
|
||||
import { StoryObj } from '@storybook/vue3';
|
||||
import MkError from './MkError.vue';
|
||||
export const Default = {
|
||||
render(args) {
|
||||
return {
|
||||
components: {
|
||||
MkError,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
args,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
props() {
|
||||
return {
|
||||
...this.args,
|
||||
};
|
||||
},
|
||||
},
|
||||
template: '<MkError v-bind="props" />',
|
||||
};
|
||||
},
|
||||
async play({ canvasElement }) {
|
||||
await expect(canvasElement.firstElementChild).not.toBeNull();
|
||||
await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active'));
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
} satisfies StoryObj<typeof MkError>;
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
import { expect } from '@storybook/jest';
|
||||
import { userEvent, within } from '@storybook/testing-library';
|
||||
import { userEvent, waitFor, within } from '@storybook/testing-library';
|
||||
import { StoryObj } from '@storybook/vue3';
|
||||
import { rest } from 'msw';
|
||||
import { commonHandlers } from '../../../.storybook/mocks';
|
||||
|
@ -30,7 +30,7 @@ export const Default = {
|
|||
const canvas = within(canvasElement);
|
||||
const a = canvas.getByRole<HTMLAnchorElement>('link');
|
||||
await expect(a).toHaveAttribute('href', 'https://misskey-hub.net/');
|
||||
await userEvent.hover(a);
|
||||
await waitFor(() => userEvent.hover(a));
|
||||
/*
|
||||
await tick(); // FIXME: wait for network request
|
||||
const anchors = canvas.getAllByRole<HTMLAnchorElement>('link');
|
||||
|
@ -44,7 +44,7 @@ export const Default = {
|
|||
await expect(icon).toBeInTheDocument();
|
||||
await expect(icon).toHaveAttribute('src', 'https://misskey-hub.net/favicon.ico');
|
||||
*/
|
||||
await userEvent.unhover(a);
|
||||
await waitFor(() => userEvent.unhover(a));
|
||||
},
|
||||
args: {
|
||||
url: 'https://misskey-hub.net/',
|
||||
|
|
|
@ -26,10 +26,10 @@ export const Default = {
|
|||
};
|
||||
},
|
||||
async play({ canvasElement }) {
|
||||
await expect(canvasElement).toHaveTextContent(userDetailed.name);
|
||||
await expect(canvasElement).toHaveTextContent(userDetailed().name);
|
||||
},
|
||||
args: {
|
||||
user: userDetailed,
|
||||
user: userDetailed(),
|
||||
},
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
|
@ -38,12 +38,12 @@ export const Default = {
|
|||
export const Anonymous = {
|
||||
...Default,
|
||||
async play({ canvasElement }) {
|
||||
await expect(canvasElement).toHaveTextContent(userDetailed.username);
|
||||
await expect(canvasElement).toHaveTextContent(userDetailed().username);
|
||||
},
|
||||
args: {
|
||||
...Default.args,
|
||||
user: {
|
||||
...userDetailed,
|
||||
...userDetailed(),
|
||||
name: null,
|
||||
},
|
||||
},
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<button v-click-anime class="item _button account" @click="openAccountMenu">
|
||||
<MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/>
|
||||
</button>
|
||||
<div class="post" @click="post">
|
||||
<div class="post" @click="os.post()">
|
||||
<MkButton class="button" gradate full rounded>
|
||||
<i class="ti ti-pencil ti-fw"></i>
|
||||
</MkButton>
|
||||
|
@ -41,93 +41,50 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, onMounted } from 'vue';
|
||||
import { openInstanceMenu } from './_common_/common';
|
||||
import { host } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import { navbarItemDef } from '@/navbar';
|
||||
import { openAccountMenu, $i } from '@/account';
|
||||
import { openAccountMenu as openAccountMenu_, $i } from '@/account';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { mainRouter } from '@/router';
|
||||
import { defaultStore } from '@/store';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
},
|
||||
const WINDOW_THRESHOLD = 1400;
|
||||
|
||||
data() {
|
||||
return {
|
||||
host: host,
|
||||
accounts: [],
|
||||
connection: null,
|
||||
navbarItemDef: navbarItemDef,
|
||||
settingsWindowed: false,
|
||||
defaultStore,
|
||||
instance,
|
||||
$i,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
menu(): string[] {
|
||||
return defaultStore.state.menu;
|
||||
},
|
||||
|
||||
otherNavItemIndicated(): boolean {
|
||||
for (const def in this.navbarItemDef) {
|
||||
if (this.menu.includes(def)) continue;
|
||||
if (this.navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'defaultStore.reactiveState.menuDisplay.value'() {
|
||||
this.calcViewState();
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('resize', this.calcViewState);
|
||||
this.calcViewState();
|
||||
},
|
||||
|
||||
methods: {
|
||||
openInstanceMenu,
|
||||
|
||||
calcViewState() {
|
||||
this.settingsWindowed = (window.innerWidth > 1400);
|
||||
},
|
||||
|
||||
post() {
|
||||
os.post();
|
||||
},
|
||||
|
||||
search() {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
|
||||
more(ev) {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
anchor: { x: 'center', y: 'bottom' },
|
||||
}, {
|
||||
}, 'closed');
|
||||
},
|
||||
|
||||
openAccountMenu: (ev) => {
|
||||
openAccountMenu({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
},
|
||||
},
|
||||
let settingsWindowed = $ref(window.innerWidth > WINDOW_THRESHOLD);
|
||||
let menu = $ref(defaultStore.state.menu);
|
||||
// const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||
let otherNavItemIndicated = computed<boolean>(() => {
|
||||
for (const def in navbarItemDef) {
|
||||
if (menu.includes(def)) continue;
|
||||
if (navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
function more(ev: MouseEvent) {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
anchor: { x: 'center', y: 'bottom' },
|
||||
}, {
|
||||
}, 'closed');
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
settingsWindowed = (window.innerWidth >= WINDOW_THRESHOLD);
|
||||
}, { passive: true });
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<button v-click-anime class="item _button account" @click="openAccountMenu">
|
||||
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
|
||||
</button>
|
||||
<div class="post" data-cy-open-post-form @click="post">
|
||||
<div class="post" data-cy-open-post-form @click="os.post">
|
||||
<MkButton class="button" gradate full rounded>
|
||||
<i class="ti ti-pencil ti-fw"></i><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span>
|
||||
</MkButton>
|
||||
|
@ -40,109 +40,59 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineAsyncComponent, defineComponent } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, computed, watch, nextTick } from 'vue';
|
||||
import { openInstanceMenu } from './_common_/common';
|
||||
import { host } from '@/config';
|
||||
// import { host } from '@/config';
|
||||
import * as os from '@/os';
|
||||
import { navbarItemDef } from '@/navbar';
|
||||
import { openAccountMenu, $i } from '@/account';
|
||||
import { openAccountMenu as openAccountMenu_, $i } from '@/account';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { StickySidebar } from '@/scripts/sticky-sidebar';
|
||||
import { mainRouter } from '@/router';
|
||||
// import { StickySidebar } from '@/scripts/sticky-sidebar';
|
||||
// import { mainRouter } from '@/router';
|
||||
//import MisskeyLogo from '@assets/client/misskey.svg';
|
||||
import { defaultStore } from '@/store';
|
||||
import { instance } from '@/instance';
|
||||
import { i18n } from '@/i18n';
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
MkButton,
|
||||
//MisskeyLogo,
|
||||
},
|
||||
const WINDOW_THRESHOLD = 1400;
|
||||
|
||||
data() {
|
||||
return {
|
||||
host: host,
|
||||
accounts: [],
|
||||
connection: null,
|
||||
navbarItemDef: navbarItemDef,
|
||||
iconOnly: false,
|
||||
settingsWindowed: false,
|
||||
defaultStore,
|
||||
instance,
|
||||
$i,
|
||||
i18n,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
menu(): string[] {
|
||||
return this.defaultStore.state.menu;
|
||||
},
|
||||
|
||||
otherNavItemIndicated(): boolean {
|
||||
for (const def in this.navbarItemDef) {
|
||||
if (this.menu.includes(def)) continue;
|
||||
if (this.navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
'defaultStore.reactiveState.menuDisplay.value'() {
|
||||
this.calcViewState();
|
||||
},
|
||||
|
||||
iconOnly() {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('change-view-mode');
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
created() {
|
||||
window.addEventListener('resize', this.calcViewState);
|
||||
this.calcViewState();
|
||||
},
|
||||
|
||||
mounted() {
|
||||
const sticky = new StickySidebar(this.$el.parentElement, 16);
|
||||
window.addEventListener('scroll', () => {
|
||||
sticky.calc(window.scrollY);
|
||||
}, { passive: true });
|
||||
},
|
||||
|
||||
methods: {
|
||||
openInstanceMenu,
|
||||
|
||||
calcViewState() {
|
||||
this.iconOnly = (window.innerWidth <= 1400) || (this.defaultStore.state.menuDisplay === 'sideIcon');
|
||||
this.settingsWindowed = (window.innerWidth > 1400);
|
||||
},
|
||||
|
||||
post() {
|
||||
os.post();
|
||||
},
|
||||
|
||||
search() {
|
||||
mainRouter.push('/search');
|
||||
},
|
||||
|
||||
more(ev) {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
}, {}, 'closed');
|
||||
},
|
||||
|
||||
openAccountMenu: (ev) => {
|
||||
openAccountMenu({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
},
|
||||
},
|
||||
const menu = $ref(defaultStore.state.menu);
|
||||
const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
|
||||
const otherNavItemIndicated = computed<boolean>(() => {
|
||||
for (const def in navbarItemDef) {
|
||||
if (menu.includes(def)) continue;
|
||||
if (navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
let el = $shallowRef<HTMLElement>();
|
||||
// let accounts = $ref([]);
|
||||
// let connection = $ref(null);
|
||||
let iconOnly = $ref(false);
|
||||
let settingsWindowed = $ref(false);
|
||||
|
||||
function calcViewState() {
|
||||
iconOnly = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
|
||||
settingsWindowed = (window.innerWidth > WINDOW_THRESHOLD);
|
||||
}
|
||||
|
||||
function more(ev: MouseEvent) {
|
||||
os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
}, {}, 'closed');
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
}
|
||||
|
||||
watch(defaultStore.reactiveState.menuDisplay, () => {
|
||||
calcViewState();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -166,8 +166,6 @@ export type Channels = {
|
|||
readAllAntennas: () => void;
|
||||
unreadAntenna: (payload: Antenna) => void;
|
||||
readAllAnnouncements: () => void;
|
||||
readAllChannels: () => void;
|
||||
unreadChannel: (payload: Note['id']) => void;
|
||||
myTokenRegenerated: () => void;
|
||||
reversiNoInvites: () => void;
|
||||
reversiInvited: (payload: FIXME) => void;
|
||||
|
@ -1857,12 +1855,6 @@ export type Endpoints = {
|
|||
req: NoParams;
|
||||
res: null;
|
||||
};
|
||||
'notifications/read': {
|
||||
req: {
|
||||
notificationId: Notification_2['id'];
|
||||
};
|
||||
res: null;
|
||||
};
|
||||
'page-push': {
|
||||
req: {
|
||||
pageId: Page['id'];
|
||||
|
@ -2361,7 +2353,6 @@ type MeDetailed = UserDetailed & {
|
|||
hasPendingReceivedFollowRequest: boolean;
|
||||
hasUnreadAnnouncement: boolean;
|
||||
hasUnreadAntenna: boolean;
|
||||
hasUnreadChannel: boolean;
|
||||
hasUnreadMentions: boolean;
|
||||
hasUnreadMessagingMessage: boolean;
|
||||
hasUnreadNotification: boolean;
|
||||
|
@ -2618,7 +2609,11 @@ export class Stream extends EventEmitter<StreamEvents> {
|
|||
// (undocumented)
|
||||
removeSharedConnectionPool(pool: Pool): void;
|
||||
// (undocumented)
|
||||
send(typeOrPayload: any, payload?: any): void;
|
||||
send(typeOrPayload: string): void;
|
||||
// (undocumented)
|
||||
send(typeOrPayload: string, payload: any): void;
|
||||
// (undocumented)
|
||||
send(typeOrPayload: Record<string, any> | any[]): void;
|
||||
// (undocumented)
|
||||
state: 'initializing' | 'reconnecting' | 'connected';
|
||||
// (undocumented)
|
||||
|
@ -2714,8 +2709,8 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
|
|||
//
|
||||
// src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:595:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:35:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
// src/api.types.ts:594:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
|
||||
// src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
||||
|
|
|
@ -169,14 +169,21 @@ export default class Stream extends EventEmitter<StreamEvents> {
|
|||
|
||||
/**
|
||||
* Send a message to connection
|
||||
* ! ストリーム上のやり取りはすべてJSONで行われます !
|
||||
*/
|
||||
public send(typeOrPayload: any, payload?: any): void {
|
||||
const data = payload === undefined ? typeOrPayload : {
|
||||
type: typeOrPayload,
|
||||
body: payload,
|
||||
};
|
||||
public send(typeOrPayload: string): void
|
||||
public send(typeOrPayload: string, payload: any): void
|
||||
public send(typeOrPayload: Record<string, any> | any[]): void
|
||||
public send(typeOrPayload: string | Record<string, any> | any[], payload?: any): void {
|
||||
if (typeof typeOrPayload === 'string') {
|
||||
this.stream.send(JSON.stringify({
|
||||
type: typeOrPayload,
|
||||
...(payload === undefined ? {} : { body: payload }),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
this.stream.send(JSON.stringify(data));
|
||||
this.stream.send(JSON.stringify(typeOrPayload));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue