mirror of
https://github.com/paricafe/misskey.git
synced 2024-11-27 23:06:44 -06:00
Compare commits
10 commits
0145477c96
...
5bc98ce733
Author | SHA1 | Date | |
---|---|---|---|
|
5bc98ce733 | ||
|
a4673784ef | ||
|
949bf0543e | ||
|
35c4989527 | ||
|
6e35b9378a | ||
|
600a978f22 | ||
|
ae1d0b08eb | ||
|
a77ad7a16b | ||
|
00301ed04f | ||
|
d91a1be562 |
8 changed files with 359 additions and 353 deletions
2
.github/workflows/storybook.yml
vendored
2
.github/workflows/storybook.yml
vendored
|
@ -15,6 +15,8 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
# chromatic is not likely to be available for fork repositories, so we disable for fork repositories.
|
||||||
|
if: github.repository == 'misskey-dev/misskey'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -4,10 +4,10 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
-
|
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
-
|
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )
|
||||||
|
|
||||||
|
|
||||||
## 2024.11.0
|
## 2024.11.0
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2024.11.0-pari.30",
|
"version": "2024.11.0-pari.31",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -593,7 +593,10 @@ export class ClientServerService {
|
||||||
reply.header('X-Robots-Tag', 'noai');
|
reply.header('X-Robots-Tag', 'noai');
|
||||||
}
|
}
|
||||||
|
|
||||||
const _user = await this.userEntityService.pack(user);
|
const _user = await this.userEntityService.pack(user, null, {
|
||||||
|
schema: 'UserDetailed',
|
||||||
|
userProfile: profile,
|
||||||
|
});
|
||||||
|
|
||||||
return await reply.view('user', {
|
return await reply.view('user', {
|
||||||
user, profile, me,
|
user, profile, me,
|
||||||
|
|
|
@ -13,7 +13,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender || defaultStore.state.enableRenderingOptimization }]"
|
:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender || defaultStore.state.enableRenderingOptimization }]"
|
||||||
:tabindex="isDeleted ? '-1' : '0'"
|
:tabindex="isDeleted ? '-1' : '0'"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div v-if="collapsedUnexpectedLangs && isUnexpectedLanguage && !languageExpanded && !isRenote" :class="$style.collapsedLanguage">
|
<div v-if="collapsedUnexpectedLangs && isUnexpectedLanguage && !languageExpanded && !isRenote" :class="$style.collapsedLanguage">
|
||||||
<MkAvatar :class="$style.collapsedLanguageAvatar" :user="appearNote.user" link preview/>
|
<MkAvatar :class="$style.collapsedLanguageAvatar" :user="appearNote.user" link preview/>
|
||||||
<Mfm
|
<Mfm
|
||||||
|
@ -27,151 +26,151 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div v-if="appearNote.reply && inReplyToCollapsed && !isRenote" :class="$style.collapsedInReplyTo">
|
<div v-if="appearNote.reply && inReplyToCollapsed && !isRenote" :class="$style.collapsedInReplyTo">
|
||||||
<MkAvatar :class="$style.collapsedInReplyToAvatar" :user="appearNote.reply.user" link preview/>
|
<MkAvatar :class="$style.collapsedInReplyToAvatar" :user="appearNote.reply.user" link preview/>
|
||||||
<Mfm :text="getNoteSummary(appearNote.reply)" :plain="true" :nowrap="true" :author="appearNote.reply.user" :nyaize="'respect'" :class="$style.collapsedInReplyToText" @click.stop="inReplyToCollapsed = false"/>
|
<Mfm :text="getNoteSummary(appearNote.reply)" :plain="true" :nowrap="true" :author="appearNote.reply.user" :nyaize="'respect'" :class="$style.collapsedInReplyToText" @click.stop="inReplyToCollapsed = false"/>
|
||||||
</div>
|
|
||||||
<MkNoteSub v-if="appearNote.reply && !renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
|
|
||||||
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
|
||||||
<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
|
||||||
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
|
||||||
<div v-if="isRenote" :class="$style.renote">
|
|
||||||
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
|
|
||||||
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
|
||||||
<i class="ti ti-repeat" style="margin-right: 4px;"></i>
|
|
||||||
<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
|
|
||||||
<template #user>
|
|
||||||
<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)">
|
|
||||||
<MkUserName :user="note.user"/>
|
|
||||||
</MkA>
|
|
||||||
</template>
|
|
||||||
</I18n>
|
|
||||||
<div :class="$style.renoteInfo">
|
|
||||||
<button ref="renoteTime" :class="$style.renoteTime" class="_button" @mousedown.prevent="showRenoteMenu()">
|
|
||||||
<i class="ti ti-dots" :class="$style.renoteMenu"></i>
|
|
||||||
<MkTime :time="note.createdAt"/>
|
|
||||||
</button>
|
|
||||||
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
|
||||||
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
|
||||||
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
|
||||||
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
|
||||||
</span>
|
|
||||||
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
|
||||||
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<MkNoteSub v-if="appearNote.reply && !renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
|
||||||
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
|
||||||
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
|
||||||
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click.stop="renoteCollapsed = false; inReplyToCollapsed = false"/>
|
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
|
||||||
</div>
|
<div v-if="isRenote" :class="$style.renote">
|
||||||
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
|
||||||
<div style="display: flex; padding-bottom: 10px;">
|
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
|
||||||
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
<i class="ti ti-repeat" style="margin-right: 4px;"></i>
|
||||||
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
|
||||||
<div :class="$style.main">
|
<template #user>
|
||||||
<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
|
<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)">
|
||||||
</div>
|
<MkUserName :user="note.user"/>
|
||||||
</div>
|
</MkA>
|
||||||
<div :class="[{ [$style.noteClickToOpen]: defaultStore.state.noteClickToOpen }]" @click.stop="defaultStore.state.noteClickToOpen ? noteClickToOpen(appearNote.id) : undefined">
|
</template>
|
||||||
<div style="container-type: inline-size;">
|
</I18n>
|
||||||
<p v-if="appearNote.cw != null" :class="$style.cw">
|
<div :class="$style.renoteInfo">
|
||||||
<Mfm
|
<button ref="renoteTime" :class="$style.renoteTime" class="_button" @mousedown.prevent="showRenoteMenu()">
|
||||||
v-if="appearNote.cw != ''"
|
<i class="ti ti-dots" :class="$style.renoteMenu"></i>
|
||||||
:text="appearNote.cw"
|
<MkTime :time="note.createdAt"/>
|
||||||
:author="appearNote.user"
|
</button>
|
||||||
:nyaize="'respect'"
|
<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
|
||||||
:enableEmojiMenu="true"
|
<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
|
||||||
:enableEmojiMenuReaction="true"
|
<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
|
||||||
/>
|
<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
|
||||||
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
</span>
|
||||||
</p>
|
<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
|
||||||
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ti ti-device-tv"></i></span>
|
||||||
<div :class="$style.text">
|
</div>
|
||||||
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
</div>
|
||||||
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
|
||||||
|
<MkAvatar :class="$style.collapsedRenoteTargetAvatar" :user="appearNote.user" link preview/>
|
||||||
|
<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :nyaize="'respect'" :class="$style.collapsedRenoteTargetText" @click.stop="renoteCollapsed = false; inReplyToCollapsed = false"/>
|
||||||
|
</div>
|
||||||
|
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||||
|
<div style="display: flex; padding-bottom: 10px;">
|
||||||
|
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
|
||||||
|
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
||||||
|
<div :class="$style.main">
|
||||||
|
<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div :class="[{ [$style.noteClickToOpen]: defaultStore.state.noteClickToOpen }]" @click.stop="defaultStore.state.noteClickToOpen ? noteClickToOpen(appearNote.id) : undefined">
|
||||||
|
<div style="container-type: inline-size;">
|
||||||
|
<p v-if="appearNote.cw != null" :class="$style.cw">
|
||||||
<Mfm
|
<Mfm
|
||||||
v-if="appearNote.text"
|
v-if="appearNote.cw != ''"
|
||||||
:parsedNodes="parsed"
|
:text="appearNote.cw"
|
||||||
:text="appearNote.text"
|
|
||||||
:author="appearNote.user"
|
:author="appearNote.user"
|
||||||
:nyaize="'respect'"
|
:nyaize="'respect'"
|
||||||
:emojiUrls="appearNote.emojis"
|
|
||||||
:enableEmojiMenu="true"
|
:enableEmojiMenu="true"
|
||||||
:enableEmojiMenuReaction="true"
|
:enableEmojiMenuReaction="true"
|
||||||
/>
|
/>
|
||||||
<div v-if="defaultStore.state.autoTranslateButton && $i.policies.canUseTranslator && appearNote.text && isUnexpectedLanguage" style="padding-top: 5px; color: var(--MI_THEME-accent);">
|
<MkCwButton v-model="showContent" :text="appearNote.text" :renote="appearNote.renote" :files="appearNote.files" :poll="appearNote.poll" style="margin: 4px 0;" @click.stop/>
|
||||||
<button v-if="!(translating || translation)" ref="translateButton" class="_button" @click.stop="translate()"><i class="ti ti-language-hiragana"></i>{{ i18n.ts.translate }}</button>
|
</p>
|
||||||
<button v-else class="_button" @click.stop="translation= null">{{ i18n.ts.close }}</button>
|
<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
|
||||||
</div>
|
<div :class="$style.text">
|
||||||
<div v-if="translating || translation" :class="$style.translation">
|
<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
|
||||||
<MkLoading v-if="translating" mini/>
|
<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
|
||||||
<div v-else-if="translation">
|
<Mfm
|
||||||
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
v-if="appearNote.text"
|
||||||
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
:parsedNodes="parsed"
|
||||||
|
:text="appearNote.text"
|
||||||
|
:author="appearNote.user"
|
||||||
|
:nyaize="'respect'"
|
||||||
|
:emojiUrls="appearNote.emojis"
|
||||||
|
:enableEmojiMenu="true"
|
||||||
|
:enableEmojiMenuReaction="true"
|
||||||
|
/>
|
||||||
|
<div v-if="defaultStore.state.autoTranslateButton && $i.policies.canUseTranslator && appearNote.text && isUnexpectedLanguage" style="padding-top: 5px; color: var(--MI_THEME-accent);">
|
||||||
|
<button v-if="!(translating || translation)" ref="translateButton" class="_button" @click.stop="translate()"><i class="ti ti-language-hiragana"></i>{{ i18n.ts.translate }}</button>
|
||||||
|
<button v-else class="_button" @click.stop="translation= null">{{ i18n.ts.close }}</button>
|
||||||
|
</div>
|
||||||
|
<div v-if="translating || translation" :class="$style.translation">
|
||||||
|
<MkLoading v-if="translating" mini/>
|
||||||
|
<div v-else-if="translation">
|
||||||
|
<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
|
||||||
|
<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="appearNote.files && appearNote.files.length > 0">
|
||||||
|
<MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/>
|
||||||
|
</div>
|
||||||
|
<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
|
||||||
|
<div v-if="isEnabledUrlPreview">
|
||||||
|
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
|
||||||
|
</div>
|
||||||
|
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" @click.stop/></div>
|
||||||
|
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click.stop="collapsed = false">
|
||||||
|
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
|
||||||
|
</button>
|
||||||
|
<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click.stop="collapsed = true">
|
||||||
|
<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="appearNote.files && appearNote.files.length > 0">
|
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
||||||
<MkMediaList ref="galleryEl" :mediaList="appearNote.files" @click.stop/>
|
|
||||||
</div>
|
|
||||||
<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
|
|
||||||
<div v-if="isEnabledUrlPreview">
|
|
||||||
<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
|
|
||||||
</div>
|
|
||||||
<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" @click.stop/></div>
|
|
||||||
<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click.stop="collapsed = false">
|
|
||||||
<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
|
|
||||||
</button>
|
|
||||||
<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click.stop="collapsed = true">
|
|
||||||
<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
|
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly' && !disableReactionsViewer" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
||||||
|
<template #more>
|
||||||
|
<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
|
||||||
|
</template>
|
||||||
|
</MkReactionsViewer>
|
||||||
|
<footer :class="$style.footer">
|
||||||
|
<button :class="$style.footerButton" class="_button" @click.stop="reply()">
|
||||||
|
<i class="ti ti-arrow-back-up"></i>
|
||||||
|
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="canRenote"
|
||||||
|
ref="renoteButton"
|
||||||
|
:class="$style.footerButton"
|
||||||
|
class="_button"
|
||||||
|
@click.stop
|
||||||
|
@mousedown.prevent="renote()"
|
||||||
|
>
|
||||||
|
<i class="ti ti-repeat"></i>
|
||||||
|
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
||||||
|
</button>
|
||||||
|
<button v-else :class="$style.footerButton" class="_button" disabled>
|
||||||
|
<i class="ti ti-ban"></i>
|
||||||
|
</button>
|
||||||
|
<button v-if="defaultStore.state.enableFallbackReactButton && appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly' && !disableReactionsViewer" ref="likeButton" :class="$style.footerButton" class="_button" @click.stop="like()">
|
||||||
|
<i class="ti ti-heart"></i>
|
||||||
|
</button>
|
||||||
|
<button ref="reactButton" :class="$style.footerButton" class="_button" @click.stop="toggleReact()">
|
||||||
|
<i v-if="(appearNote.reactionAcceptance === 'likeOnly' || disableReactionsViewer) && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
|
||||||
|
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
|
||||||
|
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || disableReactionsViewer" class="ti ti-heart"></i>
|
||||||
|
<i v-else class="ti ti-mood-plus"></i>
|
||||||
|
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount || disableReactionsViewer) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||||
|
</button>
|
||||||
|
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
|
||||||
|
<i class="ti ti-paperclip"></i>
|
||||||
|
</button>
|
||||||
|
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
|
||||||
|
<i class="ti ti-dots"></i>
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<MkReactionsViewer v-if="appearNote.reactionAcceptance !== 'likeOnly' && !disableReactionsViewer" :note="appearNote" :maxNumber="16" @mockUpdateMyReaction="emitUpdReaction">
|
</article>
|
||||||
<template #more>
|
</template>
|
||||||
<MkA :to="`/notes/${appearNote.id}/reactions`" :class="[$style.reactionOmitted]">{{ i18n.ts.more }}</MkA>
|
|
||||||
</template>
|
|
||||||
</MkReactionsViewer>
|
|
||||||
<footer :class="$style.footer">
|
|
||||||
<button :class="$style.footerButton" class="_button" @click.stop="reply()">
|
|
||||||
<i class="ti ti-arrow-back-up"></i>
|
|
||||||
<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.repliesCount) }}</p>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
v-if="canRenote"
|
|
||||||
ref="renoteButton"
|
|
||||||
:class="$style.footerButton"
|
|
||||||
class="_button"
|
|
||||||
@click.stop
|
|
||||||
@mousedown.prevent="renote()"
|
|
||||||
>
|
|
||||||
<i class="ti ti-repeat"></i>
|
|
||||||
<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.renoteCount) }}</p>
|
|
||||||
</button>
|
|
||||||
<button v-else :class="$style.footerButton" class="_button" disabled>
|
|
||||||
<i class="ti ti-ban"></i>
|
|
||||||
</button>
|
|
||||||
<button v-if="defaultStore.state.enableFallbackReactButton && appearNote.myReaction == null && appearNote.reactionAcceptance !== 'likeOnly' && !disableReactionsViewer" ref="likeButton" :class="$style.footerButton" class="_button" @click.stop="like()">
|
|
||||||
<i class="ti ti-heart"></i>
|
|
||||||
</button>
|
|
||||||
<button ref="reactButton" :class="$style.footerButton" class="_button" @click.stop="toggleReact()">
|
|
||||||
<i v-if="(appearNote.reactionAcceptance === 'likeOnly' || disableReactionsViewer) && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i>
|
|
||||||
<i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i>
|
|
||||||
<i v-else-if="appearNote.reactionAcceptance === 'likeOnly' || disableReactionsViewer" class="ti ti-heart"></i>
|
|
||||||
<i v-else class="ti ti-mood-plus"></i>
|
|
||||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount || disableReactionsViewer) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
|
||||||
</button>
|
|
||||||
<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
|
|
||||||
<i class="ti ti-paperclip"></i>
|
|
||||||
</button>
|
|
||||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
|
|
||||||
<i class="ti ti-dots"></i>
|
|
||||||
</button>
|
|
||||||
</footer>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
|
<div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
|
||||||
<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
|
<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
|
||||||
|
@ -328,16 +327,16 @@ const disableReactionsViewer = ref(defaultStore.reactiveState.disableReactionsVi
|
||||||
|
|
||||||
const collapsedUnexpectedLangs = ref(defaultStore.reactiveState.collapsedUnexpectedLangs);
|
const collapsedUnexpectedLangs = ref(defaultStore.reactiveState.collapsedUnexpectedLangs);
|
||||||
const expectedLangs = computed(() => new Set([
|
const expectedLangs = computed(() => new Set([
|
||||||
(miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2),
|
(miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2),
|
||||||
navigator.language.slice(0, 2)
|
navigator.language.slice(0, 2),
|
||||||
]));
|
]));
|
||||||
const noteLanguage = computed(() => {
|
const noteLanguage = computed(() => {
|
||||||
if (!appearNote.value.text || appearNote.value.text.length < 10) return '';
|
if (!appearNote.value.text || appearNote.value.text.length < 10) return '';
|
||||||
return detectLanguage(appearNote.value.text);
|
return detectLanguage(appearNote.value.text);
|
||||||
});
|
});
|
||||||
const isUnexpectedLanguage = computed(() => {
|
const isUnexpectedLanguage = computed(() => {
|
||||||
const lang = noteLanguage.value;
|
const lang = noteLanguage.value;
|
||||||
return lang !== '' && !expectedLangs.value.has(lang);
|
return lang !== '' && !expectedLangs.value.has(lang);
|
||||||
});
|
});
|
||||||
|
|
||||||
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
|
@ -806,7 +805,7 @@ function emitUpdReaction(emoji: string, delta: number) {
|
||||||
|
|
||||||
.skipRender {
|
.skipRender {
|
||||||
content-visibility: auto;
|
content-visibility: auto;
|
||||||
contain-intrinsic-size: 0 250px;
|
contain-intrinsic-size: 0 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tip {
|
.tip {
|
||||||
|
|
|
@ -36,12 +36,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
|
import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
|
||||||
|
import { scroll } from '@@/js/scroll.js';
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
import { scroll } from '@@/js/scroll.js';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
@ -51,10 +53,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
|
import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js';
|
||||||
import { deviceKind } from '@/scripts/device-kind.js';
|
import { deviceKind } from '@/scripts/device-kind.js';
|
||||||
import { deepMerge } from '@/scripts/merge.js';
|
import { deepMerge } from '@/scripts/merge.js';
|
||||||
import type { MenuItem } from '@/types/menu.js';
|
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
|
||||||
|
|
||||||
provide('shouldOmitHeaderTitle', true);
|
provide('shouldOmitHeaderTitle', true);
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</component>
|
</component>
|
||||||
</template>
|
</template>
|
||||||
<div :class="$style.divider"></div>
|
<div :class="$style.divider"></div>
|
||||||
<MkA v-if="$i.isAdmin || $i.isModerator" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin">
|
<MkA v-if="$i != null && ($i.isAdmin || $i.isModerator)" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin">
|
||||||
<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
|
<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<button class="_button" :class="$style.item" @click="more">
|
<button class="_button" :class="$style.item" @click="more">
|
||||||
|
@ -48,10 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</MkA>
|
</MkA>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.bottom">
|
<div :class="$style.bottom">
|
||||||
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="os.post">
|
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
|
||||||
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
|
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
|
<button v-if="$i != null" v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
|
||||||
<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
|
<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -83,8 +83,12 @@ import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||||
|
|
||||||
const iconOnly = ref(false);
|
const forceIconOnly = ref(window.innerWidth <= 1279);
|
||||||
|
const iconOnly = computed(() => {
|
||||||
|
return forceIconOnly.value || (defaultStore.reactiveState.menuDisplay.value === 'sideIcon');
|
||||||
|
});
|
||||||
|
|
||||||
const menu = computed(() => defaultStore.state.menu);
|
const menu = computed(() => defaultStore.state.menu);
|
||||||
const otherMenuItemIndicated = computed(() => {
|
const otherMenuItemIndicated = computed(() => {
|
||||||
|
@ -95,14 +99,10 @@ const otherMenuItemIndicated = computed(() => {
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
const forceIconOnly = window.innerWidth <= 1279;
|
|
||||||
|
|
||||||
function calcViewState() {
|
function calcViewState() {
|
||||||
iconOnly.value = forceIconOnly || (defaultStore.state.menuDisplay === 'sideIcon');
|
forceIconOnly.value = window.innerWidth <= 1279;
|
||||||
}
|
}
|
||||||
|
|
||||||
calcViewState();
|
|
||||||
|
|
||||||
window.addEventListener('resize', calcViewState);
|
window.addEventListener('resize', calcViewState);
|
||||||
|
|
||||||
watch(defaultStore.reactiveState.menuDisplay, () => {
|
watch(defaultStore.reactiveState.menuDisplay, () => {
|
||||||
|
@ -120,8 +120,10 @@ function openAccountMenu(ev: MouseEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function more(ev: MouseEvent) {
|
function more(ev: MouseEvent) {
|
||||||
|
const target = getHTMLElementOrNull(ev.currentTarget ?? ev.target);
|
||||||
|
if (!target) return;
|
||||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||||
src: ev.currentTarget ?? ev.target,
|
src: target,
|
||||||
}, {
|
}, {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,238 +16,238 @@ const STATIC_CACHE_NAME = `misskey-static-${_VERSION_}`;
|
||||||
const PATHS_TO_CACHE = ['/assets/', '/static-assets/', '/emoji/', '/twemoji/', '/fluent-emoji/', '/vite/'];
|
const PATHS_TO_CACHE = ['/assets/', '/static-assets/', '/emoji/', '/twemoji/', '/fluent-emoji/', '/vite/'];
|
||||||
|
|
||||||
async function cacheWithFallback(cache, paths) {
|
async function cacheWithFallback(cache, paths) {
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
try {
|
try {
|
||||||
await cache.add(new Request(path, { credentials: 'same-origin' }));
|
await cache.add(new Request(path, { credentials: 'same-origin' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.addEventListener('install', (ev) => {
|
globalThis.addEventListener('install', (ev) => {
|
||||||
ev.waitUntil((async () => {
|
ev.waitUntil((async () => {
|
||||||
const cache = await caches.open(STATIC_CACHE_NAME);
|
const cache = await caches.open(STATIC_CACHE_NAME);
|
||||||
await cacheWithFallback(cache, PATHS_TO_CACHE);
|
await cacheWithFallback(cache, PATHS_TO_CACHE);
|
||||||
await globalThis.skipWaiting();
|
await globalThis.skipWaiting();
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('activate', (ev) => {
|
globalThis.addEventListener('activate', (ev) => {
|
||||||
ev.waitUntil(
|
ev.waitUntil(
|
||||||
caches.keys()
|
caches.keys()
|
||||||
.then((cacheNames) => Promise.all(
|
.then((cacheNames) => Promise.all(
|
||||||
cacheNames
|
cacheNames
|
||||||
.filter((v) => v !== STATIC_CACHE_NAME && v !== swLang.cacheName)
|
.filter((v) => v !== STATIC_CACHE_NAME && v !== swLang.cacheName)
|
||||||
.map((name) => caches.delete(name)),
|
.map((name) => caches.delete(name)),
|
||||||
))
|
))
|
||||||
.then(() => globalThis.clients.claim()),
|
.then(() => globalThis.clients.claim()),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function offlineContentHTML() {
|
async function offlineContentHTML() {
|
||||||
const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
|
||||||
const messages = {
|
const messages = {
|
||||||
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
title: i18n.ts?._offlineScreen.title ?? 'Offline - Could not connect to server',
|
||||||
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
header: i18n.ts?._offlineScreen.header ?? 'Could not connect to server',
|
||||||
reload: i18n.ts?.reload ?? 'Reload',
|
reload: i18n.ts?.reload ?? 'Reload',
|
||||||
};
|
};
|
||||||
|
|
||||||
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1" name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#ff82ab;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#fac5eb}</style></head><body><svg class="icon" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none" stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(true)}</script></body></html>`;
|
return `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1" name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#ff82ab;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#fac5eb}</style></head><body><svg class="icon" fill="none" height="24" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none" stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(true)}</script></body></html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalThis.addEventListener('fetch', (ev) => {
|
globalThis.addEventListener('fetch', (ev) => {
|
||||||
const shouldCache = PATHS_TO_CACHE.some((path) => ev.request.url.includes(path));
|
const shouldCache = PATHS_TO_CACHE.some((path) => ev.request.url.includes(path));
|
||||||
|
|
||||||
if (shouldCache) {
|
if (shouldCache) {
|
||||||
ev.respondWith(
|
ev.respondWith(
|
||||||
caches.match(ev.request)
|
caches.match(ev.request)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response) return response;
|
if (response) return response;
|
||||||
|
|
||||||
return fetch(ev.request).then((response) => {
|
return fetch(ev.request).then((response) => {
|
||||||
if (!response || response.status !== 200 || response.type !== 'basic') return response;
|
if (response.status !== 200 || response.type !== 'basic') return response;
|
||||||
const responseToCache = response.clone();
|
const responseToCache = response.clone();
|
||||||
caches.open(STATIC_CACHE_NAME)
|
caches.open(STATIC_CACHE_NAME)
|
||||||
.then((cache) => {
|
.then((cache) => {
|
||||||
cache.put(ev.request, responseToCache);
|
cache.put(ev.request, responseToCache);
|
||||||
});
|
});
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let isHTMLRequest = false;
|
let isHTMLRequest = false;
|
||||||
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
if (ev.request.headers.get('sec-fetch-dest') === 'document') {
|
||||||
isHTMLRequest = true;
|
isHTMLRequest = true;
|
||||||
} else if (ev.request.headers.get('accept')?.includes('/html')) {
|
} else if (ev.request.headers.get('accept')?.includes('/html')) {
|
||||||
isHTMLRequest = true;
|
isHTMLRequest = true;
|
||||||
} else if (ev.request.url.endsWith('/')) {
|
} else if (ev.request.url.endsWith('/')) {
|
||||||
isHTMLRequest = true;
|
isHTMLRequest = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isHTMLRequest) return;
|
if (!isHTMLRequest) return;
|
||||||
ev.respondWith(
|
ev.respondWith(
|
||||||
fetch(ev.request)
|
fetch(ev.request)
|
||||||
.catch(async () => {
|
.catch(async () => {
|
||||||
const html = await offlineContentHTML();
|
const html = await offlineContentHTML();
|
||||||
return new Response(html, {
|
return new Response(html, {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'text/html',
|
'content-type': 'text/html',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('push', (ev) => {
|
globalThis.addEventListener('push', (ev) => {
|
||||||
ev.waitUntil(globalThis.clients.matchAll({
|
ev.waitUntil(globalThis.clients.matchAll({
|
||||||
includeUncontrolled: true,
|
includeUncontrolled: true,
|
||||||
type: 'window',
|
type: 'window',
|
||||||
}).then(async () => {
|
}).then(async () => {
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.data?.json();
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'notification':
|
case 'notification':
|
||||||
case 'unreadAntennaNote':
|
case 'unreadAntennaNote':
|
||||||
if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
|
if (Date.now() - data.dateTime > 1000 * 60 * 60 * 24) break;
|
||||||
|
|
||||||
return createNotification(data);
|
return createNotification(data);
|
||||||
case 'readAllNotifications':
|
case 'readAllNotifications':
|
||||||
await globalThis.registration.getNotifications()
|
await globalThis.registration.getNotifications()
|
||||||
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
await createEmptyNotification();
|
await createEmptyNotification();
|
||||||
return;
|
return;
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEventMap['notificationclick']) => {
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (_DEV_) {
|
if (_DEV_) {
|
||||||
console.log('notificationclick', ev.action, ev.notification.data);
|
console.log('notificationclick', ev.action, ev.notification.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { action, notification } = ev;
|
const { action, notification } = ev;
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = notification.data ?? {};
|
||||||
const { userId: loginId } = data;
|
const { userId: loginId } = data;
|
||||||
let client: WindowClient | null = null;
|
let client: WindowClient | null = null;
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
case 'notification':
|
case 'notification':
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'follow':
|
case 'follow':
|
||||||
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
if ('userId' in data.body) await swos.api('following/create', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
case 'showUser':
|
case 'showUser':
|
||||||
if ('user' in data.body) client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
if ('user' in data.body) client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
||||||
break;
|
break;
|
||||||
case 'reply':
|
case 'reply':
|
||||||
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
|
||||||
break;
|
break;
|
||||||
case 'renote':
|
case 'renote':
|
||||||
if ('note' in data.body) await swos.api('notes/create', loginId, { renoteId: data.body.note.id });
|
if ('note' in data.body) await swos.api('notes/create', loginId, { renoteId: data.body.note.id });
|
||||||
break;
|
break;
|
||||||
case 'accept':
|
case 'accept':
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
|
await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'reject':
|
case 'reject':
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
|
await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'showFollowRequests':
|
case 'showFollowRequests':
|
||||||
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
switch (data.body.type) {
|
switch (data.body.type) {
|
||||||
case 'receiveFollowRequest':
|
case 'receiveFollowRequest':
|
||||||
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
||||||
break;
|
break;
|
||||||
case 'reaction':
|
case 'reaction':
|
||||||
client = await swos.openNote(data.body.note.id, loginId);
|
client = await swos.openNote(data.body.note.id, loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
if ('note' in data.body) {
|
if ('note' in data.body) {
|
||||||
client = await swos.openNote(data.body.note.id, loginId);
|
client = await swos.openNote(data.body.note.id, loginId);
|
||||||
} else if ('user' in data.body) {
|
} else if ('user' in data.body) {
|
||||||
client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
client = await swos.openUser(Misskey.acct.toString(data.body.user), loginId);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'unreadAntennaNote':
|
case 'unreadAntennaNote':
|
||||||
client = await swos.openAntenna(data.body.antenna.id, loginId);
|
client = await swos.openAntenna(data.body.antenna.id, loginId);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
switch (action) {
|
switch (action) {
|
||||||
case 'markAllAsRead':
|
case 'markAllAsRead':
|
||||||
await globalThis.registration.getNotifications()
|
await globalThis.registration.getNotifications()
|
||||||
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
.then((notifications) => notifications.forEach((n) => n.tag !== 'read_notification' && n.close()));
|
||||||
await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then((accounts) => {
|
await get<Pick<Misskey.entities.SignupResponse, 'id' | 'token'>[]>('accounts').then((accounts) => {
|
||||||
return Promise.all((accounts ?? []).map(async (account) => {
|
return Promise.all((accounts ?? []).map(async (account) => {
|
||||||
await swos.sendMarkAllAsRead(account.id);
|
await swos.sendMarkAllAsRead(account.id);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case 'settings':
|
case 'settings':
|
||||||
client = await swos.openClient('push', '/settings/notifications', loginId);
|
client = await swos.openClient('push', '/settings/notifications', loginId);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
client.focus();
|
client.focus();
|
||||||
}
|
}
|
||||||
if (data.type === 'notification') {
|
if (data.type === 'notification') {
|
||||||
await swos.sendMarkAllAsRead(loginId);
|
await swos.sendMarkAllAsRead(loginId);
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.close();
|
notification.close();
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
globalThis.addEventListener('notificationclose', (ev: ServiceWorkerGlobalScopeEventMap['notificationclose']) => {
|
||||||
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
const data: PushNotificationDataMap[keyof PushNotificationDataMap] = ev.notification.data;
|
||||||
|
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (data.type === 'notification') {
|
if (data.type === 'notification') {
|
||||||
await swos.sendMarkAllAsRead(data.userId);
|
await swos.sendMarkAllAsRead(data.userId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
||||||
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
globalThis.addEventListener('message', (ev: ServiceWorkerGlobalScopeEventMap['message']) => {
|
||||||
ev.waitUntil((async (): Promise<void> => {
|
ev.waitUntil((async (): Promise<void> => {
|
||||||
if (ev.data === 'clear') {
|
if (ev.data === 'clear') {
|
||||||
await caches.keys()
|
await caches.keys()
|
||||||
.then((cacheNames) => Promise.all(
|
.then((cacheNames) => Promise.all(
|
||||||
cacheNames.map((name) => caches.delete(name)),
|
cacheNames.map((name) => caches.delete(name)),
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof ev.data === 'object') {
|
if (typeof ev.data === 'object') {
|
||||||
const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
|
const otype = Object.prototype.toString.call(ev.data).slice(8, -1).toLowerCase();
|
||||||
|
|
||||||
if (otype === 'object') {
|
if (otype === 'object') {
|
||||||
if (ev.data.msg === 'initialize') {
|
if (ev.data.msg === 'initialize') {
|
||||||
swLang.setLang(ev.data.lang);
|
swLang.setLang(ev.data.lang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})());
|
})());
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue