merge develop and fix conflicts.
This commit is contained in:
commit
1120ad19ae
166 changed files with 2933 additions and 1079 deletions
|
|
@ -23,10 +23,10 @@ import type MkNote from '@/components/MkNote.vue';
|
|||
import type SkNote from '@/components/SkNote.vue';
|
||||
import { prefer } from '@/preferences';
|
||||
|
||||
const XNote = computed(() =>
|
||||
prefer.r.noteDesign.value === 'misskey'
|
||||
? defineAsyncComponent(() => import('@/components/MkNote.vue'))
|
||||
: defineAsyncComponent(() => import('@/components/SkNote.vue')),
|
||||
const XNote = defineAsyncComponent(() =>
|
||||
prefer.s.noteDesign === 'misskey'
|
||||
? import('@/components/MkNote.vue')
|
||||
: import('@/components/SkNote.vue')
|
||||
);
|
||||
|
||||
const rootEl = useTemplateRef<ComponentExposed<typeof MkNote | typeof SkNote>>('rootEl');
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ import type MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
|||
import type SkNoteDetailed from '@/components/SkNoteDetailed.vue';
|
||||
import { prefer } from '@/preferences';
|
||||
|
||||
const XNoteDetailed = computed(() =>
|
||||
prefer.r.noteDesign.value === 'misskey'
|
||||
? defineAsyncComponent(() => import('@/components/MkNoteDetailed.vue'))
|
||||
: defineAsyncComponent(() => import('@/components/SkNoteDetailed.vue')),
|
||||
const XNoteDetailed = defineAsyncComponent(() =>
|
||||
prefer.s.noteDesign === 'misskey'
|
||||
? import('@/components/MkNoteDetailed.vue')
|
||||
: import('@/components/SkNoteDetailed.vue'),
|
||||
);
|
||||
|
||||
const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>('rootEl');
|
||||
|
|
|
|||
|
|
@ -21,10 +21,10 @@ import type MkNoteSimple from '@/components/MkNoteSimple.vue';
|
|||
import type SkNoteSimple from '@/components/SkNoteSimple.vue';
|
||||
import { prefer } from '@/preferences';
|
||||
|
||||
const XNoteSimple = computed(() =>
|
||||
prefer.r.noteDesign.value === 'misskey'
|
||||
? defineAsyncComponent(() => import('@/components/MkNoteSimple.vue'))
|
||||
: defineAsyncComponent(() => import('@/components/SkNoteSimple.vue')),
|
||||
const XNoteSimple = defineAsyncComponent(() =>
|
||||
prefer.s.noteDesign === 'misskey'
|
||||
? import('@/components/MkNoteSimple.vue')
|
||||
: import('@/components/SkNoteSimple.vue'),
|
||||
);
|
||||
|
||||
const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>('rootEl');
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { instance } from '@/instance.js';
|
|||
import { prefer } from '@/preferences.js';
|
||||
import { getDateText } from '@/utility/timeline-date-separate.js';
|
||||
import { $i } from '@/i.js';
|
||||
import SkTransitionGroup from '@/components/SkTransitionGroup.vue';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
|
|
@ -146,14 +147,12 @@ export default defineComponent({
|
|||
[$style['direction-up']]: props.direction === 'up',
|
||||
};
|
||||
|
||||
return () => prefer.s.animation ? h(TransitionGroup, {
|
||||
return () => h(SkTransitionGroup, {
|
||||
class: classes,
|
||||
name: 'list',
|
||||
tag: 'div',
|
||||
onBeforeLeave,
|
||||
onLeaveCancelled,
|
||||
}, { default: renderChildren }) : h('div', {
|
||||
class: classes,
|
||||
}, { default: renderChildren });
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import { i18n } from '@/i18n.js';
|
|||
import * as os from '@/os.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
import { instance } from '@/instance.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'closed'): void;
|
||||
|
|
@ -66,6 +67,7 @@ function close() {
|
|||
}
|
||||
|
||||
function neverShow() {
|
||||
prefer.commit('neverShowDonationInfo', 'true');
|
||||
miLocalStorage.setItem('neverShowDonationInfo', 'true');
|
||||
close();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div ref="rootEl" :class="$style.root" role="group" :aria-expanded="opened">
|
||||
<MkStickyContainer>
|
||||
<MkStickyContainer :sticky="sticky">
|
||||
<template #header>
|
||||
<button :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle">
|
||||
<div :class="$style.headerIcon"><slot name="icon"></slot></div>
|
||||
|
|
@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
>
|
||||
<KeepAlive>
|
||||
<div v-show="opened">
|
||||
<MkStickyContainer>
|
||||
<MkStickyContainer :sticky="sticky">
|
||||
<template #header>
|
||||
<div v-if="$slots.header" :class="$style.inBodyHeader">
|
||||
<slot name="header"></slot>
|
||||
|
|
@ -73,12 +73,14 @@ const props = withDefaults(defineProps<{
|
|||
withSpacer?: boolean;
|
||||
spacerMin?: number;
|
||||
spacerMax?: number;
|
||||
sticky?: boolean;
|
||||
}>(), {
|
||||
defaultOpen: false,
|
||||
maxHeight: null,
|
||||
withSpacer: true,
|
||||
spacerMin: 14,
|
||||
spacerMax: 22,
|
||||
sticky: true,
|
||||
});
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
|
|
|||
|
|
@ -154,13 +154,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.footerButton" @mousedown.prevent="translate()">
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
|
||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
|
|||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
||||
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||
import { focusPrev, focusNext } from '@/utility/focus.js';
|
||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
|
@ -360,7 +360,7 @@ const keymap = {
|
|||
clip();
|
||||
},
|
||||
't': () => {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||
translate();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -166,13 +166,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="translate()">
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()">
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -278,7 +278,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
|||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
||||
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getPluginHandlers } from '@/plugin.js';
|
||||
|
|
@ -388,7 +388,7 @@ const keymap = {
|
|||
clip();
|
||||
},
|
||||
't': () => {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||
translate();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
:style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''"
|
||||
@mousedown="renoted ? undoRenote() : boostVisibility($event.shiftKey)"
|
||||
@click.stop="renoted ? undoRenote() : boostVisibility($event.shiftKey)"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="note.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ note.renoteCount }}</p>
|
||||
|
|
@ -42,24 +42,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
ref="quoteButton"
|
||||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
@mousedown="quote()"
|
||||
@click.stop="quote()"
|
||||
>
|
||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction == null && note.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
|
||||
<button v-if="note.myReaction == null && note.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @click.stop="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
|
||||
<button v-if="note.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @click.stop="react()">
|
||||
<i v-if="note.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(note)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
||||
<i class="ph-dots-three ph-bold ph-lg"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -78,10 +84,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef, watch } from 'vue';
|
||||
import { computed, inject, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
|
||||
import * as config from '@@/js/config.js';
|
||||
import type { Ref } from 'vue';
|
||||
import type { Visibility } from '@/utility/boost-quote.js';
|
||||
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
|
||||
import MkNoteHeader from '@/components/MkNoteHeader.vue';
|
||||
|
|
@ -101,11 +108,12 @@ import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
|||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { reactionPicker } from '@/utility/reaction-picker.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { getNoteMenu } from '@/utility/get-note-menu.js';
|
||||
import { getNoteClipMenu, getNoteMenu, translateNote } from '@/utility/get-note-menu.js';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||
import { instance, policies } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
|
@ -128,6 +136,7 @@ const translating = ref(false);
|
|||
const isDeleted = ref(false);
|
||||
const renoted = ref(false);
|
||||
const reactButton = shallowRef<HTMLElement>();
|
||||
const clipButton = useTemplateRef('clipButton');
|
||||
const renoteButton = shallowRef<HTMLElement>();
|
||||
const quoteButton = shallowRef<HTMLElement>();
|
||||
const menuButton = shallowRef<HTMLElement>();
|
||||
|
|
@ -153,6 +162,8 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
|||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||
}));
|
||||
|
||||
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
|
||||
|
||||
async function addReplyTo(replyNote: Misskey.entities.Note) {
|
||||
replies.value.unshift(replyNote);
|
||||
appearNote.value.repliesCount += 1;
|
||||
|
|
@ -376,6 +387,14 @@ function menu(): void {
|
|||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
async function translate() {
|
||||
await translateNote(appearNote.value.id, translation, translating);
|
||||
}
|
||||
|
||||
if (props.detail) {
|
||||
misskeyApi('notes/children', {
|
||||
noteId: props.note.id,
|
||||
|
|
|
|||
|
|
@ -15,13 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #default="{ items: notes }">
|
||||
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: pagination.reversed }]">
|
||||
<template v-for="(note, i) in notes" :key="note.id">
|
||||
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
||||
<DynamicNote :class="$style.note" :note="note as Misskey.entities.Note" :withHardMute="true"/>
|
||||
<div :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
</div>
|
||||
<DynamicNote v-else :class="$style.note" :note="note as Misskey.entities.Note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||
<DynamicNote :class="$style.note" :note="note as Misskey.entities.Note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||
<MkAd v-if="note._shouldInsertAd_" :preferForms="['horizontal', 'horizontal-big']" :class="$style.ad"/>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -242,13 +242,18 @@ watch(props, async () => {
|
|||
const type = props.notification.type;
|
||||
|
||||
// To avoid extra lookups, only do the query when it actually matters.
|
||||
if (type === 'follow' || type === 'receiveFollowRequest') {
|
||||
const user = await misskeyApi('users/show', {
|
||||
userId: props.notification.userId,
|
||||
});
|
||||
if ((type === 'follow' || type === 'receiveFollowRequest') && props.notification.userId) {
|
||||
try {
|
||||
const user = await misskeyApi('users/show', {
|
||||
userId: props.notification.userId,
|
||||
});
|
||||
|
||||
userDetailed.value = user;
|
||||
followRequestDone.value = !user.hasPendingFollowRequestToYou;
|
||||
userDetailed.value = user;
|
||||
followRequestDone.value = !user.hasPendingFollowRequestToYou;
|
||||
} catch {
|
||||
userDetailed.value = null;
|
||||
followRequestDone.value = false;
|
||||
}
|
||||
} else {
|
||||
userDetailed.value = null;
|
||||
followRequestDone.value = false;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'" :class="[$style.notifications]"
|
||||
<SkTransitionGroup
|
||||
:class="[$style.notifications]"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
|
|
@ -23,11 +23,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:moveClass=" $style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<template v-for="(notification, i) in notifications" :key="notification.id">
|
||||
<div 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" :data-scroll-anchor="notification.id"/>
|
||||
<XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true" :data-scroll-anchor="notification.id"/>
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</SkTransitionGroup>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkPullToRefresh>
|
||||
|
|
@ -45,6 +45,7 @@ import { i18n } from '@/i18n.js';
|
|||
import { infoImageUrl } from '@/instance.js';
|
||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import SkTransitionGroup from '@/components/SkTransitionGroup.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
excludeTypes?: typeof notificationTypes[number][];
|
||||
|
|
|
|||
|
|
@ -557,6 +557,7 @@ async function toggleLocalOnly() {
|
|||
if (confirm.result === 'no') return;
|
||||
|
||||
if (confirm.result === 'neverShow') {
|
||||
prefer.commit('neverShowLocalOnlyInfo', 'true');
|
||||
miLocalStorage.setItem('neverShowLocalOnlyInfo', 'true');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||
<SkTransitionGroup
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
:enterFromClass="$style.transition_x_enterFrom"
|
||||
|
|
@ -14,8 +13,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
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"/>
|
||||
</component>
|
||||
<div v-if="hasMoreReactions" :key="'$more'" :class="$style.moreReactions">
|
||||
<slot name="more"/>
|
||||
</div>
|
||||
</SkTransitionGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -25,6 +26,7 @@ import { TransitionGroup } from 'vue';
|
|||
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { DI } from '@/di.js';
|
||||
import SkTransitionGroup from '@/components/SkTransitionGroup.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
|
@ -102,7 +104,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
|
|||
position: absolute;
|
||||
}
|
||||
|
||||
.root {
|
||||
.root, .moreReactions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
|
|
|
|||
|
|
@ -14,8 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
<component
|
||||
:is="prefer.s.animation ? TransitionGroup : 'div'"
|
||||
<SkTransitionGroup
|
||||
:class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap, [$style.reverse]: paginationQuery.reversed }]"
|
||||
:enterActiveClass="$style.transition_x_enterActive"
|
||||
:leaveActiveClass="$style.transition_x_leaveActive"
|
||||
|
|
@ -24,16 +23,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:moveClass=" $style.transition_x_move"
|
||||
tag="div"
|
||||
>
|
||||
<template v-for="(note, i) in notes" :key="note.id">
|
||||
<div v-if="note._shouldInsertAd_" :class="[$style.noteWithAd, { '_gaps': !noGap }]" :data-scroll-anchor="note.id">
|
||||
<DynamicNote :class="$style.note" :note="note" :withHardMute="true"/>
|
||||
<div :class="$style.ad">
|
||||
<MkAd :preferForms="['horizontal', 'horizontal-big']"/>
|
||||
</div>
|
||||
</div>
|
||||
<DynamicNote v-else :class="$style.note" :note="note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||
</template>
|
||||
</component>
|
||||
<div v-for="(note, i) in notes" :key="note.id" :class="{ '_gaps': !noGap }">
|
||||
<DynamicNote :class="$style.note" :note="note as Misskey.entities.Note" :withHardMute="true" :data-scroll-anchor="note.id"/>
|
||||
<MkAd v-if="note._shouldInsertAd_" :preferForms="['horizontal', 'horizontal-big']" :class="$style.ad"/>
|
||||
</div>
|
||||
</SkTransitionGroup>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkPullToRefresh>
|
||||
|
|
@ -54,6 +48,7 @@ import DynamicNote from '@/components/DynamicNote.vue';
|
|||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { infoImageUrl } from '@/instance.js';
|
||||
import SkTransitionGroup from '@/components/SkTransitionGroup.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
src: BasicTimelineType | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
|
||||
|
|
|
|||
84
packages/frontend/src/components/SkBadgeStrip.vue
Normal file
84
packages/frontend/src/components/SkBadgeStrip.vue
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="$style.badges">
|
||||
<div
|
||||
v-for="badge of badges"
|
||||
:key="badge.key"
|
||||
:class="[$style.badge, semanticClass(badge)]"
|
||||
>
|
||||
{{ badge.label }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export interface Badge {
|
||||
/**
|
||||
* ID/key of this badge, must be unique within the strip.
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* Label text to display.
|
||||
* Should already be translated.
|
||||
*/
|
||||
label: string;
|
||||
|
||||
/**
|
||||
* Semantic style of the badge.
|
||||
* Defaults to "neutral" if unset.
|
||||
*/
|
||||
style?: 'success' | 'neutral' | 'warning' | 'error';
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useCssModule } from 'vue';
|
||||
|
||||
const $style = useCssModule();
|
||||
|
||||
defineProps<{
|
||||
badges: Badge[],
|
||||
}>();
|
||||
|
||||
function semanticClass(badge: Badge): string {
|
||||
const style = badge.style ?? 'neutral';
|
||||
return $style[`semantic_${style}`];
|
||||
}
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
.badges {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--MI-margin);
|
||||
}
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
border: solid 1px;
|
||||
border-radius: var(--MI-radius-sm);
|
||||
padding: 2px 6px;
|
||||
font-size: 85%;
|
||||
}
|
||||
|
||||
.semantic_error {
|
||||
color: var(--MI_THEME-error);
|
||||
border-color: var(--MI_THEME-error);
|
||||
}
|
||||
|
||||
.semantic_warning {
|
||||
color: var(--MI_THEME-warn);
|
||||
border-color: var(--MI_THEME-warn);
|
||||
}
|
||||
|
||||
.semantic_success {
|
||||
color: var(--MI_THEME-success);
|
||||
border-color: var(--MI_THEME-success);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
<!-- TODO replace with SkDateSeparatedList when merged -->
|
||||
<MkDateSeparatedList v-slot="{ item: note }" :items="notes" :class="$style.panel" :noGap="true">
|
||||
<SkFollowingFeedEntry :note="note" :class="props.selectedUserId == note.userId && $style.selected" @select="u => selectUser(u.id)"/>
|
||||
</MkDateSeparatedList>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.unixtime }}</div>
|
||||
<div class="content">
|
||||
<p>{{ i18n.ts._mfm.unixtimeDescription }}</p>
|
||||
<div class="preview">
|
||||
<Mfm :text="preview_unixtime"/>
|
||||
<MkTextarea v-model="preview_unixtime"><template #label>MFM</template></MkTextarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section _block">
|
||||
<div class="title">{{ i18n.ts._mfm.inlineCode }}</div>
|
||||
<div class="content">
|
||||
|
|
@ -429,6 +439,9 @@ const preview_small = ref(
|
|||
const preview_center = ref(
|
||||
`<center>${i18n.ts._mfm.dummy}</center>`,
|
||||
);
|
||||
const preview_unixtime = ref(
|
||||
`$[unixtime ${Math.floor(Date.now() / 1000)}]`,
|
||||
);
|
||||
const preview_inlineCode = ref('`<: "Hello, world!"`');
|
||||
const preview_blockCode = ref(
|
||||
'```ai\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```',
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkUserName :user="note.user"/>
|
||||
</template>
|
||||
</I18n>
|
||||
<I18n v-else-if="prefer.s.showSoftWordMutedWord" :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<I18n v-else-if="!prefer.s.showSoftWordMutedWord" :src="i18n.ts.userSaysSomething" tag="small">
|
||||
<template #name>
|
||||
<MkUserName :user="note.user"/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -155,13 +155,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown.prevent="clip()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.footerButton" @mousedown.prevent="translate()">
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" class="_button" :class="$style.footerButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown.prevent="showMenu()">
|
||||
<button ref="menuButton" :class="$style.footerButton" class="_button" @click.stop="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -226,7 +226,7 @@ import { getNoteSummary } from '@/utility/get-note-summary.js';
|
|||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
||||
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||
import { focusPrev, focusNext } from '@/utility/focus.js';
|
||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
|
@ -360,7 +360,7 @@ const keymap = {
|
|||
clip();
|
||||
},
|
||||
't': () => {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||
translate();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -171,13 +171,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
<p v-if="(appearNote.reactionAcceptance === 'likeOnly' || prefer.s.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p>
|
||||
</button>
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="clip()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="translate()">
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown.prevent="showMenu()">
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="showMenu()">
|
||||
<i class="ti ti-dots"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -283,7 +283,7 @@ import MkPagination from '@/components/MkPagination.vue';
|
|||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { instance, isEnabledUrlPreview } from '@/instance.js';
|
||||
import { instance, isEnabledUrlPreview, policies } from '@/instance.js';
|
||||
import { getAppearNote } from '@/utility/get-appear-note.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { getPluginHandlers } from '@/plugin.js';
|
||||
|
|
@ -394,7 +394,7 @@ const keymap = {
|
|||
clip();
|
||||
},
|
||||
't': () => {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && $i?.policies.canUseTranslator && instance.translatorAvailable) {
|
||||
if (prefer.s.showTranslationButtonInNoteFooter && policies.value.canUseTranslator && instance.translatorAvailable) {
|
||||
translate();
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
:style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''"
|
||||
@mousedown="renoted ? undoRenote() : boostVisibility($event.shiftKey)"
|
||||
@click.stop="renoted ? undoRenote() : boostVisibility($event.shiftKey)"
|
||||
>
|
||||
<i class="ph-rocket-launch ph-bold ph-lg"></i>
|
||||
<p v-if="note.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ note.renoteCount }}</p>
|
||||
|
|
@ -50,24 +50,30 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
ref="quoteButton"
|
||||
class="_button"
|
||||
:class="$style.noteFooterButton"
|
||||
@mousedown="quote()"
|
||||
@click.stop="quote()"
|
||||
>
|
||||
<i class="ph-quotes ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-else class="_button" :class="$style.noteFooterButton" disabled>
|
||||
<i class="ph-prohibit ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction == null && note.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @mousedown="like()">
|
||||
<button v-if="note.myReaction == null && note.reactionAcceptance !== 'likeOnly'" ref="likeButton" :class="$style.noteFooterButton" class="_button" @click.stop="like()">
|
||||
<i class="ph-heart ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
|
||||
<button v-if="note.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @click.stop="react()">
|
||||
<i v-if="note.reactionAcceptance === 'likeOnly'" class="ph-heart ph-bold ph-lg"></i>
|
||||
<i v-else class="ph-smiley ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button v-if="note.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(note)">
|
||||
<i class="ph-minus ph-bold ph-lg"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
|
||||
<button v-if="prefer.s.showClipButtonInNoteFooter" ref="clipButton" :class="$style.noteFooterButton" class="_button" @click.stop="clip()">
|
||||
<i class="ti ti-paperclip"></i>
|
||||
</button>
|
||||
<button v-if="prefer.s.showTranslationButtonInNoteFooter && policies.canUseTranslator && instance.translatorAvailable" ref="translationButton" class="_button" :class="$style.noteFooterButton" :disabled="translating || !!translation" @click.stop="translate()">
|
||||
<i class="ti ti-language-hiragana"></i>
|
||||
</button>
|
||||
<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @click.stop="menu()">
|
||||
<i class="ph-dots-three ph-bold ph-lg"></i>
|
||||
</button>
|
||||
</footer>
|
||||
|
|
@ -86,10 +92,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, ref, shallowRef, watch } from 'vue';
|
||||
import { computed, inject, ref, shallowRef, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { computeMergedCw } from '@@/js/compute-merged-cw.js';
|
||||
import * as config from '@@/js/config.js';
|
||||
import type { Ref } from 'vue';
|
||||
import type { Visibility } from '@/utility/boost-quote.js';
|
||||
import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
|
||||
import SkNoteHeader from '@/components/SkNoteHeader.vue';
|
||||
|
|
@ -109,11 +116,12 @@ import { showMovedDialog } from '@/utility/show-moved-dialog.js';
|
|||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||
import { reactionPicker } from '@/utility/reaction-picker.js';
|
||||
import { claimAchievement } from '@/utility/achievements.js';
|
||||
import { getNoteMenu } from '@/utility/get-note-menu.js';
|
||||
import { getNoteClipMenu, getNoteMenu, translateNote } from '@/utility/get-note-menu.js';
|
||||
import { boostMenuItems, computeRenoteTooltip } from '@/utility/boost-quote.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import { useNoteCapture } from '@/use/use-note-capture.js';
|
||||
import SkMutedNote from '@/components/SkMutedNote.vue';
|
||||
import { instance, policies } from '@/instance';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
note: Misskey.entities.Note;
|
||||
|
|
@ -142,6 +150,7 @@ const translating = ref(false);
|
|||
const isDeleted = ref(false);
|
||||
const renoted = ref(false);
|
||||
const reactButton = shallowRef<HTMLElement>();
|
||||
const clipButton = useTemplateRef('clipButton');
|
||||
const renoteButton = shallowRef<HTMLElement>();
|
||||
const quoteButton = shallowRef<HTMLElement>();
|
||||
const menuButton = shallowRef<HTMLElement>();
|
||||
|
|
@ -167,6 +176,8 @@ const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
|||
url: appearNote.value.url ?? appearNote.value.uri ?? `${config.url}/notes/${appearNote.value.id}`,
|
||||
}));
|
||||
|
||||
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
|
||||
|
||||
async function addReplyTo(replyNote: Misskey.entities.Note) {
|
||||
replies.value.unshift(replyNote);
|
||||
appearNote.value.repliesCount += 1;
|
||||
|
|
@ -390,6 +401,14 @@ function menu(): void {
|
|||
os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
os.popupMenu(await getNoteClipMenu({ note: props.note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
|
||||
}
|
||||
|
||||
async function translate() {
|
||||
await translateNote(appearNote.value.id, translation, translating);
|
||||
}
|
||||
|
||||
if (props.detail) {
|
||||
misskeyApi('notes/children', {
|
||||
noteId: props.note.id,
|
||||
|
|
|
|||
43
packages/frontend/src/components/SkTransitionGroup.vue
Normal file
43
packages/frontend/src/components/SkTransitionGroup.vue
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<TransitionGroup v-if="animate ?? prefer.s.animation" v-bind="props" :class="props.class">
|
||||
<slot></slot>
|
||||
</TransitionGroup>
|
||||
<component :is="tag" v-else :class="props.class">
|
||||
<slot></slot>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { TransitionGroupProps } from 'vue';
|
||||
import { prefer } from '@/preferences';
|
||||
|
||||
// This is a "best guess" type.
|
||||
// If any valid :class binding produces a type error here, then please change this to match.
|
||||
type ClassBinding = string | Record<string, boolean | undefined>;
|
||||
|
||||
// This can be an inline type, but pulling it out makes TS errors clearer.
|
||||
interface SkTransitionGroupProps extends TransitionGroupProps {
|
||||
/**
|
||||
* Override CSS styles for the TransitionGroup or native element.
|
||||
*/
|
||||
class?: undefined | ClassBinding | ClassBinding[];
|
||||
|
||||
/**
|
||||
* If true, will render a TransitionGroup.
|
||||
* If false, will render a native element.
|
||||
* If null or undefined (default), will respect the value of prefer.s.animation.
|
||||
*/
|
||||
animate?: boolean | undefined | null;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<SkTransitionGroupProps>(), {
|
||||
tag: 'div',
|
||||
class: undefined,
|
||||
animate: undefined,
|
||||
});
|
||||
</script>
|
||||
|
|
@ -5,17 +5,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div ref="rootEl">
|
||||
<div ref="headerEl" :class="$style.header">
|
||||
<div ref="headerEl" :class="{ [$style.header]: sticky }">
|
||||
<slot name="header"></slot>
|
||||
</div>
|
||||
<div
|
||||
:class="$style.body"
|
||||
:class="{ [$style.body]: sticky }"
|
||||
:data-sticky-container-header-height="headerHeight"
|
||||
:data-sticky-container-footer-height="footerHeight"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div ref="footerEl" :class="$style.footer">
|
||||
<div ref="footerEl" :class="{ [$style.footer]: sticky }">
|
||||
<slot name="footer"></slot>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -25,6 +25,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { onMounted, onUnmounted, provide, inject, ref, watch, useTemplateRef } from 'vue';
|
||||
import { DI } from '@/di.js';
|
||||
|
||||
withDefaults(defineProps<{
|
||||
sticky?: boolean,
|
||||
}>(), {
|
||||
sticky: true,
|
||||
});
|
||||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const headerEl = useTemplateRef('headerEl');
|
||||
const footerEl = useTemplateRef('footerEl');
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@ 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 : ''"
|
||||
<SkTransitionGroup
|
||||
: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"
|
||||
:duration="200"
|
||||
tag="div" :class="$style.tabs"
|
||||
>
|
||||
|
|
@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
</SkTransitionGroup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -47,6 +47,7 @@ import { prefer } from '@/preferences.js';
|
|||
import MkLoadingPage from '@/pages/_loading_.vue';
|
||||
import { DI } from '@/di.js';
|
||||
import { deepEqual } from '@/utility/deep-equal.js';
|
||||
import SkTransitionGroup from '@/components/SkTransitionGroup.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
router?: Router;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue