Merge branch 'misskey-dev:develop' into notes-embed

This commit is contained in:
kakkokari-gtyih 2023-04-06 09:11:50 +09:00 committed by GitHub
commit bb06f64fe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 412 additions and 272 deletions

View File

@ -5,7 +5,9 @@ on:
branches: branches:
- master - master
- develop - develop
pull_request_target: pull_request:
branches-ignore:
- l10n_develop
jobs: jobs:
build: build:

View File

@ -21,6 +21,7 @@
### Client ### Client
- 検索ページでURLを入力した際に照会したときと同等の挙動をするように - 検索ページでURLを入力した際に照会したときと同等の挙動をするように
- ノートのリアクションを大きく表示するオプションを追加 - ノートのリアクションを大きく表示するオプションを追加
- ギャラリー一覧にメディア表示と同じように NSFW 設定を反映するように(ホバーで表示)
- オブジェクトストレージの設定画面を分かりやすく - オブジェクトストレージの設定画面を分かりやすく
- 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更 - 「にゃああああああああああああああ!!!!!!!!!!!!」 (`isCat`) 有効時にアバターに表示される猫耳について挙動を変更
- 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります - 「UIにぼかし効果を使用」 (`useBlurEffect`) で次の挙動が有効になります

View File

@ -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. 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 ## Sponsors
<div align="center"> <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> <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> </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.

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.10.3", "version": "13.11.0.beta-1",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -43,6 +43,7 @@ export class NotificationService implements OnApplicationShutdown {
@bindThis @bindThis
public async readAllNotification( public async readAllNotification(
userId: User['id'], userId: User['id'],
force = false,
) { ) {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`); const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${userId}`);
@ -57,7 +58,7 @@ export class NotificationService implements OnApplicationShutdown {
this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId); this.redisClient.set(`latestReadNotification:${userId}`, latestNotificationId);
if (latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) { if (force || latestReadNotificationId == null || (latestReadNotificationId < latestNotificationId)) {
return this.postReadAllNotifications(userId); return this.postReadAllNotifications(userId);
} }
} }
@ -95,7 +96,7 @@ export class NotificationService implements OnApplicationShutdown {
...data, ...data,
} as Notification; } as Notification;
this.redisClient.xadd( const redisIdPromise = this.redisClient.xadd(
`notificationTimeline:${notifieeId}`, `notificationTimeline:${notifieeId}`,
'MAXLEN', '~', '300', 'MAXLEN', '~', '300',
`${this.idService.parse(notification.id).date.getTime()}-*`, `${this.idService.parse(notification.id).date.getTime()}-*`,
@ -109,7 +110,7 @@ export class NotificationService implements OnApplicationShutdown {
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => { setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`); 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.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
this.pushNotificationService.pushNotification(notifieeId, 'notification', packed); this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);

View File

@ -86,6 +86,10 @@ export class ImportCustomEmojisProcessorService {
continue; continue;
} }
const emojiInfo = record.emoji; 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; const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({ await this.emojisRepository.delete({
name: emojiInfo.name, name: emojiInfo.name,

View File

@ -24,7 +24,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private notificationService: NotificationService, private notificationService: NotificationService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(meta, paramDef, async (ps, me) => {
this.notificationService.readAllNotification(me.id); this.notificationService.readAllNotification(me.id, true);
}); });
} }
} }

View File

@ -1,54 +1,116 @@
import type { entities } from 'misskey-js' import type { entities } from 'misskey-js'
export const userDetailed = { export function abuseUserReport() {
id: 'someuserid', return {
username: 'miskist', id: 'someabusereportid',
host: 'misskey-hub.net', createdAt: '2016-12-28T22:49:51.000Z',
name: 'Misskey User', comment: 'This user is a spammer!',
onlineStatus: 'unknown', resolved: false,
avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true', reporterId: 'reporterid',
avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay', targetUserId: 'targetuserid',
emojis: [], assigneeId: 'assigneeid',
bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog', reporter: userDetailed('reporterid', 'reporter', 'misskey-hub.net', 'Reporter'),
bannerColor: '#000000', targetUser: userDetailed('targetuserid', 'target', 'misskey-hub.net', 'Target'),
bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true', assignee: userDetailed('assigneeid', 'assignee', 'misskey-hub.net', 'Assignee'),
birthday: '2014-06-20', me: null,
createdAt: '2016-12-28T22:49:51.000Z', forwarded: false,
description: 'I am a cool user!', };
ffVisibility: 'public', }
fields: [
{ export function galleryPost(isSensitive = false) {
name: 'Website', return {
value: 'https://misskey-hub.net', 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
}, },
], url: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
followersCount: 1024, thumbnailUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
followingCount: 16, comment: null,
hasPendingFollowRequestFromYou: false, folderId: null,
hasPendingFollowRequestToYou: false, folder: null,
isAdmin: false, userId: null,
isBlocked: false, user: null,
isBlocking: false, };
isBot: false, }
isCat: false,
isFollowed: false, export function userDetailed(id = 'someuserid', username = 'miskist', host = 'misskey-hub.net', name = 'Misskey User'): entities.UserDetailed {
isFollowing: false, return {
isLocked: false, id,
isModerator: false, username,
isMuted: false, host,
isSilenced: false, name,
isSuspended: false, onlineStatus: 'unknown',
lang: 'en', avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true',
location: 'Fediverse', avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay',
notesCount: 65536, emojis: [],
pinnedNoteIds: [], bannerBlurhash: 'eQA^IW^-MH8w9tE8I=S^o{$*R4RikXtSxutRozjEnNR.RQadoyozog',
pinnedNotes: [], bannerColor: '#000000',
pinnedPage: null, bannerUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true',
pinnedPageId: null, birthday: '2014-06-20',
publicReactions: false, createdAt: '2016-12-28T22:49:51.000Z',
securityKeys: false, description: 'I am a cool user!',
twoFactorEnabled: false, ffVisibility: 'public',
updatedAt: null, fields: [
uri: null, {
url: null, name: 'Website',
} satisfies entities.UserDetailed 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,
};
}

View File

@ -394,13 +394,13 @@ function toStories(component: string): string {
); );
} }
// glob('src/{components,pages,ui,widgets}/**/*.vue').then( // glob('src/{components,pages,ui,widgets}/**/*.vue')
glob('src/components/global/**/*.vue').then( Promise.all([
(components) => glob('src/components/global/*.vue'),
Promise.all( glob('src/components/MkGalleryPostPreview.vue'),
components.map((component) => { ])
const stories = component.replace(/\.vue$/, '.stories.ts'); .then((globs) => globs.flat())
return writeFile(stories, toStories(component)); .then((components) => Promise.all(components.map((component) => {
}) const stories = component.replace(/\.vue$/, '.stories.ts');
) return writeFile(stories, toStories(component));
); })));

View File

@ -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>;

View File

@ -1,7 +1,10 @@
<template> <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"> <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> </div>
<article> <article>
<header> <header>
@ -15,12 +18,25 @@
</template> </template>
<script lang="ts" setup> <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 ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
import { defaultStore } from '@/store';
const props = defineProps<{ 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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -56,6 +72,21 @@ const props = defineProps<{
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; 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;
}
}
} }
} }

View File

@ -964,7 +964,7 @@ defineExpose({
padding: 0 12px; padding: 0 12px;
line-height: 34px; line-height: 34px;
font-weight: bold; font-weight: bold;
border-radius: 4px; border-radius: 6px;
min-width: 90px; min-width: 90px;
box-sizing: border-box; box-sizing: border-box;
color: var(--fgOnAccent); color: var(--fgOnAccent);

View File

@ -25,7 +25,7 @@ export const Default = {
}, },
args: { args: {
user: { user: {
...userDetailed, ...userDetailed(),
host: null, host: null,
}, },
}, },
@ -37,7 +37,7 @@ export const Detail = {
...Default, ...Default,
args: { args: {
...Default.args, ...Default.args,
user: userDetailed, user: userDetailed(),
detail: true, detail: true,
}, },
} satisfies StoryObj<typeof MkAcct>; } satisfies StoryObj<typeof MkAcct>;

View File

@ -24,7 +24,7 @@ const common = {
}; };
}, },
args: { args: {
user: userDetailed, user: userDetailed(),
}, },
decorators: [ decorators: [
(Story, context) => ({ (Story, context) => ({
@ -49,7 +49,7 @@ export const ProfilePageCat = {
args: { args: {
...ProfilePage.args, ...ProfilePage.args,
user: { user: {
...userDetailed, ...userDetailed(),
isCat: true, isCat: true,
}, },
}, },

View File

@ -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>;

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/explicit-function-return-type */
import { expect } from '@storybook/jest'; 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 { StoryObj } from '@storybook/vue3';
import { rest } from 'msw'; import { rest } from 'msw';
import { commonHandlers } from '../../../.storybook/mocks'; import { commonHandlers } from '../../../.storybook/mocks';
@ -30,7 +30,7 @@ export const Default = {
const canvas = within(canvasElement); const canvas = within(canvasElement);
const a = canvas.getByRole<HTMLAnchorElement>('link'); const a = canvas.getByRole<HTMLAnchorElement>('link');
await expect(a).toHaveAttribute('href', 'https://misskey-hub.net/'); 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 await tick(); // FIXME: wait for network request
const anchors = canvas.getAllByRole<HTMLAnchorElement>('link'); const anchors = canvas.getAllByRole<HTMLAnchorElement>('link');
@ -44,7 +44,7 @@ export const Default = {
await expect(icon).toBeInTheDocument(); await expect(icon).toBeInTheDocument();
await expect(icon).toHaveAttribute('src', 'https://misskey-hub.net/favicon.ico'); await expect(icon).toHaveAttribute('src', 'https://misskey-hub.net/favicon.ico');
*/ */
await userEvent.unhover(a); await waitFor(() => userEvent.unhover(a));
}, },
args: { args: {
url: 'https://misskey-hub.net/', url: 'https://misskey-hub.net/',

View File

@ -26,10 +26,10 @@ export const Default = {
}; };
}, },
async play({ canvasElement }) { async play({ canvasElement }) {
await expect(canvasElement).toHaveTextContent(userDetailed.name); await expect(canvasElement).toHaveTextContent(userDetailed().name);
}, },
args: { args: {
user: userDetailed, user: userDetailed(),
}, },
parameters: { parameters: {
layout: 'centered', layout: 'centered',
@ -38,12 +38,12 @@ export const Default = {
export const Anonymous = { export const Anonymous = {
...Default, ...Default,
async play({ canvasElement }) { async play({ canvasElement }) {
await expect(canvasElement).toHaveTextContent(userDetailed.username); await expect(canvasElement).toHaveTextContent(userDetailed().username);
}, },
args: { args: {
...Default.args, ...Default.args,
user: { user: {
...userDetailed, ...userDetailed(),
name: null, name: null,
}, },
}, },

View File

@ -31,7 +31,7 @@
<button v-click-anime class="item _button account" @click="openAccountMenu"> <button v-click-anime class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/> <MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/>
</button> </button>
<div class="post" @click="post"> <div class="post" @click="os.post()">
<MkButton class="button" gradate full rounded> <MkButton class="button" gradate full rounded>
<i class="ti ti-pencil ti-fw"></i> <i class="ti ti-pencil ti-fw"></i>
</MkButton> </MkButton>
@ -41,93 +41,50 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineAsyncComponent, defineComponent } from 'vue'; import { computed, defineAsyncComponent, onMounted } from 'vue';
import { openInstanceMenu } from './_common_/common'; import { openInstanceMenu } from './_common_/common';
import { host } from '@/config';
import * as os from '@/os'; import * as os from '@/os';
import { navbarItemDef } from '@/navbar'; import { navbarItemDef } from '@/navbar';
import { openAccountMenu, $i } from '@/account'; import { openAccountMenu as openAccountMenu_, $i } from '@/account';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { mainRouter } from '@/router';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
export default defineComponent({ const WINDOW_THRESHOLD = 1400;
components: {
MkButton,
},
data() { let settingsWindowed = $ref(window.innerWidth > WINDOW_THRESHOLD);
return { let menu = $ref(defaultStore.state.menu);
host: host, // const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
accounts: [], let otherNavItemIndicated = computed<boolean>(() => {
connection: null, for (const def in navbarItemDef) {
navbarItemDef: navbarItemDef, if (menu.includes(def)) continue;
settingsWindowed: false, if (navbarItemDef[def].indicated) return true;
defaultStore, }
instance, return false;
$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);
},
},
}); });
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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -3,7 +3,7 @@
<button v-click-anime class="item _button account" @click="openAccountMenu"> <button v-click-anime class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button> </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> <MkButton class="button" gradate full rounded>
<i class="ti ti-pencil ti-fw"></i><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span> <i class="ti ti-pencil ti-fw"></i><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span>
</MkButton> </MkButton>
@ -40,109 +40,59 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineAsyncComponent, defineComponent } from 'vue'; import { defineAsyncComponent, onMounted, computed, watch, nextTick } from 'vue';
import { openInstanceMenu } from './_common_/common'; import { openInstanceMenu } from './_common_/common';
import { host } from '@/config'; // import { host } from '@/config';
import * as os from '@/os'; import * as os from '@/os';
import { navbarItemDef } from '@/navbar'; import { navbarItemDef } from '@/navbar';
import { openAccountMenu, $i } from '@/account'; import { openAccountMenu as openAccountMenu_, $i } from '@/account';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import { StickySidebar } from '@/scripts/sticky-sidebar'; // import { StickySidebar } from '@/scripts/sticky-sidebar';
import { mainRouter } from '@/router'; // import { mainRouter } from '@/router';
//import MisskeyLogo from '@assets/client/misskey.svg'; //import MisskeyLogo from '@assets/client/misskey.svg';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
import { instance } from '@/instance'; import { instance } from '@/instance';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
export default defineComponent({ const WINDOW_THRESHOLD = 1400;
components: {
MkButton,
//MisskeyLogo,
},
data() { const menu = $ref(defaultStore.state.menu);
return { const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
host: host, const otherNavItemIndicated = computed<boolean>(() => {
accounts: [], for (const def in navbarItemDef) {
connection: null, if (menu.includes(def)) continue;
navbarItemDef: navbarItemDef, if (navbarItemDef[def].indicated) return true;
iconOnly: false, }
settingsWindowed: false, return 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);
},
},
}); });
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> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -166,8 +166,6 @@ export type Channels = {
readAllAntennas: () => void; readAllAntennas: () => void;
unreadAntenna: (payload: Antenna) => void; unreadAntenna: (payload: Antenna) => void;
readAllAnnouncements: () => void; readAllAnnouncements: () => void;
readAllChannels: () => void;
unreadChannel: (payload: Note['id']) => void;
myTokenRegenerated: () => void; myTokenRegenerated: () => void;
reversiNoInvites: () => void; reversiNoInvites: () => void;
reversiInvited: (payload: FIXME) => void; reversiInvited: (payload: FIXME) => void;
@ -1857,12 +1855,6 @@ export type Endpoints = {
req: NoParams; req: NoParams;
res: null; res: null;
}; };
'notifications/read': {
req: {
notificationId: Notification_2['id'];
};
res: null;
};
'page-push': { 'page-push': {
req: { req: {
pageId: Page['id']; pageId: Page['id'];
@ -2361,7 +2353,6 @@ type MeDetailed = UserDetailed & {
hasPendingReceivedFollowRequest: boolean; hasPendingReceivedFollowRequest: boolean;
hasUnreadAnnouncement: boolean; hasUnreadAnnouncement: boolean;
hasUnreadAntenna: boolean; hasUnreadAntenna: boolean;
hasUnreadChannel: boolean;
hasUnreadMentions: boolean; hasUnreadMentions: boolean;
hasUnreadMessagingMessage: boolean; hasUnreadMessagingMessage: boolean;
hasUnreadNotification: boolean; hasUnreadNotification: boolean;
@ -2618,7 +2609,11 @@ export class Stream extends EventEmitter<StreamEvents> {
// (undocumented) // (undocumented)
removeSharedConnectionPool(pool: Pool): void; removeSharedConnectionPool(pool: Pool): void;
// (undocumented) // (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) // (undocumented)
state: 'initializing' | 'reconnecting' | 'connected'; state: 'initializing' | 'reconnecting' | 'connected';
// (undocumented) // (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: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: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/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:35:4 - (ae-forgotten-export) The symbol "FIXME" 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) // (No @packageDocumentation comment for this package)

View File

@ -169,14 +169,21 @@ export default class Stream extends EventEmitter<StreamEvents> {
/** /**
* Send a message to connection * Send a message to connection
* ! JSONで行われます !
*/ */
public send(typeOrPayload: any, payload?: any): void { public send(typeOrPayload: string): void
const data = payload === undefined ? typeOrPayload : { public send(typeOrPayload: string, payload: any): void
type: typeOrPayload, public send(typeOrPayload: Record<string, any> | any[]): void
body: payload, 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));
} }
/** /**