Merge branch 'misskey-develop' into merge/2025-03-24

This commit is contained in:
Hazelnoot 2025-03-30 01:58:17 -04:00
commit 4ae26e6e18
113 changed files with 1303 additions and 1720 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -79,7 +79,7 @@ function opening() {
picker.value?.focus();
//
setTimeout(() => {
window.setTimeout(() => {
picker.value?.focus();
}, 10);
}

View file

@ -100,7 +100,7 @@ function touchMove(event: TouchEvent) {
pullDistance.value = 0;
isSwiping.value = false;
setTimeout(() => {
window.setTimeout(() => {
isSwipingForClass.value = false;
}, 400);

View file

@ -342,7 +342,7 @@ const bufferedDataRatio = computed(() => {
// MediaControl Events
function onMouseOver() {
if (controlStateTimer) {
clearTimeout(controlStateTimer);
window.clearTimeout(controlStateTimer);
}
isHoverring.value = true;
}

View file

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

View file

@ -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: TransitionGroupnote
// TransitionskipRender
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));
}
}
}

View file

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

View file

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

View file

@ -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);
// scrollToBottommoreFetching
// 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();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -133,7 +133,7 @@ async function enter(el: Element) {
entering = false;
});
setTimeout(renderTab, 170);
window.setTimeout(renderTab, 170);
}
function afterEnter(el: Element) {

View file

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

View file

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

View file

@ -44,7 +44,9 @@ provide(DI.routerCurrentDepth, currentDepth + 1);
const rootEl = useTemplateRef('rootEl');
onMounted(() => {
rootEl.value.style.viewTransitionName = viewId; // view-transition-namecss var使
if (prefer.s.animation) {
rootEl.value.style.viewTransitionName = viewId; // view-transition-namecss var使
}
});
// view-transition-new<pt-name-selector>css var使v-bind

View file

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

View file

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

View file

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

View file

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

View file

@ -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]+$/, ''),
},

View file

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

View file

@ -242,6 +242,10 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
font-size: 80%;
}
.fukidashi {
text-align: left;
}
.content {
overflow: clip;
overflow-wrap: break-word;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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']">

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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