Merge branch 'develop' into notification-read-api

This commit is contained in:
tamaina 2021-12-25 02:02:49 +09:00
commit a456a63e34
19 changed files with 212 additions and 171 deletions

View file

@ -7,6 +7,13 @@
--> -->
## 12.x.x (unreleased)
### Improvements
### Bugfixes
- クライアント: 一部のコンポーネントが裏に隠れるのを修正
## 12.100.2 (2021/12/18) ## 12.100.2 (2021/12/18)
### Bugfixes ### Bugfixes

View file

@ -87,7 +87,7 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows).
## Vue ## Vue
Misskey uses Vue(v3) as its front-end framework. Misskey uses Vue(v3) as its front-end framework.
**When creating a new component, please use the Composition API instead of the Options API.** **When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.**
Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome. Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
## Adding MisskeyRoom items ## Adding MisskeyRoom items

View file

@ -614,7 +614,6 @@ regenerateLoginToken: "ログイントークンを再生成"
regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。" regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。" setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
fileIdOrUrl: "ファイルIDまたはURL" fileIdOrUrl: "ファイルIDまたはURL"
chatOpenBehavior: "チャットを開くときの動作"
behavior: "動作" behavior: "動作"
sample: "サンプル" sample: "サンプル"
abuseReports: "通報" abuseReports: "通報"

View file

