mirror of
https://github.com/misskey-dev/misskey.git
synced 2026-05-21 08:45:34 +02:00
Feat: No websocket mode (#15851)
* wip * wip * wip * wip * Update MkTimeline.vue * wip * wip * wip * Update MkTimeline.vue * Update use-pagination.ts * wip * wip * Update MkTimeline.vue * Update MkTimeline.vue * wip * wip * Update MkTimeline.vue * Update MkTimeline.vue * Update MkTimeline.vue * wip * Update use-pagination.ts * wip * Update use-pagination.ts * Update MkNotifications.vue * Update MkNotifications.vue * wip * wip * wip * Update use-note-capture.ts * Update use-note-capture.ts * Update use-note-capture.ts * wip * wip * wip * wip * Update MkNoteDetailed.vue * wip * wip * Update MkTimeline.vue * wip * fix * Update MkTimeline.vue * wip * test * Revert "test" This reverts commit3375619396. * Update use-pagination.ts * test * Revert "test" This reverts commit42c53c830e. * test * Revert "test" This reverts commitc4f8cda4aa. * Update style.scss * Update MkTimeline.vue * Update MkTimeline.vue * Update MkTimeline.vue * ✌️ * Update MkTimeline.vue * wip * wip * test * Update MkPullToRefresh.vue * Update MkPullToRefresh.vue * Update MkPullToRefresh.vue * Update MkPullToRefresh.vue * Update MkTimeline.vue * wip * tweak navbar * wip * wip * wip * wip * wip * wip * wip * Update home.vue * wip * refactor * wip * wip * Update note.vue * Update navbar.vue * Update MkPullToRefresh.vue * Update MkPullToRefresh.vue * Update MkPullToRefresh.vue * wip * Update MkStreamingNotificationsTimeline.vue * Update use-pagination.ts * wip * improve perf * wip * Update MkNotesTimeline.vue * wip * megre * Update use-pagination.ts * Update use-pagination.ts * Update MkStreamingNotesTimeline.vue * Update use-pagination.ts * Update CHANGELOG.md * Update CHANGELOG.md * Update CHANGELOG.md
This commit is contained in:
@@ -55,7 +55,7 @@ import { computed, ref } from 'vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
|
||||
import FormSplit from '@/components/form/split.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -81,7 +81,7 @@ const pagination = {
|
||||
state.value === 'notResponding' ? { notResponding: true } :
|
||||
{}),
|
||||
})),
|
||||
} as Paging;
|
||||
} as PagingCtx;
|
||||
|
||||
function getStatus(instance) {
|
||||
if (instance.isSuspended) return 'Suspended';
|
||||
|
||||
@@ -87,7 +87,7 @@ const pagination = {
|
||||
};
|
||||
|
||||
function resolved(reportId) {
|
||||
reports.value?.removeItem(reportId);
|
||||
reports.value?.paginator.removeItem(reportId);
|
||||
}
|
||||
|
||||
function closeTutorial() {
|
||||
|
||||
@@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -73,7 +73,7 @@ const pagingComponent = useTemplateRef('pagingComponent');
|
||||
const type = ref('all');
|
||||
const sort = ref('+createdAt');
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination: PagingCtx = {
|
||||
endpoint: 'admin/invite/list' as const,
|
||||
limit: 10,
|
||||
params: computed(() => ({
|
||||
@@ -100,12 +100,12 @@ async function createWithOptions() {
|
||||
text: tickets.map(x => x.code).join('\n'),
|
||||
});
|
||||
|
||||
tickets.forEach(ticket => pagingComponent.value?.prepend(ticket));
|
||||
tickets.forEach(ticket => pagingComponent.value?.paginator.prepend(ticket));
|
||||
}
|
||||
|
||||
function deleted(id: string) {
|
||||
if (pagingComponent.value) {
|
||||
pagingComponent.value.items.delete(id);
|
||||
pagingComponent.value.paginator.removeItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ async function addUser() {
|
||||
username: username,
|
||||
password: password,
|
||||
}).then(res => {
|
||||
paginationComponent.value?.reload();
|
||||
paginationComponent.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, useTemplateRef } from 'vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
@@ -71,7 +71,7 @@ const paginationPast = {
|
||||
},
|
||||
};
|
||||
|
||||
const paginationEl = ref<InstanceType<typeof MkPagination>>();
|
||||
const paginationEl = useTemplateRef('paginationEl');
|
||||
|
||||
const tab = ref('current');
|
||||
|
||||
@@ -86,10 +86,10 @@ async function read(target) {
|
||||
}
|
||||
|
||||
if (!paginationEl.value) return;
|
||||
paginationEl.value.updateItem(target.id, a => {
|
||||
a.isRead = true;
|
||||
return a;
|
||||
});
|
||||
paginationEl.value.paginator.updateItem(target.id, a => ({
|
||||
...a,
|
||||
isRead: true,
|
||||
}));
|
||||
misskeyApi('i/read-announcement', { announcementId: target.id });
|
||||
updateCurrentAccountPartial({
|
||||
unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id),
|
||||
|
||||
@@ -6,17 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<div ref="rootEl">
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
<MkTimeline
|
||||
ref="tlEl" :key="antennaId"
|
||||
src="antenna"
|
||||
:antenna="antennaId"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.tl">
|
||||
<MkStreamingNotesTimeline
|
||||
ref="tlEl" :key="antennaId"
|
||||
src="antenna"
|
||||
:antenna="antennaId"
|
||||
:sound="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -25,8 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { definePage } from '@/page.js';
|
||||
@@ -40,18 +35,8 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const antenna = ref<Misskey.entities.Antenna | null>(null);
|
||||
const queue = ref(0);
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
const tlEl = useTemplateRef('tlEl');
|
||||
|
||||
function queueUpdated(q) {
|
||||
queue.value = q;
|
||||
}
|
||||
|
||||
function top() {
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
async function timetravel() {
|
||||
const { canceled, result: date } = await os.inputDate({
|
||||
title: i18n.ts.date,
|
||||
@@ -94,25 +79,6 @@ definePage(() => ({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.new {
|
||||
position: sticky;
|
||||
top: calc(var(--MI-stickyTop, 0px) + 16px);
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
margin: calc(-0.675em - 8px) 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: calc(-0.675em - 8px - var(--MI-margin));
|
||||
}
|
||||
}
|
||||
|
||||
.newButton {
|
||||
display: block;
|
||||
margin: var(--MI-margin) auto 0 auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
|
||||
.tl {
|
||||
background: var(--MI_THEME-bg);
|
||||
border-radius: var(--MI-radius);
|
||||
|
||||
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkWindow from '@/components/MkWindow.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
@@ -86,7 +86,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
||||
const windowEl = useTemplateRef('windowEl');
|
||||
const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : '');
|
||||
const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : '');
|
||||
const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : '');
|
||||
|
||||
@@ -37,10 +37,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
|
||||
<MkPostForm v-if="$i && prefer.r.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
|
||||
|
||||
<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
|
||||
<MkStreamingNotesTimeline :key="channelId" src="channel" :channel="channelId"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'featured'">
|
||||
<MkNotes :pagination="featuredPagination"/>
|
||||
<MkNotesTimeline :pagination="featuredPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'search'">
|
||||
<div v-if="notesSearchAvailable" class="_gaps">
|
||||
@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkInput>
|
||||
<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
|
||||
</div>
|
||||
<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
||||
<MkNotesTimeline v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
|
||||
@@ -73,9 +73,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import type { PageHeaderItem } from '@/types/page-header.js';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||
import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -83,7 +84,7 @@ import { $i, iAmModerator } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { deviceKind } from '@/utility/device-kind.js';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import { favoritedChannelsCache } from '@/cache.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
@@ -118,6 +119,14 @@ const featuredPagination = computed(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
useInterval(() => {
|
||||
if (channel.value == null) return;
|
||||
miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.value.id}`, Date.now());
|
||||
}, 3000, {
|
||||
immediate: true,
|
||||
afterMounted: true,
|
||||
});
|
||||
|
||||
watch(() => props.channelId, async () => {
|
||||
channel.value = await misskeyApi('channels/show', {
|
||||
channelId: props.channelId,
|
||||
|
||||
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkNotes :pagination="pagination" :detail="true"/>
|
||||
<MkNotesTimeline :pagination="pagination" :detail="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -34,7 +34,7 @@ import { computed, watch, provide, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { url } from '@@/js/config.js';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import { $i } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
@@ -115,7 +115,7 @@ const selectAll = () => {
|
||||
if (selectedEmojis.value.length > 0) {
|
||||
selectedEmojis.value = [];
|
||||
} else {
|
||||
selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id);
|
||||
selectedEmojis.value = emojisPaginationComponent.value?.paginator.items.value.map(item => item.id);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -132,7 +132,7 @@ const add = async (ev: MouseEvent) => {
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.created) {
|
||||
emojisPaginationComponent.value?.prepend(result.created);
|
||||
emojisPaginationComponent.value?.paginator.prepend(result.created);
|
||||
}
|
||||
},
|
||||
closed: () => dispose(),
|
||||
@@ -145,12 +145,12 @@ const edit = (emoji) => {
|
||||
}, {
|
||||
done: result => {
|
||||
if (result.updated) {
|
||||
emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({
|
||||
emojisPaginationComponent.value?.paginator.updateItem(result.updated.id, (oldEmoji) => ({
|
||||
...oldEmoji,
|
||||
...result.updated,
|
||||
}));
|
||||
} else if (result.deleted) {
|
||||
emojisPaginationComponent.value?.removeItem(emoji.id);
|
||||
emojisPaginationComponent.value?.paginator.removeItem(emoji.id);
|
||||
}
|
||||
},
|
||||
closed: () => dispose(),
|
||||
@@ -242,7 +242,7 @@ const setCategoryBulk = async () => {
|
||||
ids: selectedEmojis.value,
|
||||
category: result,
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const setLicenseBulk = async () => {
|
||||
@@ -254,7 +254,7 @@ const setLicenseBulk = async () => {
|
||||
ids: selectedEmojis.value,
|
||||
license: result,
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const addTagBulk = async () => {
|
||||
@@ -266,7 +266,7 @@ const addTagBulk = async () => {
|
||||
ids: selectedEmojis.value,
|
||||
aliases: result.split(' '),
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const removeTagBulk = async () => {
|
||||
@@ -278,7 +278,7 @@ const removeTagBulk = async () => {
|
||||
ids: selectedEmojis.value,
|
||||
aliases: result.split(' '),
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const setTagBulk = async () => {
|
||||
@@ -290,7 +290,7 @@ const setTagBulk = async () => {
|
||||
ids: selectedEmojis.value,
|
||||
aliases: result.split(' '),
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const delBulk = async () => {
|
||||
@@ -302,7 +302,7 @@ const delBulk = async () => {
|
||||
await os.apiWithDialog('admin/emoji/delete-bulk', {
|
||||
ids: selectedEmojis.value,
|
||||
});
|
||||
emojisPaginationComponent.value?.reload();
|
||||
emojisPaginationComponent.value?.paginator.reload();
|
||||
};
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
|
||||
@@ -6,16 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
|
||||
<MkNotes ref="tlComponent" :pagination="pagination"/>
|
||||
<MkNotesTimeline ref="tlComponent" :pagination="pagination"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
fileId: string;
|
||||
@@ -23,7 +23,7 @@ const props = defineProps<{
|
||||
|
||||
const realFileId = computed(() => props.fileId);
|
||||
|
||||
const pagination = ref<Paging>({
|
||||
const pagination = ref<PagingCtx>({
|
||||
endpoint: 'drive/files/attached-notes',
|
||||
limit: 10,
|
||||
params: {
|
||||
|
||||
@@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkWindow from '@/components/MkWindow.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
@@ -103,7 +103,7 @@ const emit = defineEmits<{
|
||||
(ev: 'closed'): void
|
||||
}>();
|
||||
|
||||
const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
|
||||
const windowEl = useTemplateRef('windowEl');
|
||||
const name = ref<string>(props.emoji ? props.emoji.name : '');
|
||||
const category = ref<string>(props.emoji?.category ? props.emoji.category : '');
|
||||
const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
|
||||
|
||||
@@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||
<option value="polls">{{ i18n.ts.poll }}</option>
|
||||
</MkTab>
|
||||
<MkNotes v-if="tab === 'notes'" :pagination="paginationForNotes"/>
|
||||
<MkNotes v-else-if="tab === 'polls'" :pagination="paginationForPolls"/>
|
||||
<MkNotesTimeline v-if="tab === 'notes'" :pagination="paginationForNotes"/>
|
||||
<MkNotesTimeline v-else-if="tab === 'polls'" :pagination="paginationForPolls"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { useTemplateRef, computed, ref } from 'vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { userPage, acct } from '@/filters/user.js';
|
||||
@@ -47,7 +47,7 @@ import { $i } from '@/i.js';
|
||||
|
||||
const paginationComponent = useTemplateRef('paginationComponent');
|
||||
|
||||
const pagination = computed<Paging>(() => tab.value === 'list' ? {
|
||||
const pagination = computed<PagingCtx>(() => tab.value === 'list' ? {
|
||||
endpoint: 'following/requests/list',
|
||||
limit: 10,
|
||||
} : {
|
||||
@@ -57,19 +57,19 @@ const pagination = computed<Paging>(() => tab.value === 'list' ? {
|
||||
|
||||
function accept(user: Misskey.entities.UserLite) {
|
||||
os.apiWithDialog('following/requests/accept', { userId: user.id }).then(() => {
|
||||
paginationComponent.value?.reload();
|
||||
paginationComponent.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function reject(user: Misskey.entities.UserLite) {
|
||||
os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => {
|
||||
paginationComponent.value?.reload();
|
||||
paginationComponent.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
function cancel(user: Misskey.entities.UserLite) {
|
||||
os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => {
|
||||
paginationComponent.value?.reload();
|
||||
paginationComponent.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref, computed, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { ChartSrc } from '@/components/MkChart.vue';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkChart from '@/components/MkChart.vue';
|
||||
import MkObjectView from '@/components/MkObjectView.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
@@ -180,7 +180,7 @@ const usersPagination = {
|
||||
hostname: props.host,
|
||||
},
|
||||
offsetMode: true,
|
||||
} satisfies Paging;
|
||||
} satisfies PagingCtx;
|
||||
|
||||
if (iAmModerator) {
|
||||
watch(moderationNote, async () => {
|
||||
|
||||
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -45,7 +45,7 @@ const currentInviteLimit = ref<null | number>(null);
|
||||
const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
|
||||
const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
|
||||
|
||||
const pagination: Paging = {
|
||||
const pagination: PagingCtx = {
|
||||
endpoint: 'invite/list' as const,
|
||||
limit: 10,
|
||||
};
|
||||
@@ -68,13 +68,13 @@ async function create() {
|
||||
text: ticket.code,
|
||||
});
|
||||
|
||||
pagingComponent.value?.prepend(ticket);
|
||||
pagingComponent.value?.paginator.prepend(ticket);
|
||||
update();
|
||||
}
|
||||
|
||||
function deleted(id: string) {
|
||||
if (pagingComponent.value) {
|
||||
pagingComponent.value.items.delete(id);
|
||||
pagingComponent.value.paginator.removeItem(id);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
@@ -73,15 +73,15 @@ async function create() {
|
||||
|
||||
clipsCache.delete();
|
||||
|
||||
pagingComponent.value?.reload();
|
||||
pagingComponent.value?.paginator.reload();
|
||||
}
|
||||
|
||||
function onClipCreated() {
|
||||
pagingComponent.value?.reload();
|
||||
pagingComponent.value?.paginator.reload();
|
||||
}
|
||||
|
||||
function onClipDeleted() {
|
||||
pagingComponent.value?.reload();
|
||||
pagingComponent.value?.paginator.reload();
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { computed, ref, useTemplateRef, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -80,7 +80,7 @@ const props = defineProps<{
|
||||
listId: string;
|
||||
}>();
|
||||
|
||||
const paginationEl = ref<InstanceType<typeof MkPagination>>();
|
||||
const paginationEl = useTemplateRef('paginationEl');
|
||||
const list = ref<Misskey.entities.UserList | null>(null);
|
||||
const isPublic = ref(false);
|
||||
const name = ref('');
|
||||
@@ -109,7 +109,7 @@ function addUser() {
|
||||
listId: list.value.id,
|
||||
userId: user.id,
|
||||
}).then(() => {
|
||||
paginationEl.value?.reload();
|
||||
paginationEl.value?.paginator.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -125,7 +125,7 @@ async function removeUser(item, ev) {
|
||||
listId: list.value.id,
|
||||
userId: item.userId,
|
||||
}).then(() => {
|
||||
paginationEl.value?.removeItem(item.id);
|
||||
paginationEl.value?.paginator.removeItem(item.id);
|
||||
});
|
||||
},
|
||||
}], ev.currentTarget ?? ev.target);
|
||||
@@ -147,7 +147,7 @@ async function showMembershipMenu(item, ev) {
|
||||
userId: item.userId,
|
||||
withReplies,
|
||||
}).then(() => {
|
||||
paginationEl.value!.updateItem(item.id, (old) => ({
|
||||
paginationEl.value!.paginator.updateItem(item.id, (old) => ({
|
||||
...old,
|
||||
withReplies,
|
||||
}));
|
||||
|
||||
@@ -6,42 +6,40 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<div>
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="note">
|
||||
<div v-if="showNext" class="_margin">
|
||||
<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
||||
</div>
|
||||
<Transition :name="prefer.s.animation ? 'fade' : ''" mode="out-in">
|
||||
<div v-if="note">
|
||||
<div v-if="showNext" class="_margin">
|
||||
<MkNotesTimeline :pullToRefresh="false" class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
|
||||
</div>
|
||||
|
||||
<div class="_margin">
|
||||
<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 rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
||||
</div>
|
||||
<div class="_margin _gaps_s">
|
||||
<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"/>
|
||||
</div>
|
||||
<div v-if="clips && clips.length > 0" class="_margin">
|
||||
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
||||
<div class="_gaps">
|
||||
<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 class="_margin">
|
||||
<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 rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
|
||||
</div>
|
||||
<div class="_margin _gaps_s">
|
||||
<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"/>
|
||||
</div>
|
||||
<div v-if="clips && clips.length > 0" class="_margin">
|
||||
<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
|
||||
<div class="_gaps">
|
||||
<MkClipPreview v-for="item in clips" :key="item.id" :clip="item"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="showPrev" class="_margin">
|
||||
<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
||||
<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>
|
||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||
<MkLoading v-else/>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<div v-if="showPrev" class="_margin">
|
||||
<MkNotesTimeline :pullToRefresh="false" class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetchNote()"/>
|
||||
<MkLoading v-else/>
|
||||
</Transition>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
@@ -50,9 +48,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { computed, watch, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { host } from '@@/js/config.js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -80,26 +78,27 @@ const showPrev = ref<'user' | 'channel' | false>(false);
|
||||
const showNext = ref<'user' | 'channel' | false>(false);
|
||||
const error = ref();
|
||||
|
||||
const prevUserPagination: Paging = {
|
||||
const prevUserPagination: PagingCtx = {
|
||||
endpoint: 'users/notes',
|
||||
limit: 10,
|
||||
baseId: props.noteId,
|
||||
direction: 'older',
|
||||
params: computed(() => note.value ? ({
|
||||
userId: note.value.userId,
|
||||
untilId: note.value.id,
|
||||
}) : undefined),
|
||||
};
|
||||
|
||||
const nextUserPagination: Paging = {
|
||||
reversed: true,
|
||||
const nextUserPagination: PagingCtx = {
|
||||
endpoint: 'users/notes',
|
||||
limit: 10,
|
||||
baseId: props.noteId,
|
||||
direction: 'newer',
|
||||
params: computed(() => note.value ? ({
|
||||
userId: note.value.userId,
|
||||
sinceId: note.value.id,
|
||||
}) : undefined),
|
||||
};
|
||||
|
||||
const prevChannelPagination: Paging = {
|
||||
const prevChannelPagination: PagingCtx = {
|
||||
endpoint: 'channels/timeline',
|
||||
limit: 10,
|
||||
params: computed(() => note.value ? ({
|
||||
@@ -108,7 +107,7 @@ const prevChannelPagination: Paging = {
|
||||
}) : undefined),
|
||||
};
|
||||
|
||||
const nextChannelPagination: Paging = {
|
||||
const nextChannelPagination: PagingCtx = {
|
||||
reversed: true,
|
||||
endpoint: 'channels/timeline',
|
||||
limit: 10,
|
||||
|
||||
@@ -7,13 +7,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs" :swipable="true">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<div v-if="tab === 'all'">
|
||||
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||
<MkStreamingNotificationsTimeline :class="$style.notifications" :excludeTypes="excludeTypes"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<MkNotes :pagination="mentionsPagination"/>
|
||||
<MkNotesTimeline :pagination="mentionsPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'directNotes'">
|
||||
<MkNotes :pagination="directNotesPagination"/>
|
||||
<MkNotesTimeline :pagination="directNotesPagination"/>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -22,8 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { notificationTypes } from '@@/js/const.js';
|
||||
import XNotifications from '@/components/MkNotifications.vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkStreamingNotificationsTimeline from '@/components/MkStreamingNotificationsTimeline.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { definePage } from '@/page.js';
|
||||
|
||||
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="tab === 'timeline'" class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||
<MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
||||
<MkStreamingNotesTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/>
|
||||
<MkResult v-else-if="!visible" type="empty" :text="i18n.ts.nothing"/>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -29,7 +29,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
roleId: string;
|
||||
|
||||
@@ -105,7 +105,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<MkFoldableSection v-if="notePagination">
|
||||
<template #header>{{ i18n.ts.searchResult }}</template>
|
||||
<MkNotes :key="`searchNotes:${key}`" :pagination="notePagination"/>
|
||||
<MkNotesTimeline :key="`searchNotes:${key}`" :pagination="notePagination"/>
|
||||
</MkFoldableSection>
|
||||
</div>
|
||||
</template>
|
||||
@@ -113,7 +113,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, shallowRef, toRef } from 'vue';
|
||||
import type * as Misskey from 'misskey-js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import { $i } from '@/i.js';
|
||||
import { host as localHost } from '@@/js/config.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -125,7 +125,7 @@ import { useRouter } from '@/router.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkUserCardMini from '@/components/MkUserCardMini.vue';
|
||||
|
||||
@@ -144,7 +144,7 @@ const props = withDefaults(defineProps<{
|
||||
const router = useRouter();
|
||||
|
||||
const key = ref(0);
|
||||
const notePagination = ref<Paging<'notes/search'>>();
|
||||
const notePagination = ref<PagingCtx<'notes/search'>>();
|
||||
|
||||
const searchQuery = ref(toRef(props, 'query').value);
|
||||
const hostInput = ref(toRef(props, 'host').value);
|
||||
|
||||
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { ref, toRef } from 'vue';
|
||||
import type { Endpoints } from 'misskey-js';
|
||||
import type { Paging } from '@/components/MkPagination.vue';
|
||||
import type { PagingCtx } from '@/use/use-pagination.js';
|
||||
import MkUserList from '@/components/MkUserList.vue';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
@@ -50,7 +50,7 @@ const props = withDefaults(defineProps<{
|
||||
const router = useRouter();
|
||||
|
||||
const key = ref(0);
|
||||
const userPagination = ref<Paging<'users/search'>>();
|
||||
const userPagination = ref<PagingCtx<'users/search'>>();
|
||||
|
||||
const searchQuery = ref(toRef(props, 'query').value);
|
||||
const searchOrigin = ref(toRef(props, 'origin').value);
|
||||
|
||||
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import FormPagination from '@/components/MkPagination.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
@@ -59,7 +59,7 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
|
||||
const list = ref<InstanceType<typeof FormPagination>>();
|
||||
const list = useTemplateRef('list');
|
||||
|
||||
const pagination = {
|
||||
endpoint: 'i/apps' as const,
|
||||
@@ -72,7 +72,7 @@ const pagination = {
|
||||
|
||||
function revoke(token) {
|
||||
misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
|
||||
list.value?.reload();
|
||||
list.value?.paginator.reload();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkRadios>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['realtimemode']">
|
||||
<MkSwitch v-model="realtimeMode">
|
||||
<template #label><i class="ti ti-bolt"></i> <SearchLabel>{{ i18n.ts.realtimeMode }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts._settings.realtimeMode_description }}</SearchKeyword></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
<MkDisableSection :disabled="realtimeMode">
|
||||
<SearchMarker :keywords="['polling', 'interval']">
|
||||
<MkPreferenceContainer k="pollingInterval">
|
||||
<MkRange v-model="pollingInterval" :min="1" :max="3" :step="1" easing :showTicks="true" :textConverter="(v) => v === 1 ? i18n.ts.low : v === 2 ? i18n.ts.middle : v === 3 ? i18n.ts.high : ''">
|
||||
<template #label><SearchLabel>{{ i18n.ts._settings.contentsUpdateFrequency }}</SearchLabel></template>
|
||||
<template #caption><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description }}</SearchKeyword><br><SearchKeyword>{{ i18n.ts._settings.contentsUpdateFrequency_description2 }}</SearchKeyword></template>
|
||||
<template #prefix><i class="ti ti-player-play"></i></template>
|
||||
<template #suffix><i class="ti ti-player-track-next"></i></template>
|
||||
</MkRange>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
</MkDisableSection>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<SearchMarker :keywords="['titlebar', 'show']">
|
||||
<MkPreferenceContainer k="showTitlebar">
|
||||
@@ -148,22 +168,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['note', 'timeline', 'gap']">
|
||||
<MkPreferenceContainer k="showGapBetweenNotesInTimeline">
|
||||
<MkSwitch v-model="showGapBetweenNotesInTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.showGapBetweenNotesInTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['disable', 'streaming', 'timeline']">
|
||||
<MkPreferenceContainer k="disableStreamingTimeline">
|
||||
<MkSwitch v-model="disableStreamingTimeline">
|
||||
<template #label><SearchLabel>{{ i18n.ts.disableStreamingTimeline }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</MkPreferenceContainer>
|
||||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['pinned', 'list']">
|
||||
<MkFolder>
|
||||
<template #label><SearchLabel>{{ i18n.ts.pinnedList }}</SearchLabel></template>
|
||||
@@ -734,7 +738,7 @@ import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkRange from '@/components/MkRange.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
import MkDisableSection from '@/components/MkDisableSection.vue';
|
||||
import FormLink from '@/components/form/link.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
@@ -757,8 +761,10 @@ const $i = ensureSignin();
|
||||
|
||||
const lang = ref(miLocalStorage.getItem('lang'));
|
||||
const dataSaver = ref(prefer.s.dataSaver);
|
||||
const realtimeMode = computed(store.makeGetterSetter('realtimeMode'));
|
||||
|
||||
const overridedDeviceKind = prefer.model('overridedDeviceKind');
|
||||
const pollingInterval = prefer.model('pollingInterval');
|
||||
const showTitlebar = prefer.model('showTitlebar');
|
||||
const keepCw = prefer.model('keepCw');
|
||||
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
|
||||
@@ -777,7 +783,6 @@ const showFixedPostFormInChannel = prefer.model('showFixedPostFormInChannel');
|
||||
const numberOfPageCache = prefer.model('numberOfPageCache');
|
||||
const enableInfiniteScroll = prefer.model('enableInfiniteScroll');
|
||||
const useReactionPickerForContextMenu = prefer.model('useReactionPickerForContextMenu');
|
||||
const disableStreamingTimeline = prefer.model('disableStreamingTimeline');
|
||||
const useGroupedNotifications = prefer.model('useGroupedNotifications');
|
||||
const alwaysConfirmFollow = prefer.model('alwaysConfirmFollow');
|
||||
const confirmWhenRevealingSensitiveMedia = prefer.model('confirmWhenRevealingSensitiveMedia');
|
||||
@@ -785,7 +790,6 @@ const confirmOnReact = prefer.model('confirmOnReact');
|
||||
const defaultNoteVisibility = prefer.model('defaultNoteVisibility');
|
||||
const defaultNoteLocalOnly = prefer.model('defaultNoteLocalOnly');
|
||||
const rememberNoteVisibility = prefer.model('rememberNoteVisibility');
|
||||
const showGapBetweenNotesInTimeline = prefer.model('showGapBetweenNotesInTimeline');
|
||||
const notificationPosition = prefer.model('notificationPosition');
|
||||
const notificationStackAxis = prefer.model('notificationStackAxis');
|
||||
const instanceTicker = prefer.model('instanceTicker');
|
||||
@@ -843,13 +847,13 @@ watch(useSystemFont, () => {
|
||||
watch([
|
||||
hemisphere,
|
||||
lang,
|
||||
realtimeMode,
|
||||
pollingInterval,
|
||||
enableInfiniteScroll,
|
||||
showNoteActionsOnlyHover,
|
||||
overridedDeviceKind,
|
||||
disableStreamingTimeline,
|
||||
alwaysConfirmFollow,
|
||||
confirmWhenRevealingSensitiveMedia,
|
||||
showGapBetweenNotesInTimeline,
|
||||
mediaListWithOneImageAppearance,
|
||||
reactionsDisplaySize,
|
||||
limitWidthOfReaction,
|
||||
|
||||
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<MkNotes ref="notes" class="" :pagination="pagination"/>
|
||||
<MkNotesTimeline ref="tlComponent" class="" :pagination="pagination"/>
|
||||
</div>
|
||||
<template v-if="$i" #footer>
|
||||
<div :class="$style.footer">
|
||||
@@ -19,8 +19,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import { computed, ref, useTemplateRef } from 'vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -40,7 +40,8 @@ const pagination = {
|
||||
tag: props.tag,
|
||||
})),
|
||||
};
|
||||
const notes = ref<InstanceType<typeof MkNotes>>();
|
||||
|
||||
const tlComponent = useTemplateRef('tlComponent');
|
||||
|
||||
async function post() {
|
||||
store.set('postFormHashtags', props.tag);
|
||||
@@ -48,7 +49,7 @@ async function post() {
|
||||
await os.post();
|
||||
store.set('postFormHashtags', '');
|
||||
store.set('postFormWithHashtags', false);
|
||||
notes.value?.pagingComponent?.reload();
|
||||
tlComponent.value?.reload();
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
|
||||
@@ -4,14 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<PageWithHeader ref="pageComponent" v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :swipable="true" :displayMyAvatar="true">
|
||||
<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :swipable="true" :displayMyAvatar="true">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
|
||||
{{ i18n.ts._timelineDescription[src] }}
|
||||
</MkInfo>
|
||||
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<MkTimeline
|
||||
<MkStreamingNotesTimeline
|
||||
ref="tlComponent"
|
||||
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
|
||||
:class="$style.tl"
|
||||
@@ -22,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
:withSensitive="withSensitive"
|
||||
:onlyFiles="onlyFiles"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
/>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -33,7 +31,7 @@ import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated }
|
||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import type { BasicTimelineType } from '@/timelines.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import MkPostForm from '@/components/MkPostForm.vue';
|
||||
import * as os from '@/os.js';
|
||||
@@ -51,11 +49,9 @@ import { prefer } from '@/preferences.js';
|
||||
provide('shouldOmitHeaderTitle', true);
|
||||
|
||||
const tlComponent = useTemplateRef('tlComponent');
|
||||
const pageComponent = useTemplateRef('pageComponent');
|
||||
|
||||
type TimelinePageSrc = BasicTimelineType | `list:${string}`;
|
||||
|
||||
const queue = ref(0);
|
||||
const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
|
||||
const src = computed<TimelinePageSrc>({
|
||||
get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value),
|
||||
@@ -110,18 +106,6 @@ const withSensitive = computed<boolean>({
|
||||
set: (x) => saveTlFilter('withSensitive', x),
|
||||
});
|
||||
|
||||
watch(src, () => {
|
||||
queue.value = 0;
|
||||
});
|
||||
|
||||
function queueUpdated(q: number): void {
|
||||
queue.value = q;
|
||||
}
|
||||
|
||||
function top(): void {
|
||||
if (pageComponent.value) pageComponent.value.scrollToTop();
|
||||
}
|
||||
|
||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||
const lists = await userListsCache.fetch();
|
||||
const items: MenuItem[] = [
|
||||
|
||||
@@ -6,17 +6,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<div ref="rootEl">
|
||||
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
|
||||
<div :class="$style.tl">
|
||||
<MkTimeline
|
||||
ref="tlEl" :key="listId"
|
||||
src="list"
|
||||
:list="listId"
|
||||
:sound="true"
|
||||
@queue="queueUpdated"
|
||||
/>
|
||||
</div>
|
||||
<div :class="$style.tl">
|
||||
<MkStreamingNotesTimeline
|
||||
ref="tlEl" :key="listId"
|
||||
src="list"
|
||||
:list="listId"
|
||||
:sound="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
@@ -25,8 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, ref, useTemplateRef } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { scrollInContainer } from '@@/js/scroll.js';
|
||||
import MkTimeline from '@/components/MkTimeline.vue';
|
||||
import MkStreamingNotesTimeline from '@/components/MkStreamingNotesTimeline.vue';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
@@ -39,9 +34,6 @@ const props = defineProps<{
|
||||
}>();
|
||||
|
||||
const list = ref<Misskey.entities.UserList | null>(null);
|
||||
const queue = ref(0);
|
||||
const tlEl = useTemplateRef('tlEl');
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
|
||||
watch(() => props.listId, async () => {
|
||||
list.value = await misskeyApi('users/lists/show', {
|
||||
@@ -49,14 +41,6 @@ watch(() => props.listId, async () => {
|
||||
});
|
||||
}, { immediate: true });
|
||||
|
||||
function queueUpdated(q) {
|
||||
queue.value = q;
|
||||
}
|
||||
|
||||
function top() {
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
function settings() {
|
||||
router.push(`/my/lists/${props.listId}`);
|
||||
}
|
||||
@@ -76,25 +60,6 @@ definePage(() => ({
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.new {
|
||||
position: sticky;
|
||||
top: calc(var(--MI-stickyTop, 0px) + 16px);
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
margin: calc(-0.675em - 8px) 0;
|
||||
|
||||
&:first-child {
|
||||
margin-top: calc(-0.675em - 8px - var(--MI-margin));
|
||||
}
|
||||
}
|
||||
|
||||
.newButton {
|
||||
display: block;
|
||||
margin: var(--MI-margin) auto 0 auto;
|
||||
padding: 8px 16px;
|
||||
border-radius: 32px;
|
||||
}
|
||||
|
||||
.tl {
|
||||
background: var(--MI_THEME-bg);
|
||||
border-radius: var(--MI-radius);
|
||||
|
||||
@@ -4,15 +4,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_spacer" style="--MI_SPACER-w: 1100px;">
|
||||
<div :class="$style.root">
|
||||
<MkPagination v-slot="{items}" :pagination="pagination">
|
||||
<div :class="$style.stream">
|
||||
<MkNoteMediaGrid v-for="note in items" :note="note" square/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div class="_spacer" style="--MI_SPACER-w: 1100px;">
|
||||
<div :class="$style.root">
|
||||
<MkPagination v-slot="{items}" :pagination="pagination">
|
||||
<div :class="$style.stream">
|
||||
<MkNoteMediaGrid v-for="note in items" :note="note" square/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -4,158 +4,160 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_spacer" :style="{ '--MI_SPACER-w': narrow ? '800px' : '1100px' }">
|
||||
<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
|
||||
<div class="main _gaps">
|
||||
<!-- TODO -->
|
||||
<!-- <div class="punished" v-if="user.isSuspended"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSuspended }}</div> -->
|
||||
<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
|
||||
<component :is="prefer.s.enablePullToRefresh ? MkPullToRefresh : 'div'" :refresher="() => reload()">
|
||||
<div class="_spacer" :style="{ '--MI_SPACER-w': narrow ? '800px' : '1100px' }">
|
||||
<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
|
||||
<div class="main _gaps">
|
||||
<!-- TODO -->
|
||||
<!-- <div class="punished" v-if="user.isSuspended"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSuspended }}</div> -->
|
||||
<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
|
||||
|
||||
<div class="profile _gaps">
|
||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!"/>
|
||||
<MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||
<div class="profile _gaps">
|
||||
<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
|
||||
<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!"/>
|
||||
<MkInfo v-if="user.host == null && user.username.includes('.')">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||
|
||||
<div :key="user.id" class="main _panel">
|
||||
<div class="banner-container" :style="style">
|
||||
<div ref="bannerEl" class="banner" :style="style"></div>
|
||||
<div class="fade"></div>
|
||||
<div :key="user.id" class="main _panel">
|
||||
<div class="banner-container" :style="style">
|
||||
<div ref="bannerEl" class="banner" :style="style"></div>
|
||||
<div class="fade"></div>
|
||||
<div class="title">
|
||||
<MkUserName class="name" :user="user" :nowrap="true"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><MkAcct :user="user" :detail="true"/></span>
|
||||
<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span>
|
||||
<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
|
||||
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
|
||||
<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
|
||||
<i class="ti ti-edit"/> {{ i18n.ts.addMemo }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||
<div class="actions">
|
||||
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
||||
<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
</div>
|
||||
</div>
|
||||
<MkAvatar class="avatar" :user="user" indicator/>
|
||||
<div class="title">
|
||||
<MkUserName class="name" :user="user" :nowrap="true"/>
|
||||
<MkUserName :user="user" :nowrap="false" class="name"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><MkAcct :user="user" :detail="true"/></span>
|
||||
<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span>
|
||||
<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
|
||||
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
|
||||
<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
|
||||
<i class="ti ti-edit"/> {{ i18n.ts.addMemo }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||
<div class="actions">
|
||||
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
||||
<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||
<div v-if="user.followedMessage != null" class="followedMessage">
|
||||
<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin>
|
||||
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
|
||||
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user" class="_selectable"/></MkSparkle></div>
|
||||
</MkFukidashi>
|
||||
</div>
|
||||
</div>
|
||||
<MkAvatar class="avatar" :user="user" indicator/>
|
||||
<div class="title">
|
||||
<MkUserName :user="user" :nowrap="false" class="name"/>
|
||||
<div class="bottom">
|
||||
<span class="username"><MkAcct :user="user" :detail="true"/></span>
|
||||
<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span>
|
||||
<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
|
||||
<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
|
||||
<div v-if="user.roles.length > 0" class="roles">
|
||||
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
|
||||
<MkA v-adaptive-bg :to="`/roles/${role.id}`">
|
||||
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
|
||||
{{ role.name }}
|
||||
</MkA>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="user.followedMessage != null" class="followedMessage">
|
||||
<MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin>
|
||||
<div class="messageHeader">{{ i18n.ts.messageToFollower }}</div>
|
||||
<div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user" class="_selectable"/></MkSparkle></div>
|
||||
</MkFukidashi>
|
||||
</div>
|
||||
<div v-if="user.roles.length > 0" class="roles">
|
||||
<span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }">
|
||||
<MkA v-adaptive-bg :to="`/roles/${role.id}`">
|
||||
<img v-if="role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="role.iconUrl"/>
|
||||
{{ role.name }}
|
||||
<div v-if="iAmModerator" class="moderationNote">
|
||||
<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
|
||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||
</MkTextarea>
|
||||
<div v-else>
|
||||
<MkButton small @click="editModerationNote = true">{{ i18n.ts.addModerationNote }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
|
||||
<div class="heading" v-text="i18n.ts.memo"/>
|
||||
<textarea
|
||||
ref="memoTextareaEl"
|
||||
v-model="memoDraft"
|
||||
rows="1"
|
||||
@focus="isEditingMemo = true"
|
||||
@blur="updateMemo"
|
||||
@input="adjustMemoTextarea"
|
||||
/>
|
||||
</div>
|
||||
<div class="description">
|
||||
<MkOmit>
|
||||
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" class="_selectable"/>
|
||||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||
</MkOmit>
|
||||
</div>
|
||||
<div class="fields system">
|
||||
<dl v-if="user.location" class="field">
|
||||
<dt class="name"><i class="ti ti-map-pin ti-fw"></i> {{ i18n.ts.location }}</dt>
|
||||
<dd class="value">{{ user.location }}</dd>
|
||||
</dl>
|
||||
<dl v-if="user.birthday" class="field">
|
||||
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
|
||||
</dl>
|
||||
<dl class="field">
|
||||
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
||||
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div v-if="user.fields.length > 0" class="fields">
|
||||
<dl v-for="(field, i) in user.fields" :key="i" class="field">
|
||||
<dt class="name">
|
||||
<Mfm :text="field.name" :author="user" :plain="true" :colored="false" class="_selectable"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<Mfm :text="field.value" :author="user" :colored="false" class="_selectable"/>
|
||||
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ti ti-circle-check" :class="$style.verifiedLink"></i>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="status">
|
||||
<MkA :to="userPage(user)">
|
||||
<b>{{ number(user.notesCount) }}</b>
|
||||
<span>{{ i18n.ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFollowingVisibleForMe(user)" :to="userPage(user, 'following')">
|
||||
<b>{{ number(user.followingCount) }}</b>
|
||||
<span>{{ i18n.ts.following }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFollowersVisibleForMe(user)" :to="userPage(user, 'followers')">
|
||||
<b>{{ number(user.followersCount) }}</b>
|
||||
<span>{{ i18n.ts.followers }}</span>
|
||||
</MkA>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="iAmModerator" class="moderationNote">
|
||||
<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
|
||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||
</MkTextarea>
|
||||
<div v-else>
|
||||
<MkButton small @click="editModerationNote = true">{{ i18n.ts.addModerationNote }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
|
||||
<div class="heading" v-text="i18n.ts.memo"/>
|
||||
<textarea
|
||||
ref="memoTextareaEl"
|
||||
v-model="memoDraft"
|
||||
rows="1"
|
||||
@focus="isEditingMemo = true"
|
||||
@blur="updateMemo"
|
||||
@input="adjustMemoTextarea"
|
||||
/>
|
||||
</div>
|
||||
<div class="description">
|
||||
<MkOmit>
|
||||
<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" class="_selectable"/>
|
||||
<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
|
||||
</MkOmit>
|
||||
</div>
|
||||
<div class="fields system">
|
||||
<dl v-if="user.location" class="field">
|
||||
<dt class="name"><i class="ti ti-map-pin ti-fw"></i> {{ i18n.ts.location }}</dt>
|
||||
<dd class="value">{{ user.location }}</dd>
|
||||
</dl>
|
||||
<dl v-if="user.birthday" class="field">
|
||||
<dt class="name"><i class="ti ti-cake ti-fw"></i> {{ i18n.ts.birthday }}</dt>
|
||||
<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
|
||||
</dl>
|
||||
<dl class="field">
|
||||
<dt class="name"><i class="ti ti-calendar ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
|
||||
<dd class="value">{{ dateString(user.createdAt) }} (<MkTime :time="user.createdAt"/>)</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div v-if="user.fields.length > 0" class="fields">
|
||||
<dl v-for="(field, i) in user.fields" :key="i" class="field">
|
||||
<dt class="name">
|
||||
<Mfm :text="field.name" :author="user" :plain="true" :colored="false" class="_selectable"/>
|
||||
</dt>
|
||||
<dd class="value">
|
||||
<Mfm :text="field.value" :author="user" :colored="false" class="_selectable"/>
|
||||
<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ti ti-circle-check" :class="$style.verifiedLink"></i>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="status">
|
||||
<MkA :to="userPage(user)">
|
||||
<b>{{ number(user.notesCount) }}</b>
|
||||
<span>{{ i18n.ts.notes }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFollowingVisibleForMe(user)" :to="userPage(user, 'following')">
|
||||
<b>{{ number(user.followingCount) }}</b>
|
||||
<span>{{ i18n.ts.following }}</span>
|
||||
</MkA>
|
||||
<MkA v-if="isFollowersVisibleForMe(user)" :to="userPage(user, 'followers')">
|
||||
<b>{{ number(user.followersCount) }}</b>
|
||||
<span>{{ i18n.ts.followers }}</span>
|
||||
</MkA>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="contents _gaps">
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gaps">
|
||||
<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
|
||||
</div>
|
||||
<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
|
||||
<template v-if="narrow">
|
||||
<MkLazy>
|
||||
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
|
||||
</MkLazy>
|
||||
<MkLazy>
|
||||
<XActivity :key="user.id" :user="user"/>
|
||||
</MkLazy>
|
||||
</template>
|
||||
<div v-if="!disableNotes">
|
||||
<MkLazy>
|
||||
<XTimeline :user="user"/>
|
||||
</MkLazy>
|
||||
<div class="contents _gaps">
|
||||
<div v-if="user.pinnedNotes.length > 0" class="_gaps">
|
||||
<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
|
||||
</div>
|
||||
<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
|
||||
<template v-if="narrow">
|
||||
<MkLazy>
|
||||
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
|
||||
</MkLazy>
|
||||
<MkLazy>
|
||||
<XActivity :key="user.id" :user="user"/>
|
||||
</MkLazy>
|
||||
</template>
|
||||
<div v-if="!disableNotes">
|
||||
<MkLazy>
|
||||
<XTimeline :user="user"/>
|
||||
</MkLazy>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
|
||||
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
|
||||
<XActivity :key="user.id" :user="user"/>
|
||||
<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
|
||||
<XFiles :key="user.id" :user="user" @unfold="emit('unfoldFiles')"/>
|
||||
<XActivity :key="user.id" :user="user"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
@@ -185,6 +187,7 @@ import { useRouter } from '@/router.js';
|
||||
import { getStaticImageUrl } from '@/utility/media-proxy.js';
|
||||
import MkSparkle from '@/components/MkSparkle.vue';
|
||||
import { prefer } from '@/preferences.js';
|
||||
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
|
||||
|
||||
function calcAge(birthdate: string): number {
|
||||
const date = new Date(birthdate);
|
||||
@@ -207,7 +210,7 @@ const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
/** Test only; MkNotes currently causes problems in vitest */
|
||||
/** Test only; MkNotesTimeline currently causes problems in vitest */
|
||||
disableNotes: boolean;
|
||||
}>(), {
|
||||
disableNotes: false,
|
||||
@@ -299,6 +302,10 @@ watch([props.user], () => {
|
||||
memoDraft.value = props.user.memo;
|
||||
});
|
||||
|
||||
async function reload() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.requestAnimationFrame(parallaxLoop);
|
||||
narrow.value = rootEl.value!.clientWidth < 1000;
|
||||
|
||||
@@ -8,19 +8,19 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #header>
|
||||
<MkTab v-model="tab" :class="$style.tab">
|
||||
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
||||
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||
</MkTab>
|
||||
</template>
|
||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :pullToRefresh="false" :class="$style.tl"/>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkNotes from '@/components/MkNotes.vue';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
@@ -28,7 +28,7 @@ const props = defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
}>();
|
||||
|
||||
const tab = ref<string | null>('all');
|
||||
const tab = ref<string>('all');
|
||||
|
||||
const pagination = computed(() => tab.value === 'featured' ? {
|
||||
endpoint: 'users/featured-notes' as const,
|
||||
|
||||
@@ -7,9 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<PageWithHeader v-model:tab="tab" :tabs="headerTabs" :actions="headerActions" :swipable="true">
|
||||
<div v-if="user">
|
||||
<XHome v-if="tab === 'home'" :user="user" @unfoldFiles="() => { tab = 'files'; }"/>
|
||||
<div v-else-if="tab === 'notes'" class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<XTimeline :user="user"/>
|
||||
</div>
|
||||
<XNotes v-else-if="tab === 'notes'" :user="user"/>
|
||||
<XFiles v-else-if="tab === 'files'" :user="user"/>
|
||||
<XActivity v-else-if="tab === 'activity'" :user="user"/>
|
||||
<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
|
||||
@@ -37,7 +35,7 @@ import { $i } from '@/i.js';
|
||||
import { serverContext, assertServerContext } from '@/server-context.js';
|
||||
|
||||
const XHome = defineAsyncComponent(() => import('./home.vue'));
|
||||
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
|
||||
const XNotes = defineAsyncComponent(() => import('./notes.vue'));
|
||||
const XFiles = defineAsyncComponent(() => import('./files.vue'));
|
||||
const XActivity = defineAsyncComponent(() => import('./activity.vue'));
|
||||
const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
|
||||
|
||||
67
packages/frontend/src/pages/user/notes.vue
Normal file
67
packages/frontend/src/pages/user/notes.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div class="_spacer" style="--MI_SPACER-w: 800px;">
|
||||
<div :class="$style.root">
|
||||
<MkStickyContainer>
|
||||
<template #header>
|
||||
<MkTab v-model="tab" :class="$style.tab">
|
||||
<option value="featured">{{ i18n.ts.featured }}</option>
|
||||
<option value="notes">{{ i18n.ts.notes }}</option>
|
||||
<option value="all">{{ i18n.ts.all }}</option>
|
||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
||||
</MkTab>
|
||||
</template>
|
||||
<MkNotesTimeline :key="tab" :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||
</MkStickyContainer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkNotesTimeline from '@/components/MkNotesTimeline.vue';
|
||||
import MkTab from '@/components/MkTab.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
|
||||
const props = defineProps<{
|
||||
user: Misskey.entities.UserDetailed;
|
||||
}>();
|
||||
|
||||
const tab = ref<string>('all');
|
||||
|
||||
const pagination = computed(() => tab.value === 'featured' ? {
|
||||
endpoint: 'users/featured-notes' as const,
|
||||
limit: 10,
|
||||
params: {
|
||||
userId: props.user.id,
|
||||
},
|
||||
} : {
|
||||
endpoint: 'users/notes' as const,
|
||||
limit: 10,
|
||||
params: {
|
||||
userId: props.user.id,
|
||||
withRenotes: tab.value === 'all',
|
||||
withReplies: tab.value === 'all',
|
||||
withChannelNotes: tab.value === 'all',
|
||||
withFiles: tab.value === 'files',
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
.tab {
|
||||
padding: calc(var(--MI-margin) / 2) 0;
|
||||
background: var(--MI_THEME-bg);
|
||||
}
|
||||
|
||||
.tl {
|
||||
background: var(--MI_THEME-bg);
|
||||
border-radius: var(--MI-radius);
|
||||
overflow: clip;
|
||||
}
|
||||
</style>
|
||||
@@ -27,7 +27,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
|
||||
</div>
|
||||
<div v-if="note.reactionCount > 0" :class="$style.reactions">
|
||||
<MkReactionsViewer :note="note" :maxNumber="16"/>
|
||||
<!-- TODO -->
|
||||
<!--<MkReactionsViewer :note="note" :maxNumber="16"/>-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user