Merge branch 'misskey-develop' into merge/2025-03-24
This commit is contained in:
commit
4ae26e6e18
113 changed files with 1303 additions and 1720 deletions
|
|
@ -29,7 +29,7 @@ import { fetchCustomEmojis } from '@/custom-emojis.js';
|
|||
import { prefer } from '@/preferences.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
export async function common(createVue: () => App<Element>) {
|
||||
export async function common(createVue: () => Promise<App<Element>>) {
|
||||
console.info(`Sharkey v${version}`);
|
||||
|
||||
if (_DEV_) {
|
||||
|
|
@ -255,7 +255,7 @@ export async function common(createVue: () => App<Element>) {
|
|||
await fetchCustomEmojis();
|
||||
} catch (err) { /* empty */ }
|
||||
|
||||
const app = createVue();
|
||||
const app = await createVue();
|
||||
|
||||
if (_DEV_) {
|
||||
app.config.performance = true;
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import { signout } from '@/signout.js';
|
|||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated, lastVersion } = await common(() => {
|
||||
const { isClientUpdated, lastVersion } = await common(async () => {
|
||||
let uiStyle = ui;
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
|
||||
|
|
@ -48,19 +48,16 @@ export async function mainBoot() {
|
|||
let rootComponent: Component;
|
||||
switch (uiStyle) {
|
||||
case 'zen':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/zen.vue'));
|
||||
rootComponent = await import('@/ui/zen.vue').then(x => x.default);
|
||||
break;
|
||||
case 'deck':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue'));
|
||||
rootComponent = await import('@/ui/deck.vue').then(x => x.default);
|
||||
break;
|
||||
case 'visitor':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue'));
|
||||
break;
|
||||
case 'classic':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue'));
|
||||
rootComponent = await import('@/ui/visitor.vue').then(x => x.default);
|
||||
break;
|
||||
default:
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue'));
|
||||
rootComponent = await import('@/ui/universal.vue').then(x => x.default);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,10 @@
|
|||
import { createApp, defineAsyncComponent } from 'vue';
|
||||
import { common } from './common.js';
|
||||
import { emojiPicker } from '@/utility/emoji-picker.js';
|
||||
import UiMinimum from '@/ui/minimum.vue';
|
||||
|
||||
export async function subBoot() {
|
||||
const { isClientUpdated } = await common(() => createApp(
|
||||
defineAsyncComponent(() => import('@/ui/minimum.vue')),
|
||||
));
|
||||
const { isClientUpdated } = await common(async () => createApp(UiMinimum));
|
||||
|
||||
emojiPicker.init();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,20 +2,18 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable import/no-default-export */
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
|
||||
import { HttpResponse, http } from 'msw';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { expect, userEvent, within } from '@storybook/test';
|
||||
import { channel } from '../../.storybook/fakes.js';
|
||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||
import MkChannelFollowButton from './MkChannelFollowButton.vue';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
|
|
|
|||
|
|
@ -2,18 +2,16 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
/* eslint-disable import/no-default-export */
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
|
||||
import { HttpResponse, http } from 'msw';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import { expect, userEvent, within } from '@storybook/test';
|
||||
import { commonHandlers } from '../../.storybook/mocks.js';
|
||||
import MkClickerGame from './MkClickerGame.vue';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
|
|
|
|||
|
|
@ -4,41 +4,45 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.root">
|
||||
<nav :class="$style.nav">
|
||||
<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
|
||||
<XNavFolder
|
||||
:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
|
||||
:parentFolder="folder"
|
||||
@move="move"
|
||||
@upload="upload"
|
||||
@removeFile="removeFile"
|
||||
@removeFolder="removeFolder"
|
||||
/>
|
||||
<template v-for="f in hierarchyFolders">
|
||||
<span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
||||
<MkStickyContainer>
|
||||
<template #header>
|
||||
<nav :class="$style.nav">
|
||||
<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
|
||||
<XNavFolder
|
||||
:folder="f"
|
||||
:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
|
||||
:parentFolder="folder"
|
||||
:class="[$style.navPathItem]"
|
||||
@move="move"
|
||||
@upload="upload"
|
||||
@removeFile="removeFile"
|
||||
@removeFolder="removeFolder"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
|
||||
</div>
|
||||
<div :class="$style.navMenu">
|
||||
<!-- "Search drive via alt text or file names" -->
|
||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" :placeholder="i18n.ts.driveSearchbarPlaceholder" @enter="fetch">
|
||||
<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<template v-for="f in hierarchyFolders">
|
||||
<span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
||||
<XNavFolder
|
||||
:folder="f"
|
||||
:parentFolder="folder"
|
||||
:class="[$style.navPathItem]"
|
||||
@move="move"
|
||||
@upload="upload"
|
||||
@removeFile="removeFile"
|
||||
@removeFolder="removeFolder"
|
||||
/>
|
||||
</template>
|
||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
|
||||
<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
|
||||
</div>
|
||||
<button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||
</div>
|
||||
</nav>
|
||||
<div :class="$style.navMenu">
|
||||
<!-- "Search drive via alt text or file names" -->
|
||||
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" :placeholder="i18n.ts.driveSearchbarPlaceholder" @enter="fetch">
|
||||
<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
|
||||
</MkInput>
|
||||
|
||||
<button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<div
|
||||
ref="main"
|
||||
:class="[$style.main, { [$style.uploading]: uploadings.length > 0, [$style.fetching]: fetching }]"
|
||||
|
|
@ -98,8 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkLoading v-if="fetching"/>
|
||||
</div>
|
||||
<div v-if="draghover" :class="$style.dropzone"></div>
|
||||
<input ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/>
|
||||
</div>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -118,6 +121,7 @@ import { i18n } from '@/i18n.js';
|
|||
import { uploadFile, uploads } from '@/utility/upload.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { chooseFileFromPc } from '@/utility/select-file.js';
|
||||
|
||||
const searchQuery = ref('');
|
||||
|
||||
|
|
@ -140,7 +144,6 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const loadMoreFiles = useTemplateRef('loadMoreFiles');
|
||||
const fileInput = useTemplateRef('fileInput');
|
||||
|
||||
const folder = ref<Misskey.entities.DriveFolder | null>(null);
|
||||
const files = ref<Misskey.entities.DriveFile[]>([]);
|
||||
|
|
@ -152,7 +155,6 @@ const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
|
|||
const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
|
||||
const uploadings = uploads;
|
||||
const connection = useStream().useChannel('drive');
|
||||
const keepOriginal = ref<boolean>(prefer.s.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
|
||||
|
||||
// ドロップされようとしているか
|
||||
const draghover = ref(false);
|
||||
|
|
@ -314,10 +316,6 @@ function onDrop(ev: DragEvent) {
|
|||
//#endregion
|
||||
}
|
||||
|
||||
function selectLocalFile() {
|
||||
fileInput.value?.click();
|
||||
}
|
||||
|
||||
function urlUpload() {
|
||||
os.inputText({
|
||||
title: i18n.ts.uploadFromUrl,
|
||||
|
|
@ -393,15 +391,8 @@ function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
|
|||
});
|
||||
}
|
||||
|
||||
function onChangeFileInput() {
|
||||
if (!fileInput.value?.files) return;
|
||||
for (const file of Array.from(fileInput.value.files)) {
|
||||
upload(file, folder.value);
|
||||
}
|
||||
}
|
||||
|
||||
function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null) {
|
||||
uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal.value).then(res => {
|
||||
function upload(file: File, folderToUpload?: Misskey.entities.DriveFolder | null, keepOriginal?: boolean) {
|
||||
uploadFile(file, (folderToUpload && typeof folderToUpload === 'object') ? folderToUpload.id : null, undefined, keepOriginal).then(res => {
|
||||
addFile(res, true);
|
||||
});
|
||||
}
|
||||
|
|
@ -644,16 +635,20 @@ function getMenu() {
|
|||
const menu: MenuItem[] = [];
|
||||
|
||||
menu.push({
|
||||
type: 'switch',
|
||||
text: i18n.ts.keepOriginalUploading,
|
||||
ref: keepOriginal,
|
||||
}, { type: 'divider' }, {
|
||||
text: i18n.ts.addFile,
|
||||
type: 'label',
|
||||
}, {
|
||||
text: i18n.ts.upload + ' (' + i18n.ts.compress + ')',
|
||||
icon: 'ti ti-upload',
|
||||
action: () => {
|
||||
chooseFileFromPc(true, { keepOriginal: false });
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.upload,
|
||||
icon: 'ti ti-upload',
|
||||
action: () => { selectLocalFile(); },
|
||||
action: () => {
|
||||
chooseFileFromPc(true, { keepOriginal: true });
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.fromUrl,
|
||||
icon: 'ti ti-link',
|
||||
|
|
@ -765,22 +760,17 @@ onBeforeUnmount(() => {
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
padding: 0 8px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
font-size: 0.9em;
|
||||
box-shadow: 0 1px 0 var(--MI_THEME-divider);
|
||||
user-select: none;
|
||||
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
.navPath {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ function opening() {
|
|||
picker.value?.focus();
|
||||
|
||||
// 何故かちょっと待たないとフォーカスされない
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
picker.value?.focus();
|
||||
}, 10);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ function touchMove(event: TouchEvent) {
|
|||
|
||||
pullDistance.value = 0;
|
||||
isSwiping.value = false;
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
isSwipingForClass.value = false;
|
||||
}, 400);
|
||||
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ const bufferedDataRatio = computed(() => {
|
|||
// MediaControl Events
|
||||
function onMouseOver() {
|
||||
if (controlStateTimer) {
|
||||
clearTimeout(controlStateTimer);
|
||||
window.clearTimeout(controlStateTimer);
|
||||
}
|
||||
isHoverring.value = true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import { deviceKind } from '@/utility/device-kind.js';
|
|||
import { focusTrap } from '@/utility/focus-trap.js';
|
||||
import { focusParent } from '@/utility/focus.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
function getFixedContainer(el: Element | null): Element | null {
|
||||
if (el == null || el.tagName === 'BODY') return null;
|
||||
|
|
@ -94,7 +95,7 @@ const emit = defineEmits<{
|
|||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
provide('modal', true);
|
||||
provide(DI.inModal, true);
|
||||
|
||||
const maxHeight = ref<number>();
|
||||
const fixed = ref(false);
|
||||
|
|
|
|||
|
|
@ -19,8 +19,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<MkNoteSub v-if="appearNote.reply" v-show="!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/>
|
||||
|
|
@ -53,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
|
||||
<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"/>
|
||||
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
||||
<div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
|
||||
<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
|
||||
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
|
||||
|
|
@ -922,7 +920,6 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
<style lang="scss" module>
|
||||
.root {
|
||||
position: relative;
|
||||
transition: box-shadow 0.1s ease;
|
||||
font-size: 1.05em;
|
||||
overflow: clip;
|
||||
contain: content;
|
||||
|
|
@ -993,6 +990,8 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
}
|
||||
|
||||
.skipRender {
|
||||
// TODO: これが有効だとTransitionGroupでnoteを追加するときに一瞬がくっとなってしまうのをどうにかしたい
|
||||
// Transitionが完了するのを待ってからskipRenderを付与すれば解決しそうだけどパフォーマンス的な影響が不明
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 0 150px;
|
||||
}
|
||||
|
|
@ -1127,9 +1126,12 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
margin: 0 14px 0 0;
|
||||
width: var(--MI-avatar);
|
||||
height: var(--MI-avatar);
|
||||
position: sticky !important;
|
||||
top: calc(22px + var(--MI-stickyTop, 0px));
|
||||
left: 0;
|
||||
|
||||
&.useSticky {
|
||||
position: sticky !important;
|
||||
top: calc(22px + var(--MI-stickyTop, 0px));
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
|
|
@ -1324,7 +1326,10 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
margin: 0 10px 0 0;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
top: calc(14px + var(--MI-stickyTop, 0px));
|
||||
|
||||
&.useSticky {
|
||||
top: calc(14px + var(--MI-stickyTop, 0px));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,38 +13,34 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
<div :class="[$style.root, { [$style.noGap]: noGap }]">
|
||||
<MkDateSeparatedList
|
||||
ref="notes"
|
||||
v-slot="{ item: note }"
|
||||
:items="notes"
|
||||
:direction="pagination.reversed ? 'up' : 'down'"
|
||||
:reversed="pagination.reversed"
|
||||
:noGap="noGap"
|
||||
:ad="true"
|
||||
:class="$style.notes"
|
||||
>
|
||||
<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
</MkDateSeparatedList>
|
||||
</div>
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
:leaveToClass="$style.transition_x_leaveTo"
|
||||
:moveClass=" $style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<template v-for="(note, i) in notes" :key="note.id">
|
||||
<DynamicNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
<div v-if="note._shouldInsertAd_" :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, useTemplateRef } from 'vue';
|
||||
import { useTemplateRef, TransitionGroup } from 'vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import DynamicNote from '@/components/DynamicNote.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import { defaultStore } from '@/store.js';
|
||||
|
||||
const MkNote = defineAsyncComponent(() =>
|
||||
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
|
||||
(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
|
||||
null
|
||||
);
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const props = defineProps<{
|
||||
pagination: Paging;
|
||||
|
|
@ -60,24 +56,49 @@ defineExpose({
|
|||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
&.noGap {
|
||||
border-radius: var(--MI-radius);
|
||||
.transition_x_move,
|
||||
.transition_x_enterActive,
|
||||
.transition_x_leaveActive {
|
||||
transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important;
|
||||
}
|
||||
.transition_x_enterFrom,
|
||||
.transition_x_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.transition_x_leaveActive {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
> .notes {
|
||||
background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent);
|
||||
.root {
|
||||
container-type: inline-size;
|
||||
|
||||
&.noGap {
|
||||
background: var(--MI_THEME-panel);
|
||||
|
||||
.note {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
.ad {
|
||||
padding: 8px;
|
||||
background-size: auto auto;
|
||||
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px);
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.noGap) {
|
||||
> .notes {
|
||||
background: var(--MI_THEME-bg);
|
||||
background: var(--MI_THEME-bg);
|
||||
|
||||
.note {
|
||||
background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent);
|
||||
border-radius: var(--MI-radius);
|
||||
}
|
||||
.note {
|
||||
background: var(--MI_THEME-panel);
|
||||
border-radius: var(--MI-radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ad:empty {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -14,34 +14,38 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
|
||||
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
|
||||
<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
|
||||
</MkDateSeparatedList>
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.notifications]"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
:leaveToClass="$style.transition_x_leaveTo"
|
||||
:moveClass=" $style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<template v-for="(notification, i) in notifications" :key="notification.id">
|
||||
<DynamicNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.item" :note="notification.note" :withHardMute="true"/>
|
||||
<XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true"/>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkPullToRefresh>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue';
|
||||
import { onUnmounted, onMounted, computed, useTemplateRef, TransitionGroup } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { notificationTypes } from '@@/js/const.js';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import XNotification from '@/components/MkNotification.vue';
|
||||
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
|
||||
import DynamicNote from '@/components/DynamicNote.vue';
|
||||
import { useStream } from '@/stream.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const MkNote = defineAsyncComponent(() =>
|
||||
(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
|
||||
(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
|
||||
null
|
||||
);
|
||||
|
||||
const props = defineProps<{
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
}>();
|
||||
|
|
@ -89,28 +93,36 @@ onMounted(() => {
|
|||
connection.on('notificationFlushed', reload);
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
pagingComponent.value?.reload();
|
||||
connection = useStream().useChannel('main');
|
||||
connection.on('notification', onNotification);
|
||||
connection.on('notificationFlushed', reload);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (connection) connection.dispose();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
if (connection) connection.dispose();
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
reload,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.list {
|
||||
.transition_x_move,
|
||||
.transition_x_enterActive,
|
||||
.transition_x_leaveActive {
|
||||
transition: opacity 0.3s cubic-bezier(0,.5,.5,1), transform 0.3s cubic-bezier(0,.5,.5,1) !important;
|
||||
}
|
||||
.transition_x_enterFrom,
|
||||
.transition_x_leaveTo {
|
||||
opacity: 0;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.transition_x_leaveActive {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
container-type: inline-size;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
.item {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<MkError v-else-if="error" @retry="init()"/>
|
||||
|
||||
<div v-else-if="empty" key="_empty_" class="empty">
|
||||
<div v-else-if="empty" key="_empty_">
|
||||
<slot name="empty">
|
||||
<div class="_fullinfo">
|
||||
<img :src="infoImageUrl" draggable="false"/>
|
||||
|
|
@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMoreAhead">
|
||||
{{ i18n.ts.loadMore }}
|
||||
</MkButton>
|
||||
<MkLoading v-else class="loading"/>
|
||||
<MkLoading v-else/>
|
||||
</div>
|
||||
<slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot>
|
||||
<div v-show="!pagination.reversed && more" key="_more_">
|
||||
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore">
|
||||
{{ i18n.ts.loadMore }}
|
||||
</MkButton>
|
||||
<MkLoading v-else class="loading"/>
|
||||
<MkLoading v-else/>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch, type Ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
|
||||
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js';
|
||||
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible } from '@@/js/scroll.js';
|
||||
import type { ComputedRef } from 'vue';
|
||||
import type { MisskeyEntity } from '@/types/date-separated-list.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
|
@ -268,7 +268,7 @@ const fetchMore = async (): Promise<void> => {
|
|||
|
||||
return nextTick(() => {
|
||||
if (scrollableElement.value) {
|
||||
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||
} else {
|
||||
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
||||
}
|
||||
|
|
@ -368,7 +368,7 @@ watch(visibility, () => {
|
|||
BACKGROUND_PAUSE_WAIT_SEC * 1000);
|
||||
} else { // 'visible'
|
||||
if (timerForSetPause) {
|
||||
clearTimeout(timerForSetPause);
|
||||
window.clearTimeout(timerForSetPause);
|
||||
timerForSetPause = null;
|
||||
} else {
|
||||
isPausingUpdate = false;
|
||||
|
|
@ -464,11 +464,11 @@ onBeforeMount(() => {
|
|||
init().then(() => {
|
||||
if (props.pagination.reversed) {
|
||||
nextTick(() => {
|
||||
setTimeout(toBottom, 800);
|
||||
window.setTimeout(toBottom, 800);
|
||||
|
||||
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
||||
// more = trueを遅らせる
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
moreFetching.value = false;
|
||||
}, 2000);
|
||||
});
|
||||
|
|
@ -478,11 +478,11 @@ onBeforeMount(() => {
|
|||
|
||||
onBeforeUnmount(() => {
|
||||
if (timerForSetPause) {
|
||||
clearTimeout(timerForSetPause);
|
||||
window.clearTimeout(timerForSetPause);
|
||||
timerForSetPause = null;
|
||||
}
|
||||
if (preventAppearFetchMoreTimer.value) {
|
||||
clearTimeout(preventAppearFetchMoreTimer.value);
|
||||
window.clearTimeout(preventAppearFetchMoreTimer.value);
|
||||
preventAppearFetchMoreTimer.value = null;
|
||||
}
|
||||
scrollObserver.value?.disconnect();
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ import { DI } from '@/di.js';
|
|||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const modal = inject('modal');
|
||||
const modal = inject(DI.inModal, false);
|
||||
|
||||
const props = withDefaults(defineProps<PostFormProps & {
|
||||
fixed?: boolean;
|
||||
|
|
@ -1431,7 +1431,7 @@ defineExpose({
|
|||
padding: 0 24px;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-size: 110%;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ [$style.slotClip]: isPullStart }">
|
||||
<slot/>
|
||||
</div>
|
||||
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -82,11 +81,11 @@ function moveBySystem(to: number): Promise<void> {
|
|||
return;
|
||||
}
|
||||
const startTime = Date.now();
|
||||
let intervalId = setInterval(() => {
|
||||
let intervalId = window.setInterval(() => {
|
||||
const time = Date.now() - startTime;
|
||||
if (time > RELEASE_TRANSITION_DURATION) {
|
||||
pullDistance.value = to;
|
||||
clearInterval(intervalId);
|
||||
window.clearInterval(intervalId);
|
||||
r();
|
||||
return;
|
||||
}
|
||||
|
|
@ -261,8 +260,4 @@ defineExpose({
|
|||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.slotClip {
|
||||
overflow-y: clip;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -4,22 +4,24 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<TransitionGroup
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''"
|
||||
:moveClass="prefer.s.animation ? $style.transition_x_move : ''"
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
:leaveToClass="$style.transition_x_leaveTo"
|
||||
:moveClass="$style.transition_x_move"
|
||||
tag="div" :class="$style.root"
|
||||
>
|
||||
<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note" @reactionToggled="onMockToggleReaction"/>
|
||||
<slot v-if="hasMoreReactions" name="more"/>
|
||||
</TransitionGroup>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { inject, watch, ref } from 'vue';
|
||||
import { TransitionGroup } from 'vue';
|
||||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:leaveActiveClass="prefer.s.animation ? $style.transition_tooltip_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_tooltip_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_tooltip_leaveTo : ''"
|
||||
appear @afterLeave="emit('closed')"
|
||||
appear :css="prefer.s.animation"
|
||||
@afterLeave="emit('closed')"
|
||||
>
|
||||
<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
|
||||
<slot>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -53,7 +52,7 @@ import { prefer } from '@/preferences.js';
|
|||
type Ad = (typeof instance)['ads'][number];
|
||||
|
||||
const props = defineProps<{
|
||||
prefer: string[];
|
||||
preferForms: string[];
|
||||
specify?: Ad;
|
||||
}>();
|
||||
|
||||
|
|
@ -72,7 +71,7 @@ const choseAd = (): Ad | null => {
|
|||
ratio: 0,
|
||||
} : ad);
|
||||
|
||||
let ads = allAds.filter(ad => props.prefer.includes(ad.place));
|
||||
let ads = allAds.filter(ad => props.preferForms.includes(ad.place));
|
||||
|
||||
if (ads.length === 0) {
|
||||
ads = allAds.filter(ad => ad.place === 'square');
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const props = defineProps<{
|
|||
menuReaction?: boolean;
|
||||
}>();
|
||||
|
||||
const react = inject(DI.mfmEmojiReactCallback);
|
||||
const react = inject(DI.mfmEmojiReactCallback, null);
|
||||
|
||||
const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : prefer.s.emojiStyle === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/explicit-function-return-type */
|
||||
|
||||
import { waitFor } from '@storybook/test';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
import MkPageHeader from './MkPageHeader.vue';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
export const Empty = {
|
||||
render(args) {
|
||||
return {
|
||||
|
|
@ -29,7 +28,7 @@ export const Empty = {
|
|||
};
|
||||
},
|
||||
async play() {
|
||||
const wait = new Promise((resolve) => setTimeout(resolve, 800));
|
||||
const wait = new Promise((resolve) => window.setTimeout(resolve, 800));
|
||||
await waitFor(async () => await wait);
|
||||
},
|
||||
args: {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ async function enter(el: Element) {
|
|||
entering = false;
|
||||
});
|
||||
|
||||
setTimeout(renderTab, 170);
|
||||
window.setTimeout(renderTab, 170);
|
||||
}
|
||||
|
||||
function afterEnter(el: Element) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div v-if="show" ref="el" :class="[$style.root]" :style="{ background: bg }">
|
||||
<div v-if="show" ref="el" :class="[$style.root]">
|
||||
<div :class="[$style.upper, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
|
||||
<div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
|
||||
<MkAvatar :class="$style.avatar" :user="$i"/>
|
||||
|
|
@ -51,7 +51,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { scrollToTop } from '@@/js/scroll.js';
|
||||
import XTabs from './MkPageHeader.tabs.vue';
|
||||
import type { Tab } from './MkPageHeader.tabs.vue';
|
||||
|
|
@ -82,7 +81,6 @@ const emit = defineEmits<{
|
|||
const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true);
|
||||
|
||||
const viewId = inject(DI.viewId);
|
||||
const viewTransitionName = computed(() => `${viewId}---pageHeader`);
|
||||
const injectedPageMetadata = inject(DI.pageMetadata);
|
||||
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
|
||||
|
||||
|
|
@ -90,7 +88,6 @@ const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props
|
|||
const thin_ = props.thin || inject('shouldHeaderThin', false);
|
||||
|
||||
const el = useTemplateRef('el');
|
||||
const bg = ref<string | undefined>(undefined);
|
||||
const narrow = ref(false);
|
||||
const hasTabs = computed(() => props.tabs.length > 0);
|
||||
const hasActions = computed(() => props.actions && props.actions.length > 0);
|
||||
|
|
@ -122,19 +119,9 @@ function goBack(): void {
|
|||
window.history.back();
|
||||
}
|
||||
|
||||
const calcBg = () => {
|
||||
const rawBg = 'var(--MI_THEME-bg)';
|
||||
const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(window.document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
|
||||
tinyBg.setAlpha(0.85);
|
||||
bg.value = tinyBg.toRgbString();
|
||||
};
|
||||
|
||||
let ro: ResizeObserver | null;
|
||||
|
||||
onMounted(() => {
|
||||
calcBg();
|
||||
globalEvents.on('themeChanging', calcBg);
|
||||
|
||||
if (el.value && el.value.parentElement) {
|
||||
narrow.value = el.value.parentElement.offsetWidth < 500;
|
||||
ro = new ResizeObserver((entries, observer) => {
|
||||
|
|
@ -147,18 +134,17 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
globalEvents.off('themeChanging', calcBg);
|
||||
if (ro) ro.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
background: color(from var(--MI_THEME-bg) srgb r g b / 0.75);
|
||||
-webkit-backdrop-filter: var(--MI-blur, blur(15px));
|
||||
backdrop-filter: var(--MI-blur, blur(15px));
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
width: 100%;
|
||||
view-transition-name: v-bind(viewTransitionName);
|
||||
}
|
||||
|
||||
.upper,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
||||
<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
|
||||
<div :class="$style.body">
|
||||
|
|
@ -16,6 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||
import type { Tab } from './MkPageHeader.tabs.vue';
|
||||
|
||||
|
|
@ -31,6 +33,13 @@ const props = withDefaults(defineProps<{
|
|||
});
|
||||
|
||||
const tab = defineModel<string>('tab');
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
defineExpose({
|
||||
scrollToTop: () => {
|
||||
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'smooth' });
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,9 @@ provide(DI.routerCurrentDepth, currentDepth + 1);
|
|||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
onMounted(() => {
|
||||
rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
|
||||
if (prefer.s.animation) {
|
||||
rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
|
||||
}
|
||||
});
|
||||
|
||||
// view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成
|
||||
|
|
|
|||
|
|
@ -50,6 +50,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref, toRefs, watch } from 'vue';
|
||||
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
|
||||
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
|
||||
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import type { GridColumn } from '@/components/grid/column.js';
|
||||
import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import { GridEventEmitter } from '@/components/grid/grid.js';
|
||||
import MkDataRow from '@/components/grid/MkDataRow.vue';
|
||||
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
|
||||
|
|
@ -68,13 +74,6 @@ import { createColumn } from '@/components/grid/column.js';
|
|||
import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js';
|
||||
import { handleKeyEvent } from '@/utility/key-event.js';
|
||||
|
||||
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
|
||||
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
|
||||
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
|
||||
import type { GridColumn } from '@/components/grid/column.js';
|
||||
import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
|
||||
type RowHolder = {
|
||||
row: GridRow,
|
||||
cells: GridCell[],
|
||||
|
|
@ -130,7 +129,7 @@ const bus = new GridEventEmitter();
|
|||
*
|
||||
* @see {@link onResize}
|
||||
*/
|
||||
const resizeObserver = new ResizeObserver((entries) => setTimeout(() => onResize(entries)));
|
||||
const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries)));
|
||||
|
||||
const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,4 +15,5 @@ export const DI = {
|
|||
currentStickyTop: Symbol() as InjectionKey<Ref<number>>,
|
||||
currentStickyBottom: Symbol() as InjectionKey<Ref<number>>,
|
||||
mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>,
|
||||
inModal: Symbol() as InjectionKey<boolean>,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -165,13 +165,6 @@ export const navbarItemDef = reactive({
|
|||
miLocalStorage.setItem('ui', 'deck');
|
||||
unisonReload();
|
||||
},
|
||||
}, {
|
||||
text: i18n.ts.classic,
|
||||
active: ui === 'classic',
|
||||
action: () => {
|
||||
miLocalStorage.setItem('ui', 'classic');
|
||||
unisonReload();
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
|||
import type { Form, GetFormResultType } from '@/utility/form.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import type { PostFormProps } from '@/types/post-form.js';
|
||||
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
|
@ -23,8 +25,6 @@ import MkToast from '@/components/MkToast.vue';
|
|||
import MkDialog from '@/components/MkDialog.vue';
|
||||
import MkPopupMenu from '@/components/MkPopupMenu.vue';
|
||||
import MkContextMenu from '@/components/MkContextMenu.vue';
|
||||
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
|
||||
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
|
||||
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
|
||||
import { pleaseLogin } from '@/utility/please-login.js';
|
||||
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
||||
|
|
|
|||
|
|
@ -18,11 +18,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkSwitch v-model="keepOriginalUploading">
|
||||
<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
|
||||
<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
|
||||
</MkSwitch>
|
||||
|
||||
<MkSwitch v-model="directoryToCategory">
|
||||
<template #label>{{ i18n.ts._customEmojisManager._local._register.directoryToCategoryLabel }}</template>
|
||||
<template #caption>{{ i18n.ts._customEmojisManager._local._register.directoryToCategoryCaption }}</template>
|
||||
|
|
@ -245,7 +240,6 @@ function setupGrid(): GridSetting {
|
|||
const uploadFolders = ref<FolderItem[]>([]);
|
||||
const gridItems = ref<GridItem[]>([]);
|
||||
const selectedFolderId = ref(prefer.s.uploadFolder);
|
||||
const keepOriginalUploading = ref(prefer.s.keepOriginalUploading);
|
||||
const directoryToCategory = ref<boolean>(false);
|
||||
const registerButtonDisabled = ref<boolean>(false);
|
||||
const requestLogs = ref<RequestLogItem[]>([]);
|
||||
|
|
@ -338,7 +332,7 @@ async function onDrop(ev: DragEvent) {
|
|||
it.file,
|
||||
selectedFolderId.value,
|
||||
it.file.name.replace(/\.[^.]+$/, ''),
|
||||
keepOriginalUploading.value,
|
||||
true,
|
||||
),
|
||||
}),
|
||||
),
|
||||
|
|
@ -373,7 +367,7 @@ async function onFileSelectClicked() {
|
|||
true,
|
||||
{
|
||||
uploadFolder: selectedFolderId.value,
|
||||
keepOriginal: keepOriginalUploading.value,
|
||||
keepOriginal: true,
|
||||
// 拡張子は消す
|
||||
nameConverter: (file) => file.name.replace(/\.[a-zA-Z0-9]+$/, ''),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
|
@ -49,7 +49,7 @@ function queueUpdated(q) {
|
|||
}
|
||||
|
||||
function top() {
|
||||
scroll(rootEl.value, { top: 0 });
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
async function timetravel() {
|
||||
|
|
|
|||
|
|
@ -242,6 +242,10 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
|
|||
font-size: 80%;
|
||||
}
|
||||
|
||||
.fukidashi {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: clip;
|
||||
overflow-wrap: break-word;
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<MkButton primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
|
||||
<MkButton v-if="$i.policies.canChat" primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
|
||||
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
<MkInfo v-else>{{ i18n.ts._chat.chatNotAvailableForThisAccountOrServer }}</MkInfo>
|
||||
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
|
||||
<MkInput
|
||||
v-model="searchQuery"
|
||||
|
|
@ -78,6 +80,7 @@ import * as os from '@/os.js';
|
|||
import { updateCurrentAccountPartial } from '@/accounts.js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
|
|
|
|||
|
|
@ -41,6 +41,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<XMessage v-for="message in messages.toReversed()" :key="message.id" :message="message"/>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<div v-if="user && (!user.canChat || user.host !== null)">
|
||||
<MkInfo warn>{{ i18n.ts._chat.chatNotAvailableInOtherAccount }}</MkInfo>
|
||||
</div>
|
||||
|
||||
<MkInfo v-if="!$i.policies.canChat" warn>{{ i18n.ts._chat.chatNotAvailableForThisAccountOrServer }}</MkInfo>
|
||||
</MkSpacer>
|
||||
|
||||
<MkSpacer v-else-if="tab === 'search'" :contentMax="700">
|
||||
|
|
@ -93,6 +99,7 @@ import { prefer } from '@/preferences.js';
|
|||
import MkButton from '@/components/MkButton.vue';
|
||||
import { useRouter } from '@/router.js';
|
||||
import { useMutationObserver } from '@/use/use-mutation-observer.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
const $i = ensureSignin();
|
||||
const router = useRouter();
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div>
|
||||
<XDrive ref="drive" @cd="x => folder = x"/>
|
||||
<XDrive @cd="x => folder = x"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
<MkA v-if="$i && $i.id === flash.userId" :to="`/play/${flash.id}/edit`" style="color: var(--MI_THEME-accent);">{{ i18n.ts._play.editThisPage }}</MkA>
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetchFlash()"/>
|
||||
<MkLoading v-else/>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkFollowButton v-if="!$i || $i.id != post.user.id" v-model:user="post.user" :inline="true" :transparent="false" :full="true" large class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
||||
<template #icon><i class="ti ti-clock"></i></template>
|
||||
<template #header>{{ i18n.ts.recentPosts }}</template>
|
||||
|
|
|
|||
|
|
@ -6,17 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template>
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<MkSpacer :contentMax="800">
|
||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||
<div v-if="tab === 'all'">
|
||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<MkNotes :pagination="mentionsPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'directNotes'">
|
||||
<MkNotes :pagination="directNotesPagination"/>
|
||||
</div>
|
||||
</MkHorizontalSwipe>
|
||||
<div v-if="tab === 'all'">
|
||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<MkNotes :pagination="mentionsPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'directNotes'">
|
||||
<MkNotes :pagination="directNotesPagination"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div v-if="page.createdAt != page.updatedAt"><i class="ti ti-clock-edit"></i> {{ i18n.ts.updatedAt }}: <MkTime :time="page.updatedAt" mode="detail"/></div>
|
||||
</div>
|
||||
</div>
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
<MkContainer :max-height="300" :foldable="true" class="other">
|
||||
<template #icon><i class="ti ti-clock"></i></template>
|
||||
<template #header>{{ i18n.ts.recentPosts }}</template>
|
||||
|
|
|
|||
|
|
@ -145,13 +145,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import * as Reversi from 'misskey-reversi';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import { deepClone } from '@/utility/clone.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { userPage } from '@/filters/user.js';
|
||||
|
|
@ -301,7 +301,7 @@ if (!props.game.isEnded) {
|
|||
|
||||
if (iAmPlayer.value) {
|
||||
if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) {
|
||||
props.connection!.send('claimTimeIsUp', {});
|
||||
props.connection!.send('claimTimeIsUp', {});
|
||||
}
|
||||
}
|
||||
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
|
||||
|
|
@ -424,7 +424,7 @@ function autoplay() {
|
|||
const tick = () => {
|
||||
const log = logs[i];
|
||||
const time = log.time - previousLog.time;
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
i++;
|
||||
logPos.value++;
|
||||
previousLog = log;
|
||||
|
|
|
|||
|
|
@ -1,173 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<SearchMarker path="/settings/accessibility" :label="i18n.ts.accessibility" :keywords="['accessibility']" icon="ti ti-accessible">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/mens_room_3d.png" color="#0011ff">
|
||||
<SearchKeyword>{{ i18n.ts._settings.accessibilityBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['animation', 'motion', 'reduce']">
|
||||
<MkPreferenceContainer k="animation">
|
||||
<MkSwitch v-model="reduceAnimation">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']">
|
||||
<MkPreferenceContainer k="disableShowingAnimatedImages">
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']">
|
||||
<MkPreferenceContainer k="animatedMfm">
|
||||
<MkSwitch v-model="animatedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['swipe', 'horizontal', 'tab']">
|
||||
<MkPreferenceContainer k="enableHorizontalSwipe">
|
||||
<MkSwitch v-model="enableHorizontalSwipe">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
||||
<MkPreferenceContainer k="keepScreenOn">
|
||||
<MkSwitch v-model="keepScreenOn">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']">
|
||||
<MkPreferenceContainer k="useNativeUiForVideoAudioPlayer">
|
||||
<MkSwitch v-model="useNativeUiForVideoAudioPlayer">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['text', 'selectable']">
|
||||
<MkPreferenceContainer k="makeEveryTextElementsSelectable">
|
||||
<MkSwitch v-model="makeEveryTextElementsSelectable">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
||||
<MkPreferenceContainer k="menuStyle">
|
||||
<MkSelect v-model="menuStyle">
|
||||
<template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['contextmenu', 'system', 'native']">
|
||||
<MkPreferenceContainer k="contextMenu">
|
||||
<MkSelect v-model="contextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template>
|
||||
<option value="app">{{ i18n.ts._contextMenu.app }}</option>
|
||||
<option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
|
||||
<option value="native">{{ i18n.ts._contextMenu.native }}</option>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'size']">
|
||||
<MkRadios v-model="fontSize">
|
||||
<template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template>
|
||||
<option :value="null"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option value="1"><span style="font-size: 15px;">Aa</span></option>
|
||||
<option value="2"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'system', 'native']">
|
||||
<MkSwitch v-model="useSystemFont">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import MkSwitch from '@/components/MkSwitch.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { reloadAsk } from '@/utility/reload-ask.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
|
||||
const reduceAnimation = prefer.model('animation', v => !v, v => !v);
|
||||
const animatedMfm = prefer.model('animatedMfm');
|
||||
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
|
||||
const keepScreenOn = prefer.model('keepScreenOn');
|
||||
const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
const menuStyle = prefer.model('menuStyle');
|
||||
const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
|
||||
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
miLocalStorage.removeItem('fontSize');
|
||||
} else {
|
||||
miLocalStorage.setItem('fontSize', fontSize.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(useSystemFont, () => {
|
||||
if (useSystemFont.value) {
|
||||
miLocalStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
miLocalStorage.removeItem('useSystemFont');
|
||||
}
|
||||
});
|
||||
|
||||
watch([
|
||||
keepScreenOn,
|
||||
contextMenu,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
makeEveryTextElementsSelectable,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => []);
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.accessibility,
|
||||
icon: 'ti ti-accessible',
|
||||
}));
|
||||
</script>
|
||||
|
|
@ -53,15 +53,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
{{ i18n.ts.drivecleaner }}
|
||||
</FormLink>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'original', 'raw', 'upload']">
|
||||
<MkPreferenceContainer k="keepOriginalUploading">
|
||||
<MkSwitch v-model="keepOriginalUploading">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepOriginalUploading }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts.keepOriginalUploadingDescription }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'original', 'filename']">
|
||||
<MkPreferenceContainer k="keepOriginalFilename">
|
||||
<MkSwitch v-model="keepOriginalFilename">
|
||||
|
|
@ -122,7 +113,6 @@ const meterStyle = computed(() => {
|
|||
};
|
||||
});
|
||||
|
||||
const keepOriginalUploading = prefer.model('keepOriginalUploading');
|
||||
const keepOriginalFilename = prefer.model('keepOriginalFilename');
|
||||
|
||||
misskeyApi('drive').then(info => {
|
||||
|
|
|
|||
|
|
@ -122,11 +122,6 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
|||
text: i18n.ts.sounds,
|
||||
to: '/settings/sounds',
|
||||
active: currentPage.value?.route.name === 'sounds',
|
||||
}, {
|
||||
icon: 'ti ti-accessible',
|
||||
text: i18n.ts.accessibility,
|
||||
to: '/settings/accessibility',
|
||||
active: currentPage.value?.route.name === 'accessibility',
|
||||
}, {
|
||||
icon: 'ti ti-plug',
|
||||
text: i18n.ts.plugins,
|
||||
|
|
|
|||
|
|
@ -40,8 +40,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.display }}</template>
|
||||
<option value="sideFull">{{ i18n.ts._menuDisplay.sideFull }}</option>
|
||||
<option value="sideIcon">{{ i18n.ts._menuDisplay.sideIcon }}</option>
|
||||
<option value="top">{{ i18n.ts._menuDisplay.top }}</option>
|
||||
<!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
|
||||
</MkRadios>
|
||||
|
||||
<SearchMarker :keywords="['navbar', 'sidebar', 'toggle', 'button', 'sub']">
|
||||
|
|
|
|||
|
|
@ -42,22 +42,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</SearchMarker>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkPreferenceContainer k="useBlurEffect">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkPreferenceContainer k="useBlurEffectForModal">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
|
||||
<MkPreferenceContainer k="showAvatarDecorations">
|
||||
<MkSwitch v-model="showAvatarDecorations">
|
||||
|
|
@ -120,15 +104,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['pinned', 'list']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template>
|
||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||
<MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
|
@ -199,6 +174,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['pinned', 'list']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template>
|
||||
<!-- 複数ピン止め管理できるようにしたいけどめんどいので一旦ひとつのみ -->
|
||||
<MkButton v-if="prefer.r.pinnedUserLists.value.length === 0" @click="setPinnedList()">{{ i18n.ts.add }}</MkButton>
|
||||
<MkButton v-else danger @click="removePinnedList()"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['show', 'ticker', 'replies']">
|
||||
<MkSwitch v-model="showTickerOnReplies">
|
||||
<template #label>{{ i18n.ts.showTickerOnReplies }}</template>
|
||||
|
|
@ -478,40 +462,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['datasaver']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
|
||||
<template #icon><i class="ti ti-antenna-bars-3"></i></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
|
||||
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
|
||||
</div>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="dataSaver.media">
|
||||
{{ i18n.ts._dataSaver._media.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.avatar">
|
||||
{{ i18n.ts._dataSaver._avatar.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.urlPreview">
|
||||
{{ i18n.ts._dataSaver._urlPreview.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.code">
|
||||
{{ i18n.ts._dataSaver._code.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['chat', 'messaging']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.chat }}</SearchLabel></template>
|
||||
|
|
@ -551,6 +501,186 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['accessibility']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.accessibility }}</SearchLabel></template>
|
||||
<template #icon><i class="ti ti-accessible"></i></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/mens_room_3d.png" color="#0011ff">
|
||||
<SearchKeyword>{{ i18n.ts._settings.accessibilityBanner }}</SearchKeyword>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['animation', 'motion', 'reduce']">
|
||||
<MkPreferenceContainer k="animation">
|
||||
<MkSwitch v-model="reduceAnimation">
|
||||
<template #label><SearchLabel>{{ i18n.ts.reduceUiAnimation }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif']">
|
||||
<MkPreferenceContainer k="disableShowingAnimatedImages">
|
||||
<MkSwitch v-model="disableShowingAnimatedImages">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableShowingAnimatedImages }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['mfm', 'enable', 'show', 'animated']">
|
||||
<MkPreferenceContainer k="animatedMfm">
|
||||
<MkSwitch v-model="animatedMfm">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableAnimatedMfm }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['swipe', 'horizontal', 'tab']">
|
||||
<MkPreferenceContainer k="enableHorizontalSwipe">
|
||||
<MkSwitch v-model="enableHorizontalSwipe">
|
||||
<template #label><SearchLabel>{{ i18n.ts.enableHorizontalSwipe }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['keep', 'screen', 'display', 'on']">
|
||||
<MkPreferenceContainer k="keepScreenOn">
|
||||
<MkSwitch v-model="keepScreenOn">
|
||||
<template #label><SearchLabel>{{ i18n.ts.keepScreenOn }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['native', 'system', 'video', 'audio', 'player', 'media']">
|
||||
<MkPreferenceContainer k="useNativeUiForVideoAudioPlayer">
|
||||
<MkSwitch v-model="useNativeUiForVideoAudioPlayer">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useNativeUIForVideoAudioPlayer }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['text', 'selectable']">
|
||||
<MkPreferenceContainer k="makeEveryTextElementsSelectable">
|
||||
<MkSwitch v-model="makeEveryTextElementsSelectable">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.makeEveryTextElementsSelectable }}</SearchLabel></template>
|
||||
<template #caption>{{ i18n.ts._settings.makeEveryTextElementsSelectable_description }}</template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
|
||||
<SearchMarker :keywords="['menu', 'style', 'popup', 'drawer']">
|
||||
<MkPreferenceContainer k="menuStyle">
|
||||
<MkSelect v-model="menuStyle">
|
||||
<template #label><SearchLabel>{{ i18n.ts.menuStyle }}</SearchLabel></template>
|
||||
<option value="auto">{{ i18n.ts.auto }}</option>
|
||||
<option value="popup">{{ i18n.ts.popup }}</option>
|
||||
<option value="drawer">{{ i18n.ts.drawer }}</option>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['contextmenu', 'system', 'native']">
|
||||
<MkPreferenceContainer k="contextMenu">
|
||||
<MkSelect v-model="contextMenu">
|
||||
<template #label><SearchLabel>{{ i18n.ts._contextMenu.title }}</SearchLabel></template>
|
||||
<option value="app">{{ i18n.ts._contextMenu.app }}</option>
|
||||
<option value="appWithShift">{{ i18n.ts._contextMenu.appWithShift }}</option>
|
||||
<option value="native">{{ i18n.ts._contextMenu.native }}</option>
|
||||
</MkSelect>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'size']">
|
||||
<MkRadios v-model="fontSize">
|
||||
<template #label><SearchLabel>{{ i18n.ts.fontSize }}</SearchLabel></template>
|
||||
<option :value="null"><span style="font-size: 14px;">Aa</span></option>
|
||||
<option value="1"><span style="font-size: 15px;">Aa</span></option>
|
||||
<option value="2"><span style="font-size: 16px;">Aa</span></option>
|
||||
<option value="3"><span style="font-size: 17px;">Aa</span></option>
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['font', 'system', 'native']">
|
||||
<MkSwitch v-model="useSystemFont">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useSystemFont }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['performance']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.performance }}</SearchLabel></template>
|
||||
<template #icon><i class="ti ti-battery-vertical-eco"></i></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['blur']">
|
||||
<MkPreferenceContainer k="useBlurEffect">
|
||||
<MkSwitch v-model="useBlurEffect">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
|
||||
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['blur', 'modal']">
|
||||
<MkPreferenceContainer k="useBlurEffectForModal">
|
||||
<MkSwitch v-model="useBlurEffectForModal">
|
||||
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
|
||||
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['sticky']">
|
||||
<MkPreferenceContainer k="useStickyIcons">
|
||||
<MkSwitch v-model="useStickyIcons">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template>
|
||||
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['datasaver']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
|
||||
<template #icon><i class="ti ti-antenna-bars-3"></i></template>
|
||||
|
||||
<div class="_gaps_m">
|
||||
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
|
||||
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
|
||||
</div>
|
||||
<div class="_gaps_m">
|
||||
<MkSwitch v-model="dataSaver.media">
|
||||
{{ i18n.ts._dataSaver._media.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.avatar">
|
||||
{{ i18n.ts._dataSaver._avatar.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.urlPreview">
|
||||
{{ i18n.ts._dataSaver._urlPreview.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="dataSaver.code">
|
||||
{{ i18n.ts._dataSaver._code.title }}
|
||||
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['other']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template>
|
||||
|
|
@ -784,6 +914,19 @@ const useBlurEffect = prefer.model('useBlurEffect');
|
|||
const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies');
|
||||
const chatShowSenderName = prefer.model('chat.showSenderName');
|
||||
const chatSendOnEnter = prefer.model('chat.sendOnEnter');
|
||||
const useStickyIcons = prefer.model('useStickyIcons');
|
||||
const reduceAnimation = prefer.model('animation', v => !v, v => !v);
|
||||
const animatedMfm = prefer.model('animatedMfm');
|
||||
const disableShowingAnimatedImages = prefer.model('disableShowingAnimatedImages');
|
||||
const keepScreenOn = prefer.model('keepScreenOn');
|
||||
const enableHorizontalSwipe = prefer.model('enableHorizontalSwipe');
|
||||
const useNativeUiForVideoAudioPlayer = prefer.model('useNativeUiForVideoAudioPlayer');
|
||||
const contextMenu = prefer.model('contextMenu');
|
||||
const menuStyle = prefer.model('menuStyle');
|
||||
const makeEveryTextElementsSelectable = prefer.model('makeEveryTextElementsSelectable');
|
||||
|
||||
const fontSize = ref(miLocalStorage.getItem('fontSize'));
|
||||
const useSystemFont = ref(miLocalStorage.getItem('useSystemFont') != null);
|
||||
|
||||
// Sharkey options
|
||||
const collapseNotesRepliedTo = prefer.model('collapseNotesRepliedTo');
|
||||
|
|
@ -811,6 +954,22 @@ watch(lang, () => {
|
|||
miLocalStorage.removeItem('localeVersion');
|
||||
});
|
||||
|
||||
watch(fontSize, () => {
|
||||
if (fontSize.value == null) {
|
||||
miLocalStorage.removeItem('fontSize');
|
||||
} else {
|
||||
miLocalStorage.setItem('fontSize', fontSize.value);
|
||||
}
|
||||
});
|
||||
|
||||
watch(useSystemFont, () => {
|
||||
if (useSystemFont.value) {
|
||||
miLocalStorage.setItem('useSystemFont', 't');
|
||||
} else {
|
||||
miLocalStorage.removeItem('useSystemFont');
|
||||
}
|
||||
});
|
||||
|
||||
watch([
|
||||
hemisphere,
|
||||
lang,
|
||||
|
|
@ -832,6 +991,12 @@ watch([
|
|||
highlightSensitiveMedia,
|
||||
enableSeasonalScreenEffect,
|
||||
chatShowSenderName,
|
||||
useStickyIcons,
|
||||
keepScreenOn,
|
||||
contextMenu,
|
||||
fontSize,
|
||||
useSystemFont,
|
||||
makeEveryTextElementsSelectable,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
|
@ -940,7 +1105,7 @@ function testNotification(): void {
|
|||
smashCount = 0;
|
||||
}
|
||||
if (smashTimer) {
|
||||
clearTimeout(smashTimer);
|
||||
window.clearTimeout(smashTimer);
|
||||
}
|
||||
smashTimer = window.setTimeout(() => {
|
||||
smashCount = 0;
|
||||
|
|
|
|||
|
|
@ -4,48 +4,44 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true">
|
||||
<MkSpacer :contentMax="800">
|
||||
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
|
||||
<div ref="rootEl">
|
||||
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||
{{ i18n.ts._timelineDescription[src] }}
|
||||
</MkInfo>
|
||||
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
<MkTimeline
|
||||
ref="tlComponent"
|
||||
:key="src + withRenotes + withBots + withReplies + onlyFiles + withSensitive"
|
||||
:src="src.split(':')[0]"
|
||||
:list="src.split(':')[1]"
|
||||
:withRenotes="withRenotes"
|
||||
:withReplies="withReplies"
|
||||
:withSensitive="withSensitive"
|
||||
:onlyFiles="onlyFiles"
|
||||
:withBots="withBots"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</MkHorizontalSwipe>
|
||||
</MkSpacer>
|
||||
</PageWithHeader>
|
||||
<div ref="rootEl" class="_pageScrollable">
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
|
||||
<MkSpacer :contentMax="800">
|
||||
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||
{{ i18n.ts._timelineDescription[src] }}
|
||||
</MkInfo>
|
||||
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<MkTimeline
|
||||
ref="tlComponent"
|
||||
:key="src + withRenotes + withBots + withReplies + onlyFiles + withSensitive"
|
||||
:class="$style.tl"
|
||||
:src="src.split(':')[0]"
|
||||
:list="src.split(':')[1]"
|
||||
:withRenotes="withRenotes"
|
||||
:withReplies="withReplies"
|
||||
:withSensitive="withSensitive"
|
||||
:onlyFiles="onlyFiles"
|
||||
:withBots="withBots"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
/>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
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 MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
|
@ -139,7 +135,7 @@ function queueUpdated(q: number): void {
|
|||
}
|
||||
|
||||
function top(): void {
|
||||
if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' });
|
||||
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'instant' });
|
||||
}
|
||||
|
||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { definePage } from '@/page.js';
|
||||
|
|
@ -77,7 +77,7 @@ function queueUpdated(q) {
|
|||
}
|
||||
|
||||
function top() {
|
||||
scroll(rootEl.value, { top: 0 });
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
function settings() {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export function migrateOldSettings() {
|
|||
prefer.commit('collapseRenotes', store.s.collapseRenotes);
|
||||
prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility);
|
||||
prefer.commit('uploadFolder', store.s.uploadFolder);
|
||||
prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading);
|
||||
prefer.commit('menu', store.s.menu);
|
||||
prefer.commit('statusbars', store.s.statusbars);
|
||||
prefer.commit('pinnedUserLists', store.s.pinnedUserLists);
|
||||
|
|
|
|||
|
|
@ -118,9 +118,6 @@ export const PREF_DEF = {
|
|||
keepCw: {
|
||||
default: true,
|
||||
},
|
||||
keepOriginalUploading: {
|
||||
default: false,
|
||||
},
|
||||
rememberNoteVisibility: {
|
||||
default: false,
|
||||
},
|
||||
|
|
@ -201,6 +198,9 @@ export const PREF_DEF = {
|
|||
useBlurEffect: {
|
||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
||||
},
|
||||
useStickyIcons: {
|
||||
default: true,
|
||||
},
|
||||
showFixedPostForm: {
|
||||
default: false,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -128,10 +128,6 @@ export const ROUTE_DEF = [{
|
|||
path: '/sounds',
|
||||
name: 'sounds',
|
||||
component: page(() => import('@/pages/settings/sounds.vue')),
|
||||
}, {
|
||||
path: '/accessibility',
|
||||
name: 'accessibility',
|
||||
component: page(() => import('@/pages/settings/accessibility.vue')),
|
||||
}, {
|
||||
path: '/plugin/install',
|
||||
name: 'plugin',
|
||||
|
|
|
|||
|
|
@ -58,9 +58,6 @@ html.radius-misskey {
|
|||
}
|
||||
|
||||
html {
|
||||
background-color: var(--MI_THEME-bg);
|
||||
color: var(--MI_THEME-fg);
|
||||
accent-color: var(--MI_THEME-accent);
|
||||
overflow: auto;
|
||||
overflow-wrap: break-word;
|
||||
font-family: 'sharkey-theme-font-face', 'Lexend', 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif;
|
||||
|
|
@ -69,6 +66,11 @@ html {
|
|||
text-size-adjust: 100%;
|
||||
tab-size: 2;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
touch-action: manipulation;
|
||||
scroll-behavior: smooth;
|
||||
background-color: var(--MI_THEME-bg);
|
||||
color: var(--MI_THEME-fg);
|
||||
accent-color: var(--MI_THEME-accent);
|
||||
|
||||
&, * {
|
||||
scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent;
|
||||
|
|
@ -124,11 +126,19 @@ html._themeChanging_ {
|
|||
}
|
||||
}
|
||||
|
||||
html, body {
|
||||
touch-action: manipulation;
|
||||
html,
|
||||
body,
|
||||
#misskey_app {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
scroll-behavior: smooth;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body {
|
||||
/* NOTE: htmlにも overflow: clip を設定したいところだが、設定すると何故か少なくともChromeで html が main thread scrolling になりパフォーマンスが(多分)落ちる */
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
a {
|
||||
|
|
@ -208,6 +218,14 @@ rt {
|
|||
overflow: clip;
|
||||
overflow-y: scroll;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
/*
|
||||
理屈は知らないけど、ここでbackgroundを設定しておかないと
|
||||
スクロールコンテナーが少なくともChromeにおいて
|
||||
main thread scrolling になってしまい、パフォーマンスが(多分)落ちる。
|
||||
backgroundが透明だと裏側を描画しないといけなくなるとかそういう理由かもしれない
|
||||
*/
|
||||
background: var(--MI_THEME-bg);
|
||||
}
|
||||
|
||||
._pageScrollableReversed {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<XUpload v-if="uploads.length > 0"/>
|
||||
|
||||
<TransitionGroup
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||
tag="div"
|
||||
:class="[$style.notifications, {
|
||||
[$style.notificationsPosition_leftTop]: prefer.s.notificationPosition === 'leftTop',
|
||||
|
|
@ -24,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
[$style.notificationsStackAxis_vertical]: prefer.s.notificationStackAxis === 'vertical',
|
||||
[$style.notificationsStackAxis_horizontal]: prefer.s.notificationStackAxis === 'horizontal',
|
||||
}]"
|
||||
:moveClass="prefer.s.animation ? $style.transition_notification_move : ''"
|
||||
:enterActiveClass="prefer.s.animation ? $style.transition_notification_enterActive : ''"
|
||||
:leaveActiveClass="prefer.s.animation ? $style.transition_notification_leaveActive : ''"
|
||||
:enterFromClass="prefer.s.animation ? $style.transition_notification_enterFrom : ''"
|
||||
:leaveToClass="prefer.s.animation ? $style.transition_notification_leaveTo : ''"
|
||||
:moveClass="$style.transition_notification_move"
|
||||
:enterActiveClass="$style.transition_notification_enterActive"
|
||||
:leaveActiveClass="$style.transition_notification_leaveActive"
|
||||
:enterFromClass="$style.transition_notification_enterFrom"
|
||||
:leaveToClass="$style.transition_notification_leaveTo"
|
||||
>
|
||||
<div
|
||||
v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{
|
||||
|
|
@ -37,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<XNotification :notification="notification"/>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</component>
|
||||
|
||||
<XStreamIndicator/>
|
||||
|
||||
|
|
@ -51,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { defineAsyncComponent, ref, TransitionGroup } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { swInject } from './sw-inject.js';
|
||||
import XNotification from './notification.vue';
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="azykntjl">
|
||||
<div class="body">
|
||||
<div class="left">
|
||||
<button v-click-anime class="item _button instance" @click="openInstanceMenu">
|
||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" draggable="false"/>
|
||||
</button>
|
||||
<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
|
||||
<i class="ti ti-home ti-fw"></i>
|
||||
</MkA>
|
||||
<template v-for="item in menu">
|
||||
<div v-if="item === '-'" class="divider"></div>
|
||||
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
|
||||
<i class="ti-fw" :class="navbarItemDef[item].icon"></i>
|
||||
<span v-if="navbarItemDef[item].indicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
|
||||
</component>
|
||||
</template>
|
||||
<div class="divider"></div>
|
||||
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
|
||||
<i class="ti ti-dashboard ti-fw"></i>
|
||||
</MkA>
|
||||
<button v-click-anime class="item _button" @click="more">
|
||||
<i class="ti ti-dots ti-fw"></i>
|
||||
<span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="right">
|
||||
<MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
|
||||
<i class="ti ti-settings ti-fw"></i>
|
||||
</MkA>
|
||||
<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="os.post()">
|
||||
<MkButton class="button" gradate full rounded>
|
||||
<i class="ti ti-pencil ti-fw"></i>
|
||||
</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
|
||||
import { openInstanceMenu } from './_common_/common.js';
|
||||
import * as os from '@/os.js';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
const WINDOW_THRESHOLD = 1400;
|
||||
|
||||
const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
|
||||
const menu = ref(prefer.s.menu);
|
||||
// const menuDisplay = computed(store.makeGetterSetter('menuDisplay'));
|
||||
const otherNavItemIndicated = computed<boolean>(() => {
|
||||
for (const def in navbarItemDef) {
|
||||
if (menu.value.includes(def)) continue;
|
||||
if (navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
function more(ev: MouseEvent) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
anchor: { x: 'center', y: 'bottom' },
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
settingsWindowed.value = (window.innerWidth >= WINDOW_THRESHOLD);
|
||||
}, { passive: true });
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.azykntjl {
|
||||
$height: 60px;
|
||||
$avatar-size: 32px;
|
||||
$avatar-margin: 8px;
|
||||
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
height: $height;
|
||||
background-color: var(--MI_THEME-bg);
|
||||
|
||||
> .body {
|
||||
max-width: 1380px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
|
||||
> .right,
|
||||
> .left {
|
||||
|
||||
> .item {
|
||||
position: relative;
|
||||
font-size: 0.9em;
|
||||
display: inline-block;
|
||||
padding: 0 12px;
|
||||
line-height: $height;
|
||||
|
||||
> i,
|
||||
> .avatar {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> i {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
width: $avatar-size;
|
||||
height: $avatar-size;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--MI_THEME-navIndicator);
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: var(--MI_THEME-navHoverFg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--MI_THEME-navActive);
|
||||
}
|
||||
}
|
||||
|
||||
> .divider {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin: 0 10px;
|
||||
border-right: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
> .instance {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 56px;
|
||||
height: 100%;
|
||||
vertical-align: bottom;
|
||||
|
||||
> img {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
> .post {
|
||||
display: inline-block;
|
||||
|
||||
> .button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .account {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
vertical-align: top;
|
||||
margin-right: 8px;
|
||||
|
||||
> .acct {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .right {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="npcljfve" :class="{ iconOnly }">
|
||||
<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="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>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<MkA v-click-anime class="item index" activeClass="active" to="/" exact>
|
||||
<i class="ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
|
||||
</MkA>
|
||||
<template v-for="item in menu">
|
||||
<div v-if="item === '-'" class="divider"></div>
|
||||
<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
|
||||
<i class="ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span>
|
||||
<span v-if="navbarItemDef[item].indicated" class="indicator _blink">
|
||||
<span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span>
|
||||
<i v-else class="_indicatorCircle"></i>
|
||||
</span>
|
||||
</component>
|
||||
</template>
|
||||
<div class="divider"></div>
|
||||
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
|
||||
<i class="ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
|
||||
</MkA>
|
||||
<button v-click-anime class="item _button" @click="more">
|
||||
<i class="ti ti-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span>
|
||||
<span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span>
|
||||
</button>
|
||||
<MkA v-click-anime class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
|
||||
<i class="ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
|
||||
</MkA>
|
||||
<div class="divider"></div>
|
||||
<div class="about">
|
||||
<button v-click-anime class="item _button" @click="openInstanceMenu">
|
||||
<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" :class="{ wideIcon: instance.sidebarLogoUrl && !iconOnly }" class="_ghost" draggable="false" />
|
||||
</button>
|
||||
</div>
|
||||
<!--<MisskeyLogo class="misskey"/>-->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import { openInstanceMenu } from './_common_/common.js';
|
||||
// import { host } from '@@/js/config.js';
|
||||
import * as os from '@/os.js';
|
||||
import { navbarItemDef } from '@/navbar.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
// import { StickySidebar } from '@/utility/sticky-sidebar.js';
|
||||
// import { mainRouter } from '@/router.js';
|
||||
//import MisskeyLogo from '@assets/client/sharkey.svg';
|
||||
import { store } from '@/store.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { openAccountMenu as openAccountMenu_ } from '@/accounts.js';
|
||||
import { $i } from '@/i.js';
|
||||
|
||||
const WINDOW_THRESHOLD = 1400;
|
||||
|
||||
const menu = ref(prefer.s.menu);
|
||||
const otherNavItemIndicated = computed<boolean>(() => {
|
||||
for (const def in navbarItemDef) {
|
||||
if (menu.value.includes(def)) continue;
|
||||
if (navbarItemDef[def].indicated) return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
const el = useTemplateRef('el');
|
||||
// let accounts = $ref([]);
|
||||
// let connection = $ref(null);
|
||||
const iconOnly = ref(false);
|
||||
const settingsWindowed = ref(false);
|
||||
|
||||
function calcViewState() {
|
||||
iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (defaultStore.state.menuDisplay === 'sideIcon');
|
||||
settingsWindowed.value = (window.innerWidth > WINDOW_THRESHOLD);
|
||||
}
|
||||
|
||||
calcViewState();
|
||||
|
||||
function more(ev: MouseEvent) {
|
||||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkLaunchPad.vue')), {
|
||||
src: ev.currentTarget ?? ev.target,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
}
|
||||
|
||||
function openAccountMenu(ev: MouseEvent) {
|
||||
openAccountMenu_({
|
||||
withExtraOperation: true,
|
||||
}, ev);
|
||||
}
|
||||
|
||||
watch(store.r.menuDisplay, () => {
|
||||
calcViewState();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.npcljfve {
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$nav-icon-only-width: 78px; // TODO: どこかに集約したい
|
||||
$avatar-size: 32px;
|
||||
$avatar-margin: 8px;
|
||||
position: sticky;
|
||||
top: 16px;
|
||||
padding: 0 16px;
|
||||
box-sizing: border-box;
|
||||
width: 260px;
|
||||
|
||||
&.iconOnly {
|
||||
flex: 0 0 $nav-icon-only-width;
|
||||
width: $nav-icon-only-width !important;
|
||||
|
||||
> .divider {
|
||||
margin: 8px auto;
|
||||
width: calc(100% - 32px);
|
||||
}
|
||||
|
||||
> .post {
|
||||
> .button {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .item {
|
||||
padding-left: 0;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: $ui-font-size * 1.1;
|
||||
line-height: 3.7rem;
|
||||
|
||||
> i,
|
||||
> .avatar {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
> i {
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
> .text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .divider {
|
||||
margin: 10px 0;
|
||||
border-top: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
> .post {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 16px 0;
|
||||
|
||||
> .button {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> .about {
|
||||
fill: currentColor;
|
||||
padding: 8px 0 16px 0;
|
||||
text-align: center;
|
||||
|
||||
> .item {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 32px;
|
||||
&.wideIcon {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .item {
|
||||
position: relative;
|
||||
display: block;
|
||||
font-size: $ui-font-size;
|
||||
line-height: 2.6rem;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
box-sizing: border-box;
|
||||
|
||||
> i {
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
> i,
|
||||
> .avatar {
|
||||
margin-right: $avatar-margin;
|
||||
}
|
||||
|
||||
> .avatar {
|
||||
width: $avatar-size;
|
||||
height: $avatar-size;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--MI_THEME-navIndicator);
|
||||
font-size: 8px;
|
||||
|
||||
&:has(.itemIndicateValueIcon) {
|
||||
animation: none;
|
||||
left: auto;
|
||||
right: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: var(--MI_THEME-navHoverFg);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--MI_THEME-navActive);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,325 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="gbhvwtnk" :class="{ wallpaper }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`">
|
||||
<XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/>
|
||||
|
||||
<div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }">
|
||||
<div v-if="!showMenuOnTop" class="sidebar">
|
||||
<XSidebar/>
|
||||
</div>
|
||||
<div v-else-if="!pageMetadata?.needWideArea" ref="widgetsLeft" class="widgets left">
|
||||
<XWidgets place="left" :marginTop="'var(--MI-margin)'" @mounted="attachSticky(widgetsLeft)"/>
|
||||
</div>
|
||||
|
||||
<main class="main" @contextmenu.stop="onContextmenu">
|
||||
<div class="content" style="container-type: inline-size;">
|
||||
<RouterView/>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div v-if="isDesktop && !pageMetadata?.needWideArea" ref="widgetsRight" class="widgets right">
|
||||
<XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--MI-margin)'" @mounted="attachSticky(widgetsRight)"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition :name="prefer.s.animation ? 'tray-back' : ''">
|
||||
<div
|
||||
v-if="widgetsShowing"
|
||||
class="tray-back _modalBg"
|
||||
@click="widgetsShowing = false"
|
||||
@touchstart.passive="widgetsShowing = false"
|
||||
></div>
|
||||
</Transition>
|
||||
|
||||
<Transition :name="prefer.s.animation ? 'tray' : ''">
|
||||
<XWidgets v-if="widgetsShowing" class="tray"/>
|
||||
</Transition>
|
||||
|
||||
<iframe v-if="prefer.s.aiChanMode" ref="live2d" class="ivnzpscs" src="https://misskey-dev.github.io/mascot-web/?scale=2&y=1.4"></iframe>
|
||||
|
||||
<XCommon/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, onMounted, provide, ref, computed, useTemplateRef } from 'vue';
|
||||
import { instanceName } from '@@/js/config.js';
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
import XSidebar from './classic.sidebar.vue';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import type { PageMetadata } from '@/page.js';
|
||||
import { StickySidebar } from '@/utility/sticky-sidebar.js';
|
||||
import * as os from '@/os.js';
|
||||
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
|
||||
import { store } from '@/store.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { mainRouter } from '@/router.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
|
||||
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
|
||||
|
||||
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
|
||||
|
||||
const DESKTOP_THRESHOLD = 1100;
|
||||
|
||||
const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
|
||||
const pageMetadata = ref<null | PageMetadata>(null);
|
||||
const widgetsShowing = ref(false);
|
||||
const fullView = ref(false);
|
||||
const globalHeaderHeight = ref(0);
|
||||
const wallpaper = miLocalStorage.getItem('wallpaper') != null;
|
||||
const showMenuOnTop = computed(() => store.s.menuDisplay === 'top');
|
||||
const live2d = useTemplateRef('live2d');
|
||||
const widgetsLeft = ref<HTMLElement>();
|
||||
const widgetsRight = ref<HTMLElement>();
|
||||
|
||||
provide(DI.router, mainRouter);
|
||||
provideMetadataReceiver((metadataGetter) => {
|
||||
const info = metadataGetter();
|
||||
pageMetadata.value = info;
|
||||
if (pageMetadata.value) {
|
||||
if (isRoot.value && pageMetadata.value.title === instanceName) {
|
||||
window.document.title = pageMetadata.value.title;
|
||||
} else {
|
||||
window.document.title = `${pageMetadata.value.title} | ${instanceName}`;
|
||||
}
|
||||
}
|
||||
});
|
||||
provideReactiveMetadata(pageMetadata);
|
||||
provide('shouldHeaderThin', showMenuOnTop.value);
|
||||
provide('forceSpacerMin', true);
|
||||
|
||||
function attachSticky(el: HTMLElement) {
|
||||
const sticky = new StickySidebar(el, 0, store.s.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
|
||||
window.addEventListener('scroll', () => {
|
||||
sticky.calc(window.scrollY);
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
function top() {
|
||||
window.scroll({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function onContextmenu(ev: MouseEvent) {
|
||||
if (isLink(ev.target)) return;
|
||||
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
|
||||
if (window.getSelection().toString() !== '') return;
|
||||
const path = mainRouter.getCurrentFullPath();
|
||||
os.contextMenu([{
|
||||
type: 'label',
|
||||
text: path,
|
||||
}, {
|
||||
icon: fullView.value ? 'ti ti-minimize' : 'ti ti-maximize',
|
||||
text: fullView.value ? i18n.ts.quitFullView : i18n.ts.fullView,
|
||||
action: () => {
|
||||
fullView.value = !fullView.value;
|
||||
},
|
||||
}, {
|
||||
icon: 'ti ti-window-maximize',
|
||||
text: i18n.ts.openInWindow,
|
||||
action: () => {
|
||||
os.pageWindow(path);
|
||||
},
|
||||
}], ev);
|
||||
}
|
||||
|
||||
function onAiClick(ev) {
|
||||
//if (this.live2d) this.live2d.click(ev);
|
||||
}
|
||||
|
||||
if (window.innerWidth < 1024) {
|
||||
const currentUI = miLocalStorage.getItem('ui');
|
||||
miLocalStorage.setItem('ui_temp', currentUI ?? 'default');
|
||||
miLocalStorage.setItem('ui', 'default');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
window.document.documentElement.style.overflowY = 'scroll';
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', () => {
|
||||
isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
|
||||
}, { passive: true });
|
||||
|
||||
if (prefer.s.aiChanMode) {
|
||||
const iframeRect = live2d.value.getBoundingClientRect();
|
||||
window.addEventListener('mousemove', ev => {
|
||||
live2d.value.contentWindow.postMessage({
|
||||
type: 'moveCursor',
|
||||
body: {
|
||||
x: ev.clientX - iframeRect.left,
|
||||
y: ev.clientY - iframeRect.top,
|
||||
},
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
window.addEventListener('touchmove', ev => {
|
||||
live2d.value.contentWindow.postMessage({
|
||||
type: 'moveCursor',
|
||||
body: {
|
||||
x: ev.touches[0].clientX - iframeRect.left,
|
||||
y: ev.touches[0].clientY - iframeRect.top,
|
||||
},
|
||||
}, '*');
|
||||
}, { passive: true });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tray-enter-active,
|
||||
.tray-leave-active {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.tray-enter-from,
|
||||
.tray-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(240px);
|
||||
}
|
||||
|
||||
.tray-back-enter-active,
|
||||
.tray-back-leave-active {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
|
||||
}
|
||||
.tray-back-enter-from,
|
||||
.tray-back-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.gbhvwtnk {
|
||||
$ui-font-size: 1em;
|
||||
$widgets-hide-threshold: 1200px;
|
||||
|
||||
min-height: 100dvh;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.wallpaper {
|
||||
background: var(--MI_THEME-wallpaperOverlay);
|
||||
//backdrop-filter: var(--MI-blur, blur(4px));
|
||||
}
|
||||
|
||||
> .columns {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
max-width: 100%;
|
||||
//margin: 32px 0;
|
||||
|
||||
&.fullView {
|
||||
margin: 0;
|
||||
|
||||
> .sidebar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .widgets {
|
||||
display: none;
|
||||
}
|
||||
|
||||
> .main {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
> .main {
|
||||
min-width: 0;
|
||||
width: 750px;
|
||||
margin: 0 16px 0 0;
|
||||
border-left: solid 1px var(--MI_THEME-divider);
|
||||
border-right: solid 1px var(--MI_THEME-divider);
|
||||
border-radius: 0;
|
||||
overflow: clip;
|
||||
--MI-margin: 12px;
|
||||
}
|
||||
|
||||
> .widgets {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
padding-top: 16px;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
|
||||
@media (max-width: $widgets-hide-threshold) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.left {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
> .sidebar {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
&.withGlobalHeader {
|
||||
> .main {
|
||||
margin-top: 0;
|
||||
border: solid 1px var(--MI_THEME-divider);
|
||||
border-radius: var(--MI-radius);
|
||||
--MI-stickyTop: var(--globalHeaderHeight);
|
||||
}
|
||||
|
||||
> .widgets {
|
||||
--MI-stickyTop: var(--globalHeaderHeight);
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
margin: 0;
|
||||
|
||||
> .sidebar {
|
||||
border-right: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
|
||||
> .main {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .tray-back {
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
> .tray {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
z-index: 1001;
|
||||
height: 100dvh;
|
||||
padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px));
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
background: var(--MI_THEME-bg);
|
||||
}
|
||||
|
||||
> .ivnzpscs {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
height: 600px;
|
||||
border: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -228,28 +228,6 @@ async function deleteProfile() {
|
|||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#sharkey_app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
.transition_menuDrawerBg_enterActive,
|
||||
.transition_menuDrawerBg_leaveActive {
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ function onDrop(ev) {
|
|||
font-size: 0.9em;
|
||||
color: var(--MI_THEME-panelHeaderFg);
|
||||
background: var(--MI_THEME-panelHeaderBg);
|
||||
box-shadow: 0 1px 0 0 var(--MI_THEME-panelHeaderDivider);
|
||||
box-shadow: 0 0.5px 0 0 var(--MI_THEME-panelHeaderDivider);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<XAnnouncements v-if="$i"/>
|
||||
<XStatusBars :class="$style.statusbars"/>
|
||||
</div>
|
||||
<div :class="$style.content">
|
||||
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']"/>
|
||||
<RouterView v-else/>
|
||||
</div>
|
||||
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :class="$style.content"/>
|
||||
<RouterView v-else :class="$style.content"/>
|
||||
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
|
||||
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
|
||||
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
|
||||
|
|
@ -97,7 +95,6 @@ import { defineAsyncComponent, provide, onMounted, computed, ref, watch, useTemp
|
|||
import { instanceName } from '@@/js/config.js';
|
||||
import { isLink } from '@@/js/is-link.js';
|
||||
import XCommon from './_common_/common.vue';
|
||||
import type { Ref } from 'vue';
|
||||
import type { PageMetadata } from '@/page.js';
|
||||
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
|
@ -211,28 +208,6 @@ watch(navFooter, () => {
|
|||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
#sharkey_app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: clip;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" module>
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$widgets-hide-threshold: 1090px;
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) {
|
|||
if (claimedAchievements.includes(type)) return;
|
||||
claimingQueue.add(type);
|
||||
claimedAchievements.push(type);
|
||||
await new Promise(resolve => setTimeout(resolve, (claimingQueue.size - 1) * 500));
|
||||
await new Promise(resolve => window.setTimeout(resolve, (claimingQueue.size - 1) * 500));
|
||||
window.setTimeout(() => {
|
||||
claimingQueue.delete(type);
|
||||
}, 500);
|
||||
|
|
|
|||
|
|
@ -290,138 +290,128 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
},
|
||||
{
|
||||
id: 'lfI3yMX9g',
|
||||
label: i18n.ts.useBlurEffect,
|
||||
keywords: ['blur'],
|
||||
},
|
||||
{
|
||||
id: '31Y4IcGEf',
|
||||
label: i18n.ts.useBlurEffectForModal,
|
||||
keywords: ['blur', 'modal'],
|
||||
},
|
||||
{
|
||||
id: '78q2asrLS',
|
||||
label: i18n.ts.showAvatarDecorations,
|
||||
keywords: ['avatar', 'icon', 'decoration', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'zydOfGYip',
|
||||
id: '31Y4IcGEf',
|
||||
label: i18n.ts.alwaysConfirmFollow,
|
||||
keywords: ['follow', 'confirm', 'always'],
|
||||
},
|
||||
{
|
||||
id: 'wqpOC22Zm',
|
||||
id: '78q2asrLS',
|
||||
label: i18n.ts.highlightSensitiveMedia,
|
||||
keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
|
||||
},
|
||||
{
|
||||
id: 'c98gbF9c6',
|
||||
id: 'zydOfGYip',
|
||||
label: i18n.ts.confirmWhenRevealingSensitiveMedia,
|
||||
keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: '4LxdiOMNh',
|
||||
id: 'wqpOC22Zm',
|
||||
label: i18n.ts.enableAdvancedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced'],
|
||||
},
|
||||
{
|
||||
id: '9gTCaLkIf',
|
||||
id: 'c98gbF9c6',
|
||||
label: i18n.ts.enableInfiniteScroll,
|
||||
keywords: ['auto', 'load', 'auto', 'more', 'scroll'],
|
||||
},
|
||||
{
|
||||
id: 'jmJT0twuJ',
|
||||
id: '6ANRSOaNg',
|
||||
label: i18n.ts.emojiStyle,
|
||||
keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
|
||||
},
|
||||
{
|
||||
id: 'igFN7RIUa',
|
||||
label: i18n.ts.pinnedList,
|
||||
keywords: ['pinned', 'list'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.general,
|
||||
keywords: ['general'],
|
||||
},
|
||||
{
|
||||
id: 'ufc2X9voy',
|
||||
id: '5G6O6qdis',
|
||||
children: [
|
||||
{
|
||||
id: 'd2H4E5ys6',
|
||||
id: 'khT3n6byY',
|
||||
label: i18n.ts.showFixedPostForm,
|
||||
keywords: ['post', 'form', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '1LHOhDKGW',
|
||||
id: 'q5ElfNSou',
|
||||
label: i18n.ts.showFixedPostFormInChannel,
|
||||
keywords: ['post', 'form', 'timeline', 'channel'],
|
||||
},
|
||||
{
|
||||
id: 'DSzwvTp7i',
|
||||
id: '3GcWIaZf8',
|
||||
label: i18n.ts.collapseRenotes,
|
||||
keywords: ['renote', i18n.ts.collapseRenotesDescription],
|
||||
},
|
||||
{
|
||||
id: 'jb3HUeyrx',
|
||||
id: 'd2H4E5ys6',
|
||||
label: i18n.ts.showGapBetweenNotesInTimeline,
|
||||
keywords: ['note', 'timeline', 'gap'],
|
||||
},
|
||||
{
|
||||
id: '2LNjwv1cr',
|
||||
id: '1LHOhDKGW',
|
||||
label: i18n.ts.disableStreamingTimeline,
|
||||
keywords: ['disable', 'streaming', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '7W6g8Dcqz',
|
||||
id: 'DSzwvTp7i',
|
||||
label: i18n.ts.pinnedList,
|
||||
keywords: ['pinned', 'list'],
|
||||
},
|
||||
{
|
||||
id: 'ykifk3NHS',
|
||||
label: i18n.ts.showNoteActionsOnlyHover,
|
||||
keywords: ['hover', 'show', 'footer', 'action'],
|
||||
},
|
||||
{
|
||||
id: 'uAOoH3LFF',
|
||||
id: 'tLGyaQagB',
|
||||
label: i18n.ts.showClipButtonInNoteFooter,
|
||||
keywords: ['footer', 'action', 'clip', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'eCiyZLC8n',
|
||||
id: '7W6g8Dcqz',
|
||||
label: i18n.ts.showReactionsCount,
|
||||
keywords: ['reaction', 'count', 'show'],
|
||||
},
|
||||
{
|
||||
id: '68u9uRmFP',
|
||||
id: 'uAOoH3LFF',
|
||||
label: i18n.ts.confirmOnReact,
|
||||
keywords: ['reaction', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: 'rHWm4sXIe',
|
||||
id: 'eCiyZLC8n',
|
||||
label: i18n.ts.loadRawImages,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
|
||||
},
|
||||
{
|
||||
id: '9L2XGJw7e',
|
||||
id: '68u9uRmFP',
|
||||
label: i18n.ts.useReactionPickerForContextMenu,
|
||||
keywords: ['reaction', 'picker', 'contextmenu', 'open'],
|
||||
},
|
||||
{
|
||||
id: 'uIMCIK7kG',
|
||||
id: 'yxehrHZ6x',
|
||||
label: i18n.ts.reactionsDisplaySize,
|
||||
keywords: ['reaction', 'size', 'scale', 'display'],
|
||||
},
|
||||
{
|
||||
id: 'uMckjO9bz',
|
||||
id: 'gi8ILaE2Z',
|
||||
label: i18n.ts.limitWidthOfReaction,
|
||||
keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
|
||||
},
|
||||
{
|
||||
id: 'yeghU4qiH',
|
||||
id: 'cEQJZ7DQG',
|
||||
label: i18n.ts.mediaListWithOneImageAppearance,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
|
||||
},
|
||||
{
|
||||
id: 'yYSOPoAKE',
|
||||
id: 'haX4QVulD',
|
||||
label: i18n.ts.instanceTicker,
|
||||
keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
|
||||
},
|
||||
{
|
||||
id: 'iOHiIu32L',
|
||||
id: 'pneYnQekL',
|
||||
label: i18n.ts.displayOfSensitiveMedia,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
|
||||
},
|
||||
|
|
@ -430,25 +420,25 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['timeline', 'note'],
|
||||
},
|
||||
{
|
||||
id: 'eROFRMtXv',
|
||||
id: 'eJ2jme16W',
|
||||
children: [
|
||||
{
|
||||
id: 'BaQfrVO82',
|
||||
id: 'ErMQr6LQk',
|
||||
label: i18n.ts.keepCw,
|
||||
keywords: ['remember', 'keep', 'note', 'cw'],
|
||||
},
|
||||
{
|
||||
id: 'vFerPo2he',
|
||||
id: 'zrJicawH9',
|
||||
label: i18n.ts.rememberNoteVisibility,
|
||||
keywords: ['remember', 'keep', 'note', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: 'dcAC0yJcH',
|
||||
id: 'BaQfrVO82',
|
||||
label: i18n.ts.enableQuickAddMfmFunction,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
|
||||
},
|
||||
{
|
||||
id: 'bECeWZVMb',
|
||||
id: 'C2WYcVM1d',
|
||||
label: i18n.ts.defaultNoteVisibility,
|
||||
keywords: ['default', 'note', 'visibility'],
|
||||
},
|
||||
|
|
@ -457,20 +447,20 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['post', 'form'],
|
||||
},
|
||||
{
|
||||
id: 'tsSP93Cc6',
|
||||
id: 'sQXSA6gik',
|
||||
children: [
|
||||
{
|
||||
id: 'dtw8FepYL',
|
||||
id: 'rICn8stqk',
|
||||
label: i18n.ts.useGroupedNotifications,
|
||||
keywords: ['group'],
|
||||
},
|
||||
{
|
||||
id: 'eb0yCYJTn',
|
||||
id: 'xFmAg2tDe',
|
||||
label: i18n.ts.position,
|
||||
keywords: ['position'],
|
||||
},
|
||||
{
|
||||
id: '1Spt4Gpr5',
|
||||
id: 'Ek4Cw3VPq',
|
||||
label: i18n.ts.stackAxis,
|
||||
keywords: ['stack', 'axis', 'direction'],
|
||||
},
|
||||
|
|
@ -479,20 +469,15 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['notification'],
|
||||
},
|
||||
{
|
||||
id: 'SYmWxGOF',
|
||||
label: i18n.ts.dataSaver,
|
||||
keywords: ['datasaver'],
|
||||
},
|
||||
{
|
||||
id: 'vPQPvmntL',
|
||||
id: 'gDVCqZfxm',
|
||||
children: [
|
||||
{
|
||||
id: 'zZxyXHk3A',
|
||||
id: 'ei8Ix3s4S',
|
||||
label: i18n.ts._settings._chat.showSenderName,
|
||||
keywords: ['show', 'sender', 'name'],
|
||||
},
|
||||
{
|
||||
id: 'omEy5Q3Ev',
|
||||
id: '2E7vdIUQd',
|
||||
label: i18n.ts._settings._chat.sendOnEnter,
|
||||
keywords: ['send', 'enter', 'newline'],
|
||||
},
|
||||
|
|
@ -501,50 +486,139 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['chat', 'messaging'],
|
||||
},
|
||||
{
|
||||
id: '5fy7VEy6i',
|
||||
id: '96LnS1sxB',
|
||||
children: [
|
||||
{
|
||||
id: 'EosiWZvak',
|
||||
id: 'vPQPvmntL',
|
||||
label: i18n.ts.reduceUiAnimation,
|
||||
keywords: ['animation', 'motion', 'reduce'],
|
||||
},
|
||||
{
|
||||
id: 'wfJ91vwzq',
|
||||
label: i18n.ts.disableShowingAnimatedImages,
|
||||
keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'],
|
||||
},
|
||||
{
|
||||
id: '42b1L4xdq',
|
||||
label: i18n.ts.enableAnimatedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'animated'],
|
||||
},
|
||||
{
|
||||
id: 'dLkRNHn3k',
|
||||
label: i18n.ts.enableHorizontalSwipe,
|
||||
keywords: ['swipe', 'horizontal', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'BvooTWFW5',
|
||||
label: i18n.ts.keepScreenOn,
|
||||
keywords: ['keep', 'screen', 'display', 'on'],
|
||||
},
|
||||
{
|
||||
id: 'yzbghkAq0',
|
||||
label: i18n.ts.useNativeUIForVideoAudioPlayer,
|
||||
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
||||
},
|
||||
{
|
||||
id: 'aSbKFHbOy',
|
||||
label: i18n.ts._settings.makeEveryTextElementsSelectable,
|
||||
keywords: ['text', 'selectable'],
|
||||
},
|
||||
{
|
||||
id: 'bTcAsPvNz',
|
||||
label: i18n.ts.menuStyle,
|
||||
keywords: ['menu', 'style', 'popup', 'drawer'],
|
||||
},
|
||||
{
|
||||
id: 'lSVBaLnyW',
|
||||
label: i18n.ts._contextMenu.title,
|
||||
keywords: ['contextmenu', 'system', 'native'],
|
||||
},
|
||||
{
|
||||
id: 'pec0uMPq5',
|
||||
label: i18n.ts.fontSize,
|
||||
keywords: ['font', 'size'],
|
||||
},
|
||||
{
|
||||
id: 'Eh7vTluDO',
|
||||
label: i18n.ts.useSystemFont,
|
||||
keywords: ['font', 'system', 'native'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.accessibility,
|
||||
keywords: ['accessibility', i18n.ts._settings.accessibilityBanner],
|
||||
},
|
||||
{
|
||||
id: 'vTRSKf1JA',
|
||||
children: [
|
||||
{
|
||||
id: '2VjlA02wB',
|
||||
label: i18n.ts.turnOffToImprovePerformance,
|
||||
keywords: ['blur'],
|
||||
},
|
||||
{
|
||||
id: 'f6J0lmg1g',
|
||||
label: i18n.ts.turnOffToImprovePerformance,
|
||||
keywords: ['blur', 'modal'],
|
||||
},
|
||||
{
|
||||
id: 'hQqXhfNg8',
|
||||
label: i18n.ts.turnOffToImprovePerformance,
|
||||
keywords: ['sticky'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.performance,
|
||||
keywords: ['performance'],
|
||||
},
|
||||
{
|
||||
id: 'utM8dEobb',
|
||||
label: i18n.ts.dataSaver,
|
||||
keywords: ['datasaver'],
|
||||
},
|
||||
{
|
||||
id: 'gOUvwkE9t',
|
||||
children: [
|
||||
{
|
||||
id: 'iUMUvFURf',
|
||||
label: i18n.ts.squareAvatars,
|
||||
keywords: ['avatar', 'icon', 'square'],
|
||||
},
|
||||
{
|
||||
id: 'qY5xTzl35',
|
||||
id: 'ceyPO9Ywi',
|
||||
label: i18n.ts.seasonalScreenEffect,
|
||||
keywords: ['effect', 'show'],
|
||||
},
|
||||
{
|
||||
id: '2VSnj81vC',
|
||||
id: 'ztwIlsXhP',
|
||||
label: i18n.ts.openImageInNewTab,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'hdQa7W2H1',
|
||||
id: 'vLSsQbZEo',
|
||||
label: i18n.ts.withRepliesByDefaultForNewlyFollowed,
|
||||
keywords: ['follow', 'replies'],
|
||||
},
|
||||
{
|
||||
id: 'nnj4DkjhP',
|
||||
id: 'hQt85bBIX',
|
||||
label: i18n.ts.whenServerDisconnected,
|
||||
keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
|
||||
},
|
||||
{
|
||||
id: 'Eh7vTluDO',
|
||||
id: 'C9SyK2m0',
|
||||
label: i18n.ts.numberOfPageCache,
|
||||
keywords: ['cache', 'page'],
|
||||
},
|
||||
{
|
||||
id: 'vTRSKf1JA',
|
||||
id: '2U0iVUtfW',
|
||||
label: i18n.ts.forceShowAds,
|
||||
keywords: ['ad', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'dwhQfcLGt',
|
||||
id: '1rA7ADEXY',
|
||||
label: i18n.ts.hemisphere,
|
||||
keywords: [],
|
||||
},
|
||||
{
|
||||
id: 'Ar1lj7f7U',
|
||||
id: 'vRayx89Rt',
|
||||
label: i18n.ts.additionalEmojiDictionary,
|
||||
keywords: ['emoji', 'dictionary', 'additional', 'extra'],
|
||||
},
|
||||
|
|
@ -759,21 +833,16 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
},
|
||||
{
|
||||
id: 'goQdtf3dD',
|
||||
label: i18n.ts.keepOriginalUploading,
|
||||
keywords: ['keep', 'original', 'raw', 'upload', i18n.ts.keepOriginalUploadingDescription],
|
||||
},
|
||||
{
|
||||
id: '83xRo0XJl',
|
||||
label: i18n.ts.keepOriginalFilename,
|
||||
keywords: ['keep', 'original', 'filename', i18n.ts.keepOriginalFilenameDescription],
|
||||
},
|
||||
{
|
||||
id: 'wf77yRQQq',
|
||||
id: '83xRo0XJl',
|
||||
label: i18n.ts.alwaysMarkSensitive,
|
||||
keywords: ['always', 'default', 'mark', 'nsfw', 'sensitive', 'media', 'file'],
|
||||
},
|
||||
{
|
||||
id: '3pxwNB8e4',
|
||||
id: 'BrBqZL35E',
|
||||
label: i18n.ts.enableAutoSensitive,
|
||||
keywords: ['auto', 'nsfw', 'sensitive', 'media', 'file', i18n.ts.enableAutoSensitiveDescription],
|
||||
},
|
||||
|
|
@ -899,70 +968,6 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
path: '/settings/account-data',
|
||||
icon: 'ti ti-package',
|
||||
},
|
||||
{
|
||||
id: 'f08Mi1Uwn',
|
||||
children: [
|
||||
{
|
||||
id: 'C5dRH2Ypy',
|
||||
label: i18n.ts.reduceUiAnimation,
|
||||
keywords: ['animation', 'motion', 'reduce'],
|
||||
},
|
||||
{
|
||||
id: '5mZxz2cru',
|
||||
label: i18n.ts.disableShowingAnimatedImages,
|
||||
keywords: ['disable', 'animation', 'image', 'photo', 'picture', 'media', 'thumbnail', 'gif'],
|
||||
},
|
||||
{
|
||||
id: 'c0Iy5hL5o',
|
||||
label: i18n.ts.enableAnimatedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'animated'],
|
||||
},
|
||||
{
|
||||
id: '4HYFjs2Nv',
|
||||
label: i18n.ts.enableHorizontalSwipe,
|
||||
keywords: ['swipe', 'horizontal', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'kYVJ3SVNq',
|
||||
label: i18n.ts.keepScreenOn,
|
||||
keywords: ['keep', 'screen', 'display', 'on'],
|
||||
},
|
||||
{
|
||||
id: 'w4Bv0meAt',
|
||||
label: i18n.ts.useNativeUIForVideoAudioPlayer,
|
||||
keywords: ['native', 'system', 'video', 'audio', 'player', 'media'],
|
||||
},
|
||||
{
|
||||
id: 'b1GYEEJeh',
|
||||
label: i18n.ts._settings.makeEveryTextElementsSelectable,
|
||||
keywords: ['text', 'selectable'],
|
||||
},
|
||||
{
|
||||
id: 'vVLxwINTJ',
|
||||
label: i18n.ts.menuStyle,
|
||||
keywords: ['menu', 'style', 'popup', 'drawer'],
|
||||
},
|
||||
{
|
||||
id: '14cMhMLHL',
|
||||
label: i18n.ts._contextMenu.title,
|
||||
keywords: ['contextmenu', 'system', 'native'],
|
||||
},
|
||||
{
|
||||
id: 'oSo4LXMX9',
|
||||
label: i18n.ts.fontSize,
|
||||
keywords: ['font', 'size'],
|
||||
},
|
||||
{
|
||||
id: '7LQSAThST',
|
||||
label: i18n.ts.useSystemFont,
|
||||
keywords: ['font', 'system', 'native'],
|
||||
},
|
||||
],
|
||||
label: i18n.ts.accessibility,
|
||||
keywords: ['accessibility', i18n.ts._settings.accessibilityBanner],
|
||||
path: '/settings/accessibility',
|
||||
icon: 'ti ti-accessible',
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type SearchIndex = typeof searchIndexes;
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ export function confetti(options: { duration?: number; } = {}) {
|
|||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const interval = window.setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now();
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
return clearInterval(interval);
|
||||
return window.clearInterval(interval);
|
||||
}
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration);
|
||||
|
|
|
|||
|
|
@ -361,12 +361,18 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
|
|||
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
|
||||
os.post({ specified: user, initialText: `${canonical} ` });
|
||||
},
|
||||
}, {
|
||||
type: 'link',
|
||||
icon: 'ti ti-messages',
|
||||
text: i18n.ts._chat.chatWithThisUser,
|
||||
to: `/chat/user/${user.id}`,
|
||||
}, { type: 'divider' }, {
|
||||
});
|
||||
|
||||
if ($i.policies.canChat && user.canChat && user.host == null) {
|
||||
menuItems.push({
|
||||
type: 'link',
|
||||
icon: 'ti ti-messages',
|
||||
text: i18n.ts._chat.chatWithThisUser,
|
||||
to: `/chat/user/${user.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({ type: 'divider' }, {
|
||||
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
|
||||
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
||||
action: toggleMute,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { getHTMLElementOrNull } from "@/utility/get-dom-node-or-null.js";
|
||||
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
|
||||
|
||||
//#region types
|
||||
export type Keymap = Record<string, CallbackFunction | CallbackObject>;
|
||||
|
|
@ -136,7 +136,7 @@ let lastHotKeyStoreTimer: number | null = null;
|
|||
|
||||
const storePattern = (ev: KeyboardEvent, callback: CallbackFunction) => {
|
||||
if (lastHotKeyStoreTimer != null) {
|
||||
clearTimeout(lastHotKeyStoreTimer);
|
||||
window.clearTimeout(lastHotKeyStoreTimer);
|
||||
}
|
||||
|
||||
latestHotkey = {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.requestIdleCallback ?? ((callback) => {
|
||||
const start = performance.now();
|
||||
const timeoutId = setTimeout(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
callback({
|
||||
didTimeout: false, // polyfill でタイムアウト発火することはない
|
||||
timeRemaining() {
|
||||
|
|
@ -17,7 +17,7 @@ const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.re
|
|||
return timeoutId;
|
||||
});
|
||||
const cancelIdleCallback: typeof globalThis.cancelIdleCallback = globalThis.cancelIdleCallback ?? ((timeoutId) => {
|
||||
clearTimeout(timeoutId);
|
||||
window.clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
class IdlingRenderScheduler {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function chooseFileFromPc(
|
|||
},
|
||||
): Promise<Misskey.entities.DriveFile[]> {
|
||||
const uploadFolder = options?.uploadFolder ?? prefer.s.uploadFolder;
|
||||
const keepOriginal = options?.keepOriginal ?? prefer.s.keepOriginalUploading;
|
||||
const keepOriginal = options?.keepOriginal ?? false;
|
||||
const nameConverter = options?.nameConverter ?? (() => undefined);
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
|
|
@ -96,19 +96,17 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
|
|||
|
||||
function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
|
||||
return new Promise((res, rej) => {
|
||||
const keepOriginal = ref(prefer.s.keepOriginalUploading);
|
||||
|
||||
os.popupMenu([label ? {
|
||||
text: label,
|
||||
type: 'label',
|
||||
} : undefined, {
|
||||
type: 'switch',
|
||||
text: i18n.ts.keepOriginalUploading,
|
||||
ref: keepOriginal,
|
||||
text: i18n.ts.upload + ' (' + i18n.ts.compress + ')',
|
||||
icon: 'ti ti-upload',
|
||||
action: () => chooseFileFromPc(multiple, { keepOriginal: false }).then(files => res(files)),
|
||||
}, {
|
||||
text: i18n.ts.upload,
|
||||
icon: 'ti ti-upload',
|
||||
action: () => chooseFileFromPc(multiple, { keepOriginal: keepOriginal.value }).then(files => res(files)),
|
||||
action: () => chooseFileFromPc(multiple, { keepOriginal: true }).then(files => res(files)),
|
||||
}, {
|
||||
text: i18n.ts.fromDrive,
|
||||
icon: 'ti ti-cloud',
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export async function playMisskeySfxFile(soundStore: SoundStore): Promise<boolea
|
|||
canPlay = false;
|
||||
return await playMisskeySfxFileInternal(soundStore).finally(() => {
|
||||
// ごく短時間に音が重複しないように
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
canPlay = true;
|
||||
}, 25);
|
||||
});
|
||||
|
|
@ -230,10 +230,10 @@ export async function getSoundDuration(file: string): Promise<number> {
|
|||
const audioEl = window.document.createElement('audio');
|
||||
audioEl.src = file;
|
||||
return new Promise((resolve) => {
|
||||
const si = setInterval(() => {
|
||||
const si = window.setInterval(() => {
|
||||
if (audioEl.readyState > 0) {
|
||||
resolve(audioEl.duration * 1000);
|
||||
clearInterval(si);
|
||||
window.clearInterval(si);
|
||||
audioEl.remove();
|
||||
}
|
||||
}, 100);
|
||||
|
|
|
|||
|
|
@ -5,5 +5,5 @@
|
|||
|
||||
export async function tick(): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);
|
||||
await new Promise((globalThis.requestIdleCallback ?? window.setTimeout) as never);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export function uploadFile(
|
|||
file: File,
|
||||
folder?: string | Misskey.entities.DriveFolder | null,
|
||||
name?: string,
|
||||
keepOriginal: boolean = prefer.s.keepOriginalUploading,
|
||||
keepOriginal = false,
|
||||
): Promise<Misskey.entities.DriveFile> {
|
||||
if ($i == null) throw new Error('Not logged in');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue