Compare commits

..

2 Commits

Author SHA1 Message Date
syuilo
f9e2cd4088 Merge branch 'develop' into multi-server 2025-03-14 15:44:18 +09:00
syuilo
35a3acab42 init 2025-03-14 14:47:43 +09:00
24 changed files with 89 additions and 164 deletions

View File

@@ -6,7 +6,7 @@
### Client ### Client
- Feat: 設定の管理が強化されました - Feat: 設定の管理が強化されました
- 自動でバックアップされるように - 自動でバックアップされるように
- 任意の設定項目をデバイス間で同期できるように - 任意の設定項目をデバイス間で同期できるように(実験的)
- Enhance: プラグインの管理が強化されました - Enhance: プラグインの管理が強化されました
- インストール/アンインストール/設定の変更時にリロード不要になりました - インストール/アンインストール/設定の変更時にリロード不要になりました
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように

View File

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

View File

@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { watch, ref } from 'vue'; import { inject, watch, ref } from 'vue';
import XReaction from '@/components/EmReactionsViewer.reaction.vue'; import XReaction from '@/components/EmReactionsViewer.reaction.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@@ -22,6 +22,12 @@ const props = withDefaults(defineProps<{
maxNumber: Infinity, maxNumber: Infinity,
}); });
const mock = inject<boolean>('mock', false);
const emit = defineEmits<{
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
}>();
const initialReactions = new Set(Object.keys(props.note.reactions)); const initialReactions = new Set(Object.keys(props.note.reactions));
const reactions = ref<[string, number][]>([]); const reactions = ref<[string, number][]>([]);
@@ -32,8 +38,12 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
} }
function onMockToggleReaction(emoji: string, count: number) { function onMockToggleReaction(emoji: string, count: number) {
if (!mock) return;
const i = reactions.value.findIndex((item) => item[0] === emoji); const i = reactions.value.findIndex((item) => item[0] === emoji);
if (i < 0) return; if (i < 0) return;
emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
} }
watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {

View File

@@ -11,9 +11,18 @@ SPDX-License-Identifier: AGPL-3.0-only
@touchmove.passive="touchMove" @touchmove.passive="touchMove"
@touchend.passive="touchEnd" @touchend.passive="touchEnd"
> >
<Transition
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
:enterActiveClass="$style.swipeAnimation_enterActive"
:leaveActiveClass="$style.swipeAnimation_leaveActive"
:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
:style="`--swipe: ${pullDistance}px;`"
>
<!-- 注意slot内の最上位要素に動的にkeyを設定すること --> <!-- 注意slot内の最上位要素に動的にkeyを設定すること -->
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません --> <!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
<slot></slot> <slot></slot>
</Transition>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu"> <article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div> <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" :style="{ viewTransitionName: transitionName }"/> <MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<div :class="$style.main"> <div :class="$style.main">
<MkNoteHeader :note="appearNote" :mini="true"/> <MkNoteHeader :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/> <MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
@@ -177,7 +177,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, ref, shallowRef, watch, provide, reactive, nextTick } from 'vue'; import { computed, inject, onMounted, ref, shallowRef, watch, provide } from 'vue';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { isLink } from '@@/js/is-link.js'; import { isLink } from '@@/js/is-link.js';
@@ -223,8 +223,6 @@ import { focusPrev, focusNext } from '@/utility/focus.js';
import { getAppearNote } from '@/utility/get-appear-note.js'; import { getAppearNote } from '@/utility/get-appear-note.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js'; import { getPluginHandlers } from '@/plugin.js';
import { prepareViewTransition } from '@/page.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@@ -235,18 +233,7 @@ const props = withDefaults(defineProps<{
mock: false, mock: false,
}); });
const transitionNames = reactive({ provide('mock', props.mock);
avatar: '',
});
provide(DI.mock, props.mock);
provide(DI.navHook, (path, flag) => {
const names = prepareViewTransition(path);
transitionNames.avatar = names.avatar;
nextTick(() => {
router.push(path, flag);
});
});
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'reaction', emoji: string): void; (ev: 'reaction', emoji: string): void;
@@ -865,8 +852,6 @@ function emitUpdReaction(emoji: string, delta: number) {
position: sticky !important; position: sticky !important;
top: calc(22px + var(--MI-stickyTop, 0px)); top: calc(22px + var(--MI-stickyTop, 0px));
left: 0; left: 0;
contain: paint;
} }
.main { .main {

View File

@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<article :class="$style.note" @contextmenu.stop="onContextmenu"> <article :class="$style.note" @contextmenu.stop="onContextmenu">
<header :class="$style.noteHeader"> <header :class="$style.noteHeader">
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview :style="{ viewTransitionName: transitionName }"/> <MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
<div :class="$style.noteHeaderBody"> <div :class="$style.noteHeaderBody">
<div> <div>
<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)"> <MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
@@ -255,7 +255,6 @@ import { isEnabledUrlPreview } from '@/instance.js';
import { getAppearNote } from '@/utility/get-appear-note.js'; import { getAppearNote } from '@/utility/get-appear-note.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js'; import { getPluginHandlers } from '@/plugin.js';
import { prepareViewTransition } from '@/page.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@@ -264,8 +263,6 @@ const props = withDefaults(defineProps<{
initialTab: 'replies', initialTab: 'replies',
}); });
const transitionName = prepareViewTransition('note-noteDetailed', props.note.id).avatar;
const inChannel = inject('inChannel', null); const inChannel = inject('inChannel', null);
const note = ref(deepClone(props.note)); const note = ref(deepClone(props.note));
@@ -672,8 +669,6 @@ function loadConversation() {
flex-shrink: 0; flex-shrink: 0;
width: 58px; width: 58px;
height: 58px; height: 58px;
contain: paint;
} }
.noteHeaderBody { .noteHeaderBody {

View File

@@ -40,13 +40,12 @@ import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { notePage } from '@/filters/note.js'; import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js'; import { userPage } from '@/filters/user.js';
import { DI } from '@/di.js';
defineProps<{ defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
}>(); }>();
const mock = inject(DI.mock, false); const mock = inject<boolean>('mock', false);
</script> </script>
<style lang="scss" module> <style lang="scss" module>

View File

@@ -138,7 +138,6 @@ import { emojiPicker } from '@/utility/emoji-picker.js';
import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js'; import { mfmFunctionPicker } from '@/utility/mfm-function-picker.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { getPluginHandlers } from '@/plugin.js'; import { getPluginHandlers } from '@/plugin.js';
import { DI } from '@/di.js';
const $i = signinRequired(); const $i = signinRequired();
@@ -156,7 +155,7 @@ const props = withDefaults(defineProps<PostFormProps & {
initialLocalOnly: undefined, initialLocalOnly: undefined,
}); });
provide(DI.mock, props.mock); provide('mock', props.mock);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'posted'): void; (ev: 'posted'): void;

View File

@@ -42,7 +42,6 @@ import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -51,7 +50,7 @@ const props = defineProps<{
detachMediaFn?: (id: string) => void; detachMediaFn?: (id: string) => void;
}>(); }>();
const mock = inject(DI.mock, false); const mock = inject<boolean>('mock', false);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void; (ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;

View File

@@ -35,7 +35,6 @@ import * as sound from '@/utility/sound.js';
import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js'; import { checkReactionPermissions } from '@/utility/check-reaction-permissions.js';
import { customEmojisMap } from '@/custom-emojis.js'; import { customEmojisMap } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
const props = defineProps<{ const props = defineProps<{
reaction: string; reaction: string;
@@ -44,7 +43,7 @@ const props = defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
}>(); }>();
const mock = inject(DI.mock, false); const mock = inject<boolean>('mock', false);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'reactionToggled', emoji: string, newCount: number): void; (ev: 'reactionToggled', emoji: string, newCount: number): void;

View File

@@ -22,7 +22,6 @@ import * as Misskey from 'misskey-js';
import { inject, watch, ref } from 'vue'; import { inject, watch, ref } from 'vue';
import XReaction from '@/components/MkReactionsViewer.reaction.vue'; import XReaction from '@/components/MkReactionsViewer.reaction.vue';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
note: Misskey.entities.Note; note: Misskey.entities.Note;
@@ -31,7 +30,7 @@ const props = withDefaults(defineProps<{
maxNumber: Infinity, maxNumber: Infinity,
}); });
const mock = inject(DI.mock, false); const mock = inject<boolean>('mock', false);
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void; (ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;

View File

@@ -20,7 +20,6 @@ import * as os from '@/os.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { DI } from '@/di.js';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
to: string; to: string;
@@ -38,7 +37,6 @@ const el = shallowRef<HTMLElement>();
defineExpose({ $el: el }); defineExpose({ $el: el });
const router = useRouter(); const router = useRouter();
const navHook = inject(DI.navHook, null);
const active = computed(() => { const active = computed(() => {
if (props.activeClass == null) return false; if (props.activeClass == null) return false;
@@ -101,10 +99,6 @@ function nav(ev: MouseEvent) {
return openWindow(); return openWindow();
} }
if (navHook != null) {
navHook(props.to, ev.ctrlKey ? 'forcePage' : null);
} else {
router.push(props.to, ev.ctrlKey ? 'forcePage' : null); router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
}
} }
</script> </script>

View File

@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
:exclude="pageCacheController" :exclude="pageCacheController"
> >
<Suspense :timeout="0"> <Suspense :timeout="0">
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)" :style="{ viewTransitionName: viewId }"/> <component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
<template #fallback> <template #fallback>
<MkLoading/> <MkLoading/>
@@ -20,7 +20,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue'; import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
import { v4 as uuid } from 'uuid';
import type { IRouter, Resolved, RouteDef } from '@/nirax.js'; import type { IRouter, Resolved, RouteDef } from '@/nirax.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { globalEvents } from '@/events.js'; import { globalEvents } from '@/events.js';
@@ -41,12 +40,6 @@ if (router == null) {
const currentDepth = inject(DI.routerCurrentDepth, 0); const currentDepth = inject(DI.routerCurrentDepth, 0);
provide(DI.routerCurrentDepth, currentDepth + 1); provide(DI.routerCurrentDepth, currentDepth + 1);
const viewId = uuid();
provide(DI.viewId, viewId);
const viewTransitionId = ref(uuid());
provide(DI.viewTransitionId, viewTransitionId);
function resolveNested(current: Resolved, d = 0): Resolved | null { function resolveNested(current: Resolved, d = 0): Resolved | null {
if (!props.nested) return current; if (!props.nested) return current;
@@ -66,31 +59,19 @@ const currentPageComponent = shallowRef('component' in current.route ? current.r
const currentPageProps = ref(current.props); const currentPageProps = ref(current.props);
const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props))); const key = ref(router.getCurrentKey() + JSON.stringify(Object.fromEntries(current.props)));
async function onChange({ resolved, key: newKey }) { function onChange({ resolved, key: newKey }) {
const current = resolveNested(resolved); const current = resolveNested(resolved);
if (current == null || 'redirect' in current.route) return; if (current == null || 'redirect' in current.route) return;
viewTransitionId.value = uuid();
await nextTick();
nextTick(() => {
console.log('onChange', viewTransitionId.value);
document.startViewTransition(() => new Promise((res) => {
console.log('startViewTransition', viewTransitionId.value);
currentPageComponent.value = current.route.component; currentPageComponent.value = current.route.component;
currentPageProps.value = current.props; currentPageProps.value = current.props;
key.value = newKey + JSON.stringify(Object.fromEntries(current.props)); key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
nextTick(async () => { nextTick(() => {
//res();
setTimeout(res, 100);
// ページ遷移完了後に再びキャッシュを有効化 // ページ遷移完了後に再びキャッシュを有効化
if (clearCacheRequested.value) { if (clearCacheRequested.value) {
clearCacheRequested.value = false; clearCacheRequested.value = false;
} }
}); });
}));
});
} }
router.addListener('change', onChange); router.addListener('change', onChange);
@@ -119,31 +100,3 @@ onBeforeUnmount(() => {
router.removeListener('change', onChange); router.removeListener('change', onChange);
}); });
</script> </script>
<style lang="scss" scoped>
@keyframes fade-in {
from { opacity: 0; }
}
@keyframes fade-out {
to { opacity: 0; }
}
@keyframes slide-from-right {
from { transform: translateX(300px); }
}
@keyframes slide-to-left {
to { transform: translateX(-300px); }
}
::view-transition-old(v-bind(viewId)) {
animation: 90ms cubic-bezier(0.4, 0, 1, 1) both fade-out,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-to-left;
}
::view-transition-new(v-bind(viewId)) {
animation: 210ms cubic-bezier(0, 0, 0.2, 1) 90ms both fade-in,
300ms cubic-bezier(0.4, 0, 0.2, 1) both slide-from-right;
}
</style>

