Compare commits
16 Commits
multi-serv
...
view-trans
Author | SHA1 | Date | |
---|---|---|---|
![]() |
76dc7affe0 | ||
![]() |
1c9d9923f4 | ||
![]() |
c8db2043b5 | ||
![]() |
2ddedd0ce6 | ||
![]() |
f40c5f27dd | ||
![]() |
386494dd6c | ||
![]() |
c88f5f5195 | ||
![]() |
30de6d80bb | ||
![]() |
63993dace6 | ||
![]() |
f5c946b44d | ||
![]() |
5fe23d3f69 | ||
![]() |
7d86efd087 | ||
![]() |
361f810da8 | ||
![]() |
be16622de2 | ||
![]() |
f930cd7842 | ||
![]() |
f1014bc7f7 |
@@ -6,7 +6,7 @@
|
|||||||
### Client
|
### Client
|
||||||
- Feat: 設定の管理が強化されました
|
- Feat: 設定の管理が強化されました
|
||||||
- 自動でバックアップされるように
|
- 自動でバックアップされるように
|
||||||
- 任意の設定項目をデバイス間で同期できるように(実験的)
|
- 任意の設定項目をデバイス間で同期できるように
|
||||||
- Enhance: プラグインの管理が強化されました
|
- Enhance: プラグインの管理が強化されました
|
||||||
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
||||||
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"version": "2025.3.2-beta.0",
|
"version": "2025.3.2-beta.1",
|
||||||
"codename": "nasubi",
|
"codename": "nasubi",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@@ -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 { inject, watch, ref } from 'vue';
|
import { 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,12 +22,6 @@ 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][]>([]);
|
||||||
@@ -38,12 +32,8 @@ 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]) => {
|
||||||
|
@@ -11,18 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
@touchmove.passive="touchMove"
|
@touchmove.passive="touchMove"
|
||||||
@touchend.passive="touchEnd"
|
@touchend.passive="touchEnd"
|
||||||
>
|
>
|
||||||
<Transition
|
<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
|
||||||
:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
|
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
||||||
:enterActiveClass="$style.swipeAnimation_enterActive"
|
<slot></slot>
|
||||||
: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を設定すること -->
|
|
||||||
<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
|
|
||||||
<slot></slot>
|
|
||||||
</Transition>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
@@ -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"/>
|
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock" :style="{ viewTransitionName: transitionName }"/>
|
||||||
<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 } from 'vue';
|
import { computed, inject, onMounted, ref, shallowRef, watch, provide, reactive, nextTick } 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,6 +223,8 @@ 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;
|
||||||
@@ -233,7 +235,18 @@ const props = withDefaults(defineProps<{
|
|||||||
mock: false,
|
mock: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
provide('mock', props.mock);
|
const transitionNames = reactive({
|
||||||
|
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;
|
||||||
@@ -852,6 +865,8 @@ 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 {
|
||||||
|
@@ -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/>
|
<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview :style="{ viewTransitionName: transitionName }"/>
|
||||||
<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,6 +255,7 @@ 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;
|
||||||
@@ -263,6 +264,8 @@ 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));
|
||||||
@@ -669,6 +672,8 @@ function loadConversation() {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 58px;
|
width: 58px;
|
||||||
height: 58px;
|
height: 58px;
|
||||||
|
|
||||||
|
contain: paint;
|
||||||
}
|
}
|
||||||
|
|
||||||
.noteHeaderBody {
|
.noteHeaderBody {
|
||||||
|
@@ -40,12 +40,13 @@ 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<boolean>('mock', false);
|
const mock = inject(DI.mock, false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@@ -138,6 +138,7 @@ 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();
|
||||||
|
|
||||||
@@ -155,7 +156,7 @@ const props = withDefaults(defineProps<PostFormProps & {
|
|||||||
initialLocalOnly: undefined,
|
initialLocalOnly: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
provide('mock', props.mock);
|
provide(DI.mock, props.mock);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'posted'): void;
|
(ev: 'posted'): void;
|
||||||
|
@@ -42,6 +42,7 @@ 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));
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ const props = defineProps<{
|
|||||||
detachMediaFn?: (id: string) => void;
|
detachMediaFn?: (id: string) => void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const mock = inject<boolean>('mock', false);
|
const mock = inject(DI.mock, false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
|
(ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void;
|
||||||
|
@@ -35,6 +35,7 @@ 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;
|
||||||
@@ -43,7 +44,7 @@ const props = defineProps<{
|
|||||||
note: Misskey.entities.Note;
|
note: Misskey.entities.Note;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const mock = inject<boolean>('mock', false);
|
const mock = inject(DI.mock, false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'reactionToggled', emoji: string, newCount: number): void;
|
(ev: 'reactionToggled', emoji: string, newCount: number): void;
|
||||||
|
@@ -22,6 +22,7 @@ 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;
|
||||||
@@ -30,7 +31,7 @@ const props = withDefaults(defineProps<{
|
|||||||
maxNumber: Infinity,
|
maxNumber: Infinity,
|
||||||
});
|
});
|
||||||
|
|
||||||
const mock = inject<boolean>('mock', false);
|
const mock = inject(DI.mock, false);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
|
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
|
||||||
|
@@ -20,6 +20,7 @@ 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;
|
||||||
@@ -37,6 +38,7 @@ 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;
|
||||||
@@ -99,6 +101,10 @@ function nav(ev: MouseEvent) {
|
|||||||
return openWindow();
|
return openWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
|
if (navHook != null) {
|
||||||
|
navHook(props.to, ev.ctrlKey ? 'forcePage' : null);
|
||||||
|
} else {
|
||||||
|
router.push(props.to, ev.ctrlKey ? 'forcePage' : null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@@ -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)"/>
|
<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)" :style="{ viewTransitionName: viewId }"/>
|
||||||
|
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<MkLoading/>
|
<MkLoading/>
|
||||||
@@ -20,6 +20,7 @@ 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';
|
||||||
@@ -40,6 +41,12 @@ 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;
|
||||||
|
|
||||||
@@ -59,18 +66,30 @@ 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)));
|
||||||
|
|
||||||
function onChange({ resolved, key: newKey }) {
|
async 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;
|
||||||
currentPageComponent.value = current.route.component;
|
|
||||||
currentPageProps.value = current.props;
|
|
||||||
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
|
|
||||||
|
|
||||||
|
viewTransitionId.value = uuid();
|
||||||
|
await nextTick();
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// ページ遷移完了後に再びキャッシュを有効化
|
console.log('onChange', viewTransitionId.value);
|
||||||
if (clearCacheRequested.value) {
|
document.startViewTransition(() => new Promise((res) => {
|
||||||
clearCacheRequested.value = false;
|
console.log('startViewTransition', viewTransitionId.value);
|
||||||
}
|
currentPageComponent.value = current.route.component;
|
||||||
|
currentPageProps.value = current.props;
|
||||||
|
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
|
||||||
|
|
||||||
|
nextTick(async () => {
|
||||||
|
//res();
|
||||||
|
setTimeout(res, 100);
|
||||||
|
|
||||||
|
// ページ遷移完了後に再びキャッシュを有効化
|
||||||
|
if (clearCacheRequested.value) {
|
||||||
|
clearCacheRequested.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,3 +119,31 @@ 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>
|
||||||
|
@@ -4,9 +4,13 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import type { InjectionKey, Ref } from 'vue';
|
import type { InjectionKey, Ref } from 'vue';
|
||||||
import type { IRouter } from '@/nirax.js';
|
import type { IRouter, RouterFlag } 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>,
|
||||||
};
|
};
|
||||||
|
@@ -37,7 +37,7 @@ interface RouteDefWithRedirect extends RouteDefBase {
|
|||||||
|
|
||||||
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
|
||||||
|
|
||||||
export type RouterFlag = 'forcePage';
|
export type RouterFlag = 'forcePage' | null;
|
||||||
|
|
||||||
type ParsedPath = (string | {
|
type ParsedPath = (string | {
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -4,8 +4,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
|
import { computed, 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;
|
||||||
@@ -69,3 +70,12 @@ 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@@ -8,40 +8,38 @@ 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"/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="_margin">
|
<div class="_margin">
|
||||||
<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
|
<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
|
||||||
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ti ti-chevron-up"></i> <i class="ti ti-device-tv"></i></MkButton>
|
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ti ti-chevron-up"></i> <i class="ti ti-device-tv"></i></MkButton>
|
||||||
<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="_margin _gaps_s">
|
<div class="_margin _gaps_s">
|
||||||
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
|
<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
|
||||||
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note"/>
|
<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="clips && clips.length > 0" class="_margin">
|
<div v-if="clips && clips.length > 0" class="_margin">
|
||||||
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
|
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
|
|
||||||
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ti ti-chevron-down"></i> <i class="ti ti-device-tv"></i></MkButton>
|
|
||||||
<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ti ti-chevron-down"></i> <i class="ti ti-user"></i></MkButton>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
|
||||||
<div v-if="showPrev" class="_margin">
|
<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ti ti-chevron-down"></i> <i class="ti ti-device-tv"></i></MkButton>
|
||||||
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ti ti-chevron-down"></i> <i class="ti ti-user"></i></MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
|
||||||
<MkLoading v-else/>
|
<div v-if="showPrev" class="_margin">
|
||||||
</Transition>
|
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||||
|
<MkLoading v-else/>
|
||||||
</div>
|
</div>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
|
@@ -9,6 +9,7 @@ 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">
|
||||||
@@ -24,13 +25,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);
|
||||||
|
@@ -9,6 +9,7 @@ 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>
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
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';
|
||||||
|
|
||||||
@@ -14,7 +13,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<Router | null>(DI.router, null) ?? mainRouter;
|
return inject(DI.router, null) ?? mainRouter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -178,7 +178,18 @@ 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',
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "misskey-js",
|
"name": "misskey-js",
|
||||||
"version": "2025.3.2-beta.0",
|
"version": "2025.3.2-beta.1",
|
||||||
"description": "Misskey SDK for JavaScript",
|
"description": "Misskey SDK for JavaScript",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
|
Reference in New Issue
Block a user