@ -4,6 +4,7 @@ block vars
- const user = note.user; - const user = note.user;
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`; - const url = `${config.url}/notes/${note.id}`;
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
block title block title
= `${title} | ${instanceName}` = `${title} | ${instanceName}`
@ -19,7 +20,7 @@ block og
meta(property='og:image' content= user.avatarUrl) meta(property='og:image' content= user.avatarUrl)
block meta block meta
if user.host || profile.noCrawle if user.host || isRenote || profile.noCrawle
meta(name='robots' content='noindex') meta(name='robots' content='noindex')
meta(name='misskey:user-username' content=user.username) meta(name='misskey:user-username' content=user.username)

View file

@ -3,10 +3,33 @@ import config from '@/config/index';
import { SwSubscriptions } from '@/models/index'; import { SwSubscriptions } from '@/models/index';
import { fetchMeta } from '@/misc/fetch-meta'; import { fetchMeta } from '@/misc/fetch-meta';
import { Packed } from '@/misc/schema'; import { Packed } from '@/misc/schema';
import { getNoteSummary } from '@/misc/get-note-summary';
type notificationType = 'notification' | 'unreadMessagingMessage'; type notificationType = 'notification' | 'unreadMessagingMessage';
type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>; type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>;
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
function truncateNotification(notification: Packed<'Notification'>): any {
if (notification.note) {
return {
...notification,
note: {
...notification.note,
// textをgetNoteSummaryしたものに置き換える
text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
...{
cw: undefined,
reply: undefined,
renote: undefined,
user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
}
}
};
}
return notification;
}
export default async function(userId: string, type: notificationType, body: notificationBody) { export default async function(userId: string, type: notificationType, body: notificationBody) {
const meta = await fetchMeta(); const meta = await fetchMeta();
@ -32,7 +55,9 @@ export default async function(userId: string, type: notificationType, body: noti
}; };
push.sendNotification(pushSubscription, JSON.stringify({ push.sendNotification(pushSubscription, JSON.stringify({
type, body, type,
body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body,
userId,
}), { }), {
proxy: config.proxy, proxy: config.proxy,
}).catch((err: any) => { }).catch((err: any) => {

View file

@ -106,11 +106,6 @@ export default defineComponent({
return; return;
} }
if (this.to.startsWith('/my/messaging')) {
if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window();
if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout();
}
if (this.behavior) { if (this.behavior) {
if (this.behavior === 'window') { if (this.behavior === 'window') {
return this.window(); return this.window();

View file

@ -1,7 +1,5 @@
<template> <template>
<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target" <component :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
@mouseover="onMouseover"
@mouseleave="onMouseleave"
@contextmenu.stop="() => {}" @contextmenu.stop="() => {}"
> >
<template v-if="!self"> <template v-if="!self">
@ -20,11 +18,11 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
import { toUnicode as decodePunycode } from 'punycode/'; import { toUnicode as decodePunycode } from 'punycode/';
import { url as local } from '@/config'; import { url as local } from '@/config';
import { isTouchUsing } from '@/scripts/touch';
import * as os from '@/os'; import * as os from '@/os';
import { useTooltip } from '@/scripts/use-tooltip';
export default defineComponent({ export default defineComponent({
props: { props: {
@ -35,74 +33,36 @@ export default defineComponent({
rel: { rel: {
type: String, type: String,
required: false, required: false,
default: null,
} }
}, },
data() { setup(props) {
const self = this.url.startsWith(local); const self = props.url.startsWith(local);
const url = new URL(props.url);
const el = ref();
useTooltip(el, (showing) => {
os.popup(import('@/components/url-preview-popup.vue'), {
showing,
url: props.url,
source: el.value,
}, {}, 'closed');
});
return { return {
local, local,
schema: null as string | null, schema: url.protocol,
hostname: null as string | null, hostname: decodePunycode(url.hostname),
port: null as string | null, port: url.port,
pathname: null as string | null, pathname: decodeURIComponent(url.pathname),
query: null as string | null, query: decodeURIComponent(url.search),
hash: null as string | null, hash: decodeURIComponent(url.hash),
self: self, self: self,
attr: self ? 'to' : 'href', attr: self ? 'to' : 'href',
target: self ? null : '_blank', target: self ? null : '_blank',
showTimer: null, el,
hideTimer: null,
checkTimer: null,
close: null,
}; };
}, },
created() {
const url = new URL(this.url);
this.schema = url.protocol;
this.hostname = decodePunycode(url.hostname);
this.port = url.port;
this.pathname = decodeURIComponent(url.pathname);
this.query = decodeURIComponent(url.search);
this.hash = decodeURIComponent(url.hash);
},
methods: {
async showPreview() {
if (!document.body.contains(this.$el)) return;
if (this.close) return;
const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), {
url: this.url,
source: this.$el
});
this.close = () => {
dispose();
};
this.checkTimer = setInterval(() => {
if (!document.body.contains(this.$el)) this.closePreview();
}, 1000);
},
closePreview() {
if (this.close) {
clearInterval(this.checkTimer);
this.close();
this.close = null;
}
},
onMouseover() {
if (isTouchUsing) return;
clearTimeout(this.showTimer);
clearTimeout(this.hideTimer);
this.showTimer = setTimeout(this.showPreview, 500);
},
onMouseleave() {
if (isTouchUsing) return;
clearTimeout(this.showTimer);
clearTimeout(this.hideTimer);
this.hideTimer = setTimeout(this.closePreview, 500);
}
}
}); });
</script> </script>

View file

@ -105,6 +105,7 @@ export default defineComponent({
return { return {
previewable, previewable,
gallery, gallery,
pswpZIndex: os.claimZIndex('middle'),
}; };
}, },
}); });
@ -188,3 +189,11 @@ export default defineComponent({
} }
} }
</style> </style>
<style lang="scss">
.pswp {
//
//z-index: v-bind(pswpZIndex);
z-index: 2000000;
}
</style>

View file

@ -16,7 +16,13 @@
<template #headerLeft> <template #headerLeft>
<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button> <button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
</template> </template>
<div class="yrolvcoq"> <template #headerRight>
<button v-tooltip="$ts.showInPage" class="_button" @click="expand()"><i class="fas fa-expand-alt"></i></button>
<button v-tooltip="$ts.popout" class="_button" @click="popout()"><i class="fas fa-external-link-alt"></i></button>
<button class="_button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
</template>
<div class="yrolvcoq" :style="{ background: pageInfo?.bg }">
<MkStickyContainer> <MkStickyContainer>
<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
<component :is="component" v-bind="props" :ref="changePage"/> <component :is="component" v-bind="props" :ref="changePage"/>
@ -33,6 +39,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
import { resolve } from '@/router'; import { resolve } from '@/router';
import { url } from '@/config'; import { url } from '@/config';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import * as os from '@/os';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -139,6 +146,23 @@ export default defineComponent({
this.props = props; this.props = props;
}, },
menu(ev) {
os.popupMenu([{
icon: 'fas fa-external-link-alt',
text: this.$ts.openInNewTab,
action: () => {
window.open(this.url, '_blank');
this.$refs.window.close();
}
}, {
icon: 'fas fa-link',
text: this.$ts.copyLink,
action: () => {
copyToClipboard(this.url);
}
}], ev.currentTarget || ev.target);
},
back() { back() {
this.navigate(this.history.pop(), false); this.navigate(this.history.pop(), false);
}, },

View file

@ -284,7 +284,7 @@ export default defineComponent({
} }
&.asDrawer { &.asDrawer {
padding: 12px 0; padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
width: 100%; width: 100%;
> .item { > .item {

View file

@ -5,7 +5,12 @@
<MkError v-else-if="error" @retry="init()"/> <MkError v-else-if="error" @retry="init()"/>
<div v-else-if="empty" key="_empty_" class="empty"> <div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty"></slot> <slot name="empty">
<div class="_fullinfo">
<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
<div>{{ $ts.nothing }}</div>
</div>
</slot>
</div> </div>
<div v-else class="cxiknjgy"> <div v-else class="cxiknjgy">

View file

@ -414,6 +414,10 @@ export default defineComponent({
} }
} }
> .left {
min-width: 16px;
}
> .title { > .title {
flex: 1; flex: 1;
position: relative; position: relative;
@ -421,7 +425,6 @@ export default defineComponent({
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: center;
cursor: move; cursor: move;
} }
} }

View file

@ -1,28 +1,26 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<div class="_section"> <div class="_formRoot">
<div class="_content"> <MkInput v-model="name" class="_formBlock">
<MkInput v-model="name"> <template #label>{{ $ts.name }}</template>
<template #label>{{ $ts.name }}</template> </MkInput>
</MkInput>
<MkTextarea v-model="description"> <MkTextarea v-model="description" class="_formBlock">
<template #label>{{ $ts.description }}</template> <template #label>{{ $ts.description }}</template>
</MkTextarea> </MkTextarea>
<div class="banner"> <div class="banner">
<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton> <MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
<div v-else-if="bannerUrl"> <div v-else-if="bannerUrl">
<img :src="bannerUrl" style="width: 100%;"/> <img :src="bannerUrl" style="width: 100%;"/>
<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton> <MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
</div>
</div> </div>
</div> </div>
<div class="_footer"> <div class="_formBlock">
<MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton> <MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton>
</div> </div>
</div> </div>
</div> </MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -51,9 +49,11 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.channelId ? { [symbols.PAGE_INFO]: computed(() => this.channelId ? {
title: this.$ts._channel.edit, title: this.$ts._channel.edit,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
} : { } : {
title: this.$ts._channel.create, title: this.$ts._channel.create,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
}), }),
channel: null, channel: null,
name: null, name: null,

View file

@ -1,29 +1,31 @@
<template> <template>
<div v-if="channel" class="_section"> <MkSpacer :content-max="700">
<div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }"> <div v-if="channel">
<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/> <div class="wpgynlbz _panel _gap" :class="{ hide: !showBanner }">
<button class="_button toggle" @click="() => showBanner = !showBanner"> <XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
<template v-if="showBanner"><i class="fas fa-angle-up"></i></template> <button class="_button toggle" @click="() => showBanner = !showBanner">
<template v-else><i class="fas fa-angle-down"></i></template> <template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
</button> <template v-else><i class="fas fa-angle-down"></i></template>
<div v-if="!showBanner" class="hideOverlay"> </button>
</div> <div v-if="!showBanner" class="hideOverlay">
<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner"> </div>
<div class="status"> <div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
<div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div> <div class="status">
<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div> <div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
</div>
<div class="fade"></div>
</div>
<div v-if="channel.description" class="description">
<Mfm :text="channel.description" :is-note="false" :i="$i"/>
</div> </div>
<div class="fade"></div>
</div>
<div v-if="channel.description" class="description">
<Mfm :text="channel.description" :is-note="false" :i="$i"/>
</div> </div>
<XPostForm v-if="$i" :channel="channel" class="post-form _panel _gap" fixed/>
<XTimeline :key="channelId" class="_gap" src="channel" :channel="channelId" @before="before" @after="after"/>
</div> </div>
</MkSpacer>
<XPostForm v-if="$i" :channel="channel" class="post-form _content _panel _gap" fixed/>
<XTimeline :key="channelId" class="_content _gap" src="channel" :channel="channelId" @before="before" @after="after"/>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -55,6 +57,12 @@ export default defineComponent({
[symbols.PAGE_INFO]: computed(() => this.channel ? { [symbols.PAGE_INFO]: computed(() => this.channel ? {
title: this.channel.name, title: this.channel.name,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
bg: 'var(--bg)',
actions: [...(this.$i && this.$i.id === this.channel.userId ? [{
icon: 'fas fa-cog',
text: this.$ts.edit,
handler: this.edit,
}] : [])],
} : null), } : null),
channel: null, channel: null,
showBanner: true, showBanner: true,
@ -79,8 +87,10 @@ export default defineComponent({
} }
}, },
created() { methods: {
edit() {
this.$router.push(`/channels/${this.channel.id}/edit`);
}
}, },
}); });
</script> </script>

View file

@ -1,58 +1,63 @@
<template> <template>
<div> <MkSpacer :content-max="700">
<div v-if="$i" class="_section" style="padding: 0;"> <div v-if="tab === 'featured'" class="_content grwlizim featured">
<MkTab v-model="tab" class="_content"> <MkPagination v-slot="{items}" :pagination="featuredPagination">
<option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
<option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option> </MkPagination>
<option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option>
</MkTab>
</div> </div>
<div v-else-if="tab === 'following'" class="_content grwlizim following">
<div class="_section"> <MkPagination v-slot="{items}" :pagination="followingPagination">
<div v-if="tab === 'featured'" class="_content grwlizim featured"> <MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
<MkPagination v-slot="{items}" :pagination="featuredPagination"> </MkPagination>
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination>
</div>
<div v-if="tab === 'following'" class="_content grwlizim following">
<MkPagination v-slot="{items}" :pagination="followingPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination>
</div>
<div v-if="tab === 'owned'" class="_content grwlizim owned">
<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination>
</div>
</div> </div>
</div> <div v-else-if="tab === 'owned'" class="_content grwlizim owned">
<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
</MkPagination>
</div>
</MkSpacer>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import MkChannelPreview from '@/components/channel-preview.vue'; import MkChannelPreview from '@/components/channel-preview.vue';
import MkPagination from '@/components/ui/pagination.vue'; import MkPagination from '@/components/ui/pagination.vue';
import MkButton from '@/components/ui/button.vue'; import MkButton from '@/components/ui/button.vue';
import MkTab from '@/components/tab.vue';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
export default defineComponent({ export default defineComponent({
components: { components: {
MkChannelPreview, MkPagination, MkButton, MkTab MkChannelPreview, MkPagination, MkButton,
}, },
data() { data() {
return { return {
[symbols.PAGE_INFO]: { [symbols.PAGE_INFO]: computed(() => ({
title: this.$ts.channel, title: this.$ts.channel,
icon: 'fas fa-satellite-dish', icon: 'fas fa-satellite-dish',
action: { bg: 'var(--bg)',
actions: [{
icon: 'fas fa-plus', icon: 'fas fa-plus',
handler: this.create text: this.$ts.create,
} handler: this.create,
}, }],
tabs: [{
active: this.tab === 'featured',
title: this.$ts._channel.featured,
icon: 'fas fa-fire-alt',
onClick: () => { this.tab = 'featured'; },
}, {
active: this.tab === 'following',
title: this.$ts._channel.following,
icon: 'fas fa-heart',
onClick: () => { this.tab = 'following'; },
}, {
active: this.tab === 'owned',
title: this.$ts._channel.owned,
icon: 'fas fa-edit',
onClick: () => { this.tab = 'owned'; },
},]
})),
tab: 'featured', tab: 'featured',
featuredPagination: { featuredPagination: {
endpoint: 'channels/featured', endpoint: 'channels/featured',

View file

@ -77,13 +77,6 @@
<FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch> <FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
</FormGroup> </FormGroup>
<FormSelect v-model="chatOpenBehavior" class="_formBlock">
<template #label>{{ $ts.chatOpenBehavior }}</template>
<option value="page">{{ $ts.showInPage }}</option>
<option value="window">{{ $ts.openInWindow }}</option>
<option value="popout">{{ $ts.popout }}</option>
</FormSelect>
<FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink> <FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink>
<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink> <FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
@ -149,7 +142,6 @@ export default defineComponent({
disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'), disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'), showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
defaultSideView: defaultStore.makeGetterSetter('defaultSideView'), defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
instanceTicker: defaultStore.makeGetterSetter('instanceTicker'), instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'), enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'), useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),

View file

@ -1,4 +1,4 @@
import { Ref, ref, watch } from 'vue'; import { Ref, ref, watch, onUnmounted } from 'vue';
export function useTooltip( export function useTooltip(
elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>, elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
@ -18,6 +18,9 @@ export function useTooltip(
const open = () => { const open = () => {
close(); close();
if (!isHovering) return; if (!isHovering) return;
if (elRef.value == null) return;
const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el;
if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
const showing = ref(true); const showing = ref(true);
onShow(showing); onShow(showing);
@ -69,9 +72,14 @@ export function useTooltip(
el.addEventListener('mouseleave', onMouseleave, { passive: true }); el.addEventListener('mouseleave', onMouseleave, { passive: true });
el.addEventListener('touchstart', onTouchstart, { passive: true }); el.addEventListener('touchstart', onTouchstart, { passive: true });
el.addEventListener('touchend', onTouchend, { passive: true }); el.addEventListener('touchend', onTouchend, { passive: true });
el.addEventListener('click', close, { passive: true });
} }
}, { }, {
immediate: true, immediate: true,
flush: 'post', flush: 'post',
}); });
onUnmounted(() => {
close();
});
} }

View file

@ -245,7 +245,6 @@ export class ColdDeviceStorage {
lightTheme: require('@/themes/l-light.json5') as Theme, lightTheme: require('@/themes/l-light.json5') as Theme,
darkTheme: require('@/themes/d-dark.json5') as Theme, darkTheme: require('@/themes/d-dark.json5') as Theme,
syncDeviceDarkMode: true, syncDeviceDarkMode: true,
chatOpenBehavior: 'page' as 'page' | 'window' | 'popout',
plugins: [] as Plugin[], plugins: [] as Plugin[],
mediaVolume: 0.5, mediaVolume: 0.5,
sound_masterVolume: 0.3, sound_masterVolume: 0.3,

View file

@ -3,7 +3,6 @@
*/ */
declare var self: ServiceWorkerGlobalScope; declare var self: ServiceWorkerGlobalScope;
import { getNoteSummary } from '@/scripts/get-note-summary';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
function getUserName(user: misskey.entities.User): string { function getUserName(user: misskey.entities.User): string {
@ -26,37 +25,37 @@ export default async function(type, data, i18n): Promise<[string, NotificationOp
switch (data.type) { switch (data.type) {
case 'mention': case 'mention':
return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), { return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];
case 'reply': case 'reply':
return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), { return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];
case 'renote': case 'renote':
return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), { return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];
case 'quote': case 'quote':
return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), { return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];
case 'reaction': case 'reaction':
return [`${data.reaction} ${getUserName(data.user)}`, { return [`${data.reaction} ${getUserName(data.user)}`, {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];
case 'pollVote': case 'pollVote':
return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), { return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), {
body: getNoteSummary(data.note, i18n.locale), body: data.note.text,
icon: data.user.avatarUrl icon: data.user.avatarUrl
}]; }];