View File

@@ -4,13 +4,9 @@
*/ */
import type { InjectionKey, Ref } from 'vue'; import type { InjectionKey, Ref } from 'vue';
import type { IRouter, RouterFlag } from '@/nirax.js'; import type { IRouter } from '@/nirax.js';
export const DI = { export const DI = {
routerCurrentDepth: Symbol() as InjectionKey<number>, routerCurrentDepth: Symbol() as InjectionKey<number>,
router: Symbol() as InjectionKey<IRouter>, router: Symbol() as InjectionKey<IRouter>,
viewId: Symbol() as InjectionKey<string>,
viewTransitionId: Symbol() as InjectionKey<Ref<string>>,
mock: Symbol() as InjectionKey<boolean>,
navHook: Symbol() as InjectionKey<(path: string, flag?: RouterFlag) => void>,
}; };

View File

@@ -37,7 +37,7 @@ interface RouteDefWithRedirect extends RouteDefBase {
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect; export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
export type RouterFlag = 'forcePage' | null; export type RouterFlag = 'forcePage';
type ParsedPath = (string | { type ParsedPath = (string | {
name: string; name: string;

View File

@@ -4,9 +4,8 @@
*/ */
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { computed, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue'; import { inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
import type { MaybeRefOrGetter, Ref } from 'vue'; import type { MaybeRefOrGetter, Ref } from 'vue';
import { DI } from '@/di.js';
export type PageMetadata = { export type PageMetadata = {
title: string; title: string;
@@ -70,12 +69,3 @@ export const injectReactiveMetadata = (): Ref<PageMetadata | null> => {
const metadataRef = getMetadata(); const metadataRef = getMetadata();
return isRef(metadataRef) ? metadataRef : ref(null); return isRef(metadataRef) ? metadataRef : ref(null);
}; };
export function prepareViewTransition(type: string, id: string) {
const viewId = inject(DI.viewId);
const viewTransitionId = inject(DI.viewTransitionId);
return {
avatar: computed(() => 'adsfsdfsfg' + viewId + viewTransitionId.value + id),
//avatar: computed(() => 'adsfsdfsfg' + id),
};
}

View File

@@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="800"> <MkSpacer :contentMax="800">
<div> <div>
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
<div v-if="note"> <div v-if="note">
<div v-if="showNext" class="_margin"> <div v-if="showNext" class="_margin">
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/> <MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
@@ -40,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
<MkError v-else-if="error" @retry="fetchNote()"/> <MkError v-else-if="error" @retry="fetchNote()"/>
<MkLoading v-else/> <MkLoading v-else/>
</Transition>
</div> </div>
</MkSpacer> </MkSpacer>
</MkStickyContainer> </MkStickyContainer>

View File

@@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="800"> <MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs"> <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<div v-if="tab === 'all'" key="all"> <div v-if="tab === 'all'" key="all">
<div style="view-transition-name: a; contain: paint; margin: 64px;">BBBBBBBBB</div>
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/> <XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div> </div>
<div v-else-if="tab === 'mentions'" key="mention"> <div v-else-if="tab === 'mentions'" key="mention">
@@ -25,13 +24,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { notificationTypes } from '@@/js/const.js';
import XNotifications from '@/components/MkNotifications.vue'; import XNotifications from '@/components/MkNotifications.vue';
import MkNotes from '@/components/MkNotes.vue'; import MkNotes from '@/components/MkNotes.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js'; import { definePage } from '@/page.js';
import { notificationTypes } from '@@/js/const.js';
const tab = ref('all'); const tab = ref('all');
const includeTypes = ref<string[] | null>(null); const includeTypes = ref<string[] | null>(null);

View File

@@ -91,6 +91,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="skipNoteRender"> <MkSwitch v-model="skipNoteRender">
<template #label>Enable note render skipping</template> <template #label>Enable note render skipping</template>
</MkSwitch> </MkSwitch>
<MkSwitch v-model="multiServer">
<template #label>Enable multi server</template>
</MkSwitch>
</div> </div>
</MkFolder> </MkFolder>
</SearchMarker> </SearchMarker>
@@ -142,6 +145,7 @@ const reportError = prefer.model('reportError');
const enableCondensedLine = prefer.model('enableCondensedLine'); const enableCondensedLine = prefer.model('enableCondensedLine');
const skipNoteRender = prefer.model('skipNoteRender'); const skipNoteRender = prefer.model('skipNoteRender');
const devMode = prefer.model('devMode'); const devMode = prefer.model('devMode');
const multiServer = prefer.model('experimental.multiServer');
watch(skipNoteRender, async () => { watch(skipNoteRender, async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });

View File

@@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSpacer :contentMax="800"> <MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> <MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
<div :key="src" ref="rootEl"> <div :key="src" ref="rootEl">
<div style="view-transition-name: a; contain: paint;">AAAAAAAAAA</div>
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> <MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }} {{ i18n.ts._timelineDescription[src] }}
</MkInfo> </MkInfo>

View File

@@ -355,4 +355,8 @@ export const PREF_DEF = {
sfxVolume: 1, sfxVolume: 1,
}, },
}, },
'experimental.multiServer': {
default: false,
},
} satisfies PreferencesDefinition; } satisfies PreferencesDefinition;

View File

@@ -5,6 +5,7 @@
import { inject } from 'vue'; import { inject } from 'vue';
import type { IRouter } from '@/nirax.js'; import type { IRouter } from '@/nirax.js';
import { Router } from '@/nirax.js';
import { mainRouter } from '@/router/main.js'; import { mainRouter } from '@/router/main.js';
import { DI } from '@/di.js'; import { DI } from '@/di.js';
@@ -13,7 +14,7 @@ import { DI } from '@/di.js';
* あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない) * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない)
*/ */
export function useRouter(): IRouter { export function useRouter(): IRouter {
return inject(DI.router, null) ?? mainRouter; return inject<Router | null>(DI.router, null) ?? mainRouter;
} }
/** /**

View File

@@ -178,18 +178,7 @@ export const store = markRaw(new Storage('base', {
}, },
menu: { menu: {
where: 'deviceAccount', where: 'deviceAccount',
default: [ default: [],
'notifications',
'clips',
'drive',
'followRequests',
'-',
'explore',
'announcements',
'search',
'-',
'ui',
],
}, },
statusbars: { statusbars: {
where: 'deviceAccount', where: 'deviceAccount',

View File

@@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "misskey-js", "name": "misskey-js",
"version": "2025.3.2-beta.1", "version": "2025.3.2-beta.0",
"description": "Misskey SDK for JavaScript", "description": "Misskey SDK for JavaScript",
"license": "MIT", "license": "MIT",
"main": "./built/index.js", "main": "./built/index.js",