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:
- master
- develop
pull_request_target:
pull_request:
branches-ignore:
- l10n_develop
jobs:
build:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@ -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,
},
},

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 */
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/',

View File

@ -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,
},
},

View File

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

View File

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

View File

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

View File

@ -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));
}
/**