feat: note time travel
Some checks failed
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Check SPDX-License-Identifier / check-spdx-license-id (push) Failing after 46s
Check copyright year / check_copyright_year (push) Successful in 46s
Dockle / dockle (push) Successful in 1m26s
Lint / pnpm_install (push) Successful in 2m45s
Publish Docker image (develop) / Build (linux/amd64) (push) Waiting to run
Publish Docker image (develop) / merge (push) Blocked by required conditions
Lint / locale_verify (push) Successful in 2m45s
Storybook / build (push) Has been skipped
Test (frontend) / vitest (20.16.0) (push) Failing after 3m17s
Test (frontend) / e2e (chrome, 20.16.0) (push) Failing after 3m47s
Test (production install and build) / production (20.16.0) (push) Failing after 1m44s
Lint / lint (backend) (push) Has been cancelled
Some checks failed
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Check SPDX-License-Identifier / check-spdx-license-id (push) Failing after 46s
Check copyright year / check_copyright_year (push) Successful in 46s
Dockle / dockle (push) Successful in 1m26s
Lint / pnpm_install (push) Successful in 2m45s
Publish Docker image (develop) / Build (linux/amd64) (push) Waiting to run
Publish Docker image (develop) / merge (push) Blocked by required conditions
Lint / locale_verify (push) Successful in 2m45s
Storybook / build (push) Has been skipped
Test (frontend) / vitest (20.16.0) (push) Failing after 3m17s
Test (frontend) / e2e (chrome, 20.16.0) (push) Failing after 3m47s
Test (production install and build) / production (20.16.0) (push) Failing after 1m44s
Lint / lint (backend) (push) Has been cancelled
This commit is contained in:
parent
b6dff11a52
commit
7686cd3eac
@ -1294,6 +1294,8 @@ _abuseUserReport:
|
|||||||
accept: "Accept"
|
accept: "Accept"
|
||||||
reject: "Reject"
|
reject: "Reject"
|
||||||
resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative."
|
resolveTutorial: "If the report is legitimate in content, select \"Accept\" to mark the case as resolved in the affirmative.\nIf the content of the report is not legitimate, select \"Reject\" to mark the case as resolved in the negative."
|
||||||
|
timeTravel: Time Travel
|
||||||
|
timeTravelDescription: Show posts before this date
|
||||||
_delivery:
|
_delivery:
|
||||||
status: "Delivery status"
|
status: "Delivery status"
|
||||||
stop: "Suspended"
|
stop: "Suspended"
|
||||||
|
@ -1300,6 +1300,8 @@ _abuseUserReport:
|
|||||||
accept: "确认"
|
accept: "确认"
|
||||||
reject: "拒绝"
|
reject: "拒绝"
|
||||||
resolveTutorial: "如果举报内容有理且已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果举报内容站不住脚,选择「拒绝」将案件以否定的态度标记为已解决。"
|
resolveTutorial: "如果举报内容有理且已解决,选择「确认」将案件以肯定的态度标记为已解决。\n如果举报内容站不住脚,选择「拒绝」将案件以否定的态度标记为已解决。"
|
||||||
|
timeTravel: 时光机
|
||||||
|
timeTravelDescription: 显示该日期以前的帖子
|
||||||
_delivery:
|
_delivery:
|
||||||
status: "投递状态"
|
status: "投递状态"
|
||||||
stop: "停止投递"
|
stop: "停止投递"
|
||||||
|
@ -61,7 +61,8 @@ type TimelineQueryType = {
|
|||||||
visibility?: string,
|
visibility?: string,
|
||||||
listId?: string,
|
listId?: string,
|
||||||
channelId?: string,
|
channelId?: string,
|
||||||
roleId?: string
|
roleId?: string,
|
||||||
|
untilDate?: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
|
const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
|
||||||
@ -89,7 +90,7 @@ function prepend(note) {
|
|||||||
|
|
||||||
let connection: Misskey.ChannelConnection | null = null;
|
let connection: Misskey.ChannelConnection | null = null;
|
||||||
let connection2: Misskey.ChannelConnection | null = null;
|
let connection2: Misskey.ChannelConnection | null = null;
|
||||||
let paginationQuery: Paging | null = null;
|
const paginationQuery = ref<Paging | null>(null);
|
||||||
|
|
||||||
const stream = useStream();
|
const stream = useStream();
|
||||||
|
|
||||||
@ -124,7 +125,7 @@ function connectChannel() {
|
|||||||
});
|
});
|
||||||
} else if (props.src === 'mentions') {
|
} else if (props.src === 'mentions') {
|
||||||
connection = stream.useChannel('main');
|
connection = stream.useChannel('main');
|
||||||
connection.on('mention', prepend);
|
connection?.on('mention', prepend);
|
||||||
} else if (props.src === 'directs') {
|
} else if (props.src === 'directs') {
|
||||||
const onNote = note => {
|
const onNote = note => {
|
||||||
if (note.visibility === 'specified') {
|
if (note.visibility === 'specified') {
|
||||||
@ -132,7 +133,7 @@ function connectChannel() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
connection = stream.useChannel('main');
|
connection = stream.useChannel('main');
|
||||||
connection.on('mention', onNote);
|
connection?.on('mention', onNote);
|
||||||
} else if (props.src === 'list') {
|
} else if (props.src === 'list') {
|
||||||
if (props.list == null) return;
|
if (props.list == null) return;
|
||||||
connection = stream.useChannel('userList', {
|
connection = stream.useChannel('userList', {
|
||||||
@ -159,7 +160,7 @@ function disconnectChannel() {
|
|||||||
if (connection2) connection2.dispose();
|
if (connection2) connection2.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePaginationQuery() {
|
function updatePaginationQuery(untilDate?: Date) {
|
||||||
let endpoint: keyof Misskey.Endpoints | null;
|
let endpoint: keyof Misskey.Endpoints | null;
|
||||||
let query: TimelineQueryType | null;
|
let query: TimelineQueryType | null;
|
||||||
|
|
||||||
@ -224,14 +225,22 @@ function updatePaginationQuery() {
|
|||||||
query = null;
|
query = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (untilDate) {
|
||||||
|
query = query ?? {};
|
||||||
|
query.untilDate = Number(untilDate);
|
||||||
|
} else {
|
||||||
|
query = query ?? {};
|
||||||
|
query.untilDate = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if (endpoint && query) {
|
if (endpoint && query) {
|
||||||
paginationQuery = {
|
paginationQuery.value = {
|
||||||
endpoint: endpoint,
|
endpoint: endpoint,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
params: query,
|
params: query,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
paginationQuery = null;
|
paginationQuery.value = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -267,7 +276,12 @@ function reloadTimeline() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function timetravel(date: Date) {
|
||||||
|
updatePaginationQuery(date);
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
reloadTimeline,
|
reloadTimeline,
|
||||||
|
timetravel,
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
@ -56,6 +56,8 @@ const props = withDefaults(defineProps<{
|
|||||||
actions?: PageHeaderItem[] | null;
|
actions?: PageHeaderItem[] | null;
|
||||||
thin?: boolean;
|
thin?: boolean;
|
||||||
displayMyAvatar?: boolean;
|
displayMyAvatar?: boolean;
|
||||||
|
displayBackButton?: boolean;
|
||||||
|
hideTitle?: boolean;
|
||||||
}>(), {
|
}>(), {
|
||||||
tabs: () => ([] as Tab[]),
|
tabs: () => ([] as Tab[]),
|
||||||
});
|
});
|
||||||
@ -66,7 +68,7 @@ const emit = defineEmits<{
|
|||||||
|
|
||||||
const pageMetadata = injectReactiveMetadata();
|
const pageMetadata = injectReactiveMetadata();
|
||||||
|
|
||||||
const hideTitle = inject('shouldOmitHeaderTitle', false);
|
const hideTitle = props.hideTitle || inject('shouldOmitHeaderTitle', false);
|
||||||
const thin_ = props.thin || inject('shouldHeaderThin', false);
|
const thin_ = props.thin || inject('shouldHeaderThin', false);
|
||||||
|
|
||||||
const el = shallowRef<HTMLElement | undefined>(undefined);
|
const el = shallowRef<HTMLElement | undefined>(undefined);
|
||||||
|
@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
|
import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
|
||||||
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
|
||||||
|
import type { BasicTimelineType } from '@/timelines.js';
|
||||||
import MkTimeline from '@/components/MkTimeline.vue';
|
import MkTimeline from '@/components/MkTimeline.vue';
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
import MkInfo from '@/components/MkInfo.vue';
|
||||||
import MkPostForm from '@/components/MkPostForm.vue';
|
import MkPostForm from '@/components/MkPostForm.vue';
|
||||||
@ -53,7 +54,6 @@ import { deepMerge } from '@/scripts/merge.js';
|
|||||||
import type { MenuItem } from '@/types/menu.js';
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
import { availableBasicTimelines, hasWithReplies, isAvailableBasicTimeline, isBasicTimeline, basicTimelineIconClass } from '@/timelines.js';
|
||||||
import type { BasicTimelineType } from '@/timelines.js';
|
|
||||||
|
|
||||||
provide('shouldOmitHeaderTitle', true);
|
provide('shouldOmitHeaderTitle', true);
|
||||||
|
|
||||||
@ -221,11 +221,12 @@ function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue:
|
|||||||
|
|
||||||
async function timetravel(): Promise<void> {
|
async function timetravel(): Promise<void> {
|
||||||
const { canceled, result: date } = await os.inputDate({
|
const { canceled, result: date } = await os.inputDate({
|
||||||
title: i18n.ts.date,
|
title: i18n.ts.timeTravel as string,
|
||||||
|
text: i18n.ts.timeTravelDescription as string,
|
||||||
});
|
});
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
tlComponent.value.timetravel(date);
|
tlComponent.value?.timetravel(date);
|
||||||
}
|
}
|
||||||
|
|
||||||
function focus(): void {
|
function focus(): void {
|
||||||
@ -284,9 +285,16 @@ const headerActions = computed(() => {
|
|||||||
text: i18n.ts.fileAttachedOnly,
|
text: i18n.ts.fileAttachedOnly,
|
||||||
ref: onlyFiles,
|
ref: onlyFiles,
|
||||||
disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false,
|
disabled: isBasicTimeline(src.value) && hasWithReplies(src.value) ? withReplies : false,
|
||||||
});
|
}, {
|
||||||
|
type: 'divider',
|
||||||
os.popupMenu(menuItems, ev.currentTarget ?? ev.target);
|
}, {
|
||||||
|
type: 'button',
|
||||||
|
text: i18n.ts.timeTravel,
|
||||||
|
icon: 'ti ti-calendar-time',
|
||||||
|
action: () => {
|
||||||
|
timetravel();
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -6,23 +6,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
<template>
|
<template>
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header>
|
<template #header>
|
||||||
<MkTab v-model="tab" :class="$style.tab">
|
<MkPageHeader v-model:tab="tab" :class="$style.tab" :actions="headerActions" :tabs="headerTabs" hideTitle/>
|
||||||
<option value="featured">{{ i18n.ts.featured }}</option>
|
|
||||||
<option :value="null">{{ i18n.ts.notes }}</option>
|
|
||||||
<option value="all">{{ i18n.ts.all }}</option>
|
|
||||||
<option value="files">{{ i18n.ts.withFiles }}</option>
|
|
||||||
</MkTab>
|
|
||||||
</template>
|
</template>
|
||||||
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
|
||||||
</MkStickyContainer>
|
</MkStickyContainer>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from 'vue';
|
import { ref, computed, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import MkNotes from '@/components/MkNotes.vue';
|
import MkNotes from '@/components/MkNotes.vue';
|
||||||
import MkTab from '@/components/MkTab.vue';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.UserDetailed;
|
user: Misskey.entities.UserDetailed;
|
||||||
@ -30,6 +25,12 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const tab = ref<string | null>('all');
|
const tab = ref<string | null>('all');
|
||||||
|
|
||||||
|
const timetraveled = ref<Date>();
|
||||||
|
const withRenotes = ref(true);
|
||||||
|
const onlyFiles = ref(false);
|
||||||
|
const withReplies = ref(true);
|
||||||
|
const withChannelNotes = ref(true);
|
||||||
|
|
||||||
const pagination = computed(() => tab.value === 'featured' ? {
|
const pagination = computed(() => tab.value === 'featured' ? {
|
||||||
endpoint: 'users/featured-notes' as const,
|
endpoint: 'users/featured-notes' as const,
|
||||||
limit: 10,
|
limit: 10,
|
||||||
@ -41,18 +42,88 @@ const pagination = computed(() => tab.value === 'featured' ? {
|
|||||||
limit: 10,
|
limit: 10,
|
||||||
params: {
|
params: {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
withRenotes: tab.value === 'all',
|
withRenotes: withRenotes.value,
|
||||||
withReplies: tab.value === 'all',
|
withReplies: withReplies.value,
|
||||||
withChannelNotes: tab.value === 'all',
|
withChannelNotes: withChannelNotes.value,
|
||||||
withFiles: tab.value === 'files',
|
withFiles: onlyFiles.value,
|
||||||
|
untilDate: timetraveled.value ? Number(timetraveled.value) : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function timetravel(): Promise<void> {
|
||||||
|
const { canceled, result: date } = await os.inputDate({
|
||||||
|
title: i18n.ts.timeTravel as string,
|
||||||
|
text: i18n.ts.timeTravelDescription as string,
|
||||||
|
});
|
||||||
|
if (canceled) return;
|
||||||
|
timetraveled.value = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(withReplies, (nv) => {
|
||||||
|
if (nv && onlyFiles.value) {
|
||||||
|
onlyFiles.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
watch(onlyFiles, (nv) => {
|
||||||
|
if (withReplies.value && nv) {
|
||||||
|
withReplies.value = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const headerActions = computed(() => [
|
||||||
|
{
|
||||||
|
icon: 'ti ti-dots',
|
||||||
|
text: i18n.ts.options,
|
||||||
|
handler: (ev) => {
|
||||||
|
os.popupMenu([
|
||||||
|
{
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.withReplies,
|
||||||
|
ref: withReplies,
|
||||||
|
}, {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.showRenotes,
|
||||||
|
ref: withRenotes,
|
||||||
|
}, {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.channel,
|
||||||
|
ref: withChannelNotes,
|
||||||
|
}, {
|
||||||
|
type: 'switch',
|
||||||
|
text: i18n.ts.fileAttachedOnly,
|
||||||
|
ref: onlyFiles,
|
||||||
|
}, {
|
||||||
|
type: 'divider',
|
||||||
|
}, {
|
||||||
|
type: 'button',
|
||||||
|
text: i18n.ts.timeTravel,
|
||||||
|
icon: 'ti ti-calendar-time',
|
||||||
|
action: () => {
|
||||||
|
timetravel();
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
const headerTabs = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'featured',
|
||||||
|
title: i18n.ts.featured,
|
||||||
|
icon: 'ph-lightbulb ph-bold ph-lg',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'all',
|
||||||
|
title: i18n.ts.notes,
|
||||||
|
icon: 'ph-pencil-simple ph-bold ph-lg',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.tab {
|
.tab {
|
||||||
padding: calc(var(--MI-margin) / 2) 0;
|
// padding: calc(var(--margin) / 2) 0;
|
||||||
background: var(--MI_THEME-bg);
|
// background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tl {
|
.tl {
|
||||||
|
Loading…
Reference in New Issue
Block a user