forked from FoundKeyGang/FoundKey
Compare commits
2 commits
main
...
missing-ti
Author | SHA1 | Date | |
---|---|---|---|
8a90dfa60b | |||
f5ea7b6d5b |
23 changed files with 237 additions and 247 deletions
|
@ -0,0 +1,53 @@
|
||||||
|
export class noteVisibilityFunction1662132062000 {
|
||||||
|
name = 'noteVisibilityFunction1662132062000';
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION note_visible(note_id varchar, user_id varchar) RETURNS BOOLEAN
|
||||||
|
LANGUAGE SQL
|
||||||
|
STABLE
|
||||||
|
CALLED ON NULL INPUT
|
||||||
|
AS $$
|
||||||
|
SELECT CASE
|
||||||
|
WHEN note_id IS NULL THEN TRUE
|
||||||
|
WHEN NOT EXISTS (SELECT 1 FROM note WHERE id = note_id) THEN FALSE
|
||||||
|
WHEN user_id IS NULL THEN (
|
||||||
|
-- simplified check without logged in user
|
||||||
|
SELECT
|
||||||
|
visibility IN ('public', 'home')
|
||||||
|
-- check reply / renote recursively
|
||||||
|
AND note_visible("replyId", NULL)
|
||||||
|
AND note_visible("renoteId", NULL)
|
||||||
|
FROM note WHERE note.id = note_id
|
||||||
|
) ELSE (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
visibility IN ('public', 'home')
|
||||||
|
OR
|
||||||
|
user_id = "userId"
|
||||||
|
OR
|
||||||
|
user_id = ANY("visibleUserIds")
|
||||||
|
OR
|
||||||
|
user_id = ANY("mentions")
|
||||||
|
OR (
|
||||||
|
visibility = 'followers'
|
||||||
|
AND
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM following WHERE "followeeId" = "userId" AND "followerId" = user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-- check reply / renote recursively
|
||||||
|
AND note_visible("replyId", user_id)
|
||||||
|
AND note_visible("renoteId", user_id)
|
||||||
|
FROM note WHERE note.id = note_id
|
||||||
|
)
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query('DROP FUNCTION note_visible');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,54 +0,0 @@
|
||||||
import { Brackets } from 'typeorm';
|
|
||||||
import { fetchMeta } from '@/misc/fetch-meta.js';
|
|
||||||
import { Instances } from '@/models/index.js';
|
|
||||||
import { Instance } from '@/models/entities/instance.js';
|
|
||||||
import { DAY } from '@/const.js';
|
|
||||||
|
|
||||||
// Threshold from last contact after which an instance will be considered
|
|
||||||
// "dead" and should no longer get activities delivered to it.
|
|
||||||
const deadThreshold = 30 * DAY;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the subset of hosts which should be skipped.
|
|
||||||
*
|
|
||||||
* @param hosts array of punycoded instance hosts
|
|
||||||
* @returns array of punycoed instance hosts that should be skipped (subset of hosts parameter)
|
|
||||||
*/
|
|
||||||
export async function skippedInstances(hosts: Array<Instace['host']>): Array<Instance['host']> {
|
|
||||||
// first check for blocked instances since that info may already be in memory
|
|
||||||
const { blockedHosts } = await fetchMeta();
|
|
||||||
|
|
||||||
const skipped = hosts.filter(host => blockedHosts.includes(host));
|
|
||||||
// if possible return early and skip accessing the database
|
|
||||||
if (skipped.length === hosts.length) return hosts;
|
|
||||||
|
|
||||||
const deadTime = new Date(Date.now() - deadThreshold);
|
|
||||||
|
|
||||||
return skipped.concat(
|
|
||||||
await Instances.createQueryBuilder('instance')
|
|
||||||
.where('instance.host in (:...hosts)', {
|
|
||||||
// don't check hosts again that we already know are suspended
|
|
||||||
// also avoids adding duplicates to the list
|
|
||||||
hosts: hosts.filter(host => !skipped.includes(host)),
|
|
||||||
})
|
|
||||||
.andWhere(new Brackets(qb => { qb
|
|
||||||
.where('instance.isSuspended')
|
|
||||||
.orWhere('instance.lastCommunicatedAt < :deadTime', { deadTime });
|
|
||||||
}))
|
|
||||||
.select('host')
|
|
||||||
.getRawMany()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a specific host (punycoded) should be skipped.
|
|
||||||
* Convenience wrapper around skippedInstances which should only be used if there is a single host to check.
|
|
||||||
* If you have multiple hosts, consider using skippedInstances instead to do a bulk check.
|
|
||||||
*
|
|
||||||
* @param host punycoded instance host
|
|
||||||
* @returns whether the given host should be skipped
|
|
||||||
*/
|
|
||||||
export async function shouldSkipInstance(host: Instance['host']): boolean {
|
|
||||||
const skipped = await skippedInstances([host]);
|
|
||||||
return skipped.length > 0;
|
|
||||||
}
|
|
|
@ -77,7 +77,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: {
|
||||||
|
|
||||||
export const NoteRepository = db.getRepository(Note).extend({
|
export const NoteRepository = db.getRepository(Note).extend({
|
||||||
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
|
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
|
||||||
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
// This code must always be synchronized with the `note_visible` SQL function.
|
||||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||||
if (note.visibility === 'specified') {
|
if (note.visibility === 'specified') {
|
||||||
if (meId == null) {
|
if (meId == null) {
|
||||||
|
|
|
@ -6,20 +6,39 @@ import Logger from '@/services/logger.js';
|
||||||
import { Instances } from '@/models/index.js';
|
import { Instances } from '@/models/index.js';
|
||||||
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
import { apRequestChart, federationChart, instanceChart } from '@/services/chart/index.js';
|
||||||
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata.js';
|
||||||
|
import { fetchMeta } from '@/misc/fetch-meta.js';
|
||||||
import { toPuny } from '@/misc/convert-host.js';
|
import { toPuny } from '@/misc/convert-host.js';
|
||||||
|
import { Cache } from '@/misc/cache.js';
|
||||||
|
import { Instance } from '@/models/entities/instance.js';
|
||||||
import { StatusError } from '@/misc/fetch.js';
|
import { StatusError } from '@/misc/fetch.js';
|
||||||
import { shouldSkipInstance } from '@/misc/skipped-instances.js';
|
|
||||||
import { DeliverJobData } from '@/queue/types.js';
|
import { DeliverJobData } from '@/queue/types.js';
|
||||||
|
import { LessThan } from 'typeorm';
|
||||||
|
import { DAY } from '@/const.js';
|
||||||
|
|
||||||
const logger = new Logger('deliver');
|
const logger = new Logger('deliver');
|
||||||
|
|
||||||
let latest: string | null = null;
|
let latest: string | null = null;
|
||||||
|
|
||||||
|
const deadThreshold = 30 * DAY;
|
||||||
|
|
||||||
export default async (job: Bull.Job<DeliverJobData>) => {
|
export default async (job: Bull.Job<DeliverJobData>) => {
|
||||||
const { host } = new URL(job.data.to);
|
const { host } = new URL(job.data.to);
|
||||||
const puny = toPuny(host);
|
const puny = toPuny(host);
|
||||||
|
|
||||||
if (await shouldSkipInstance(puny)) return 'skip';
|
// ブロックしてたら中断
|
||||||
|
const meta = await fetchMeta();
|
||||||
|
if (meta.blockedHosts.includes(puny)) {
|
||||||
|
return 'skip (blocked)';
|
||||||
|
}
|
||||||
|
|
||||||
|
const deadTime = new Date(Date.now() - deadThreshold);
|
||||||
|
const isSuspendedOrDead = await Instances.countBy([
|
||||||
|
{ host: puny, isSuspended: true },
|
||||||
|
{ host: puny, lastCommunicatedAt: LessThan(deadTime) },
|
||||||
|
]);
|
||||||
|
if (isSuspendedOrDead) {
|
||||||
|
return 'skip (suspended or dead)';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
|
if (latest !== (latest = JSON.stringify(job.data.content, null, 2))) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { IsNull, Not } from 'typeorm';
|
||||||
import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js';
|
import { ILocalUser, IRemoteUser, User } from '@/models/entities/user.js';
|
||||||
import { Users, Followings } from '@/models/index.js';
|
import { Users, Followings } from '@/models/index.js';
|
||||||
import { deliver } from '@/queue/index.js';
|
import { deliver } from '@/queue/index.js';
|
||||||
import { skippedInstances } from '@/misc/skipped-instances.js';
|
|
||||||
|
|
||||||
//#region types
|
//#region types
|
||||||
interface IRecipe {
|
interface IRecipe {
|
||||||
|
@ -151,19 +150,8 @@ export default class DeliverManager {
|
||||||
)
|
)
|
||||||
.forEach(recipe => inboxes.add(recipe.to.inbox!));
|
.forEach(recipe => inboxes.add(recipe.to.inbox!));
|
||||||
|
|
||||||
const instancesToSkip = await skippedInstances(
|
|
||||||
// get (unique) list of hosts
|
|
||||||
Array.from(new Set(
|
|
||||||
Array.from(inboxes)
|
|
||||||
.map(inbox => new URL(inbox).host)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
|
|
||||||
// deliver
|
// deliver
|
||||||
for (const inbox of inboxes) {
|
for (const inbox of inboxes) {
|
||||||
// skip instances as indicated
|
|
||||||
if (instancesToSkip.includes(new URL(inbox).host)) continue;
|
|
||||||
|
|
||||||
deliver(this.actor, this.activity, inbox);
|
deliver(this.actor, this.activity, inbox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,42 +1,17 @@
|
||||||
import { Brackets, SelectQueryBuilder } from 'typeorm';
|
import { SelectQueryBuilder } from 'typeorm';
|
||||||
import { User } from '@/models/entities/user.js';
|
import { User } from '@/models/entities/user.js';
|
||||||
import { Followings } from '@/models/index.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
|
import { Notes } from '@/models/index.js';
|
||||||
|
|
||||||
export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) {
|
export function visibilityQuery(q: SelectQueryBuilder<Note>, meId?: User['id'] | null = null): SelectQueryBuilder<Note> {
|
||||||
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
const superQuery = Notes.createQueryBuilder()
|
||||||
if (me == null) {
|
.from(() => q, 'note');
|
||||||
q.andWhere(new Brackets(qb => { qb
|
|
||||||
.where("note.visibility = 'public'")
|
if (meId == null) {
|
||||||
.orWhere("note.visibility = 'home'");
|
superQuery.where('note_visible(note.id, null);');
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
const followingQuery = Followings.createQueryBuilder('following')
|
superQuery.where('note_visible(note.id, :meId)', { meId });
|
||||||
.select('following.followeeId')
|
|
||||||
.where('following.followerId = :meId');
|
|
||||||
|
|
||||||
q.andWhere(new Brackets(qb => { qb
|
|
||||||
// 公開投稿である
|
|
||||||
.where(new Brackets(qb => { qb
|
|
||||||
.where("note.visibility = 'public'")
|
|
||||||
.orWhere("note.visibility = 'home'");
|
|
||||||
}))
|
|
||||||
// または 自分自身
|
|
||||||
.orWhere('note.userId = :meId')
|
|
||||||
// または 自分宛て
|
|
||||||
.orWhere(':meId = ANY(note.visibleUserIds)')
|
|
||||||
.orWhere(':meId = ANY(note.mentions)')
|
|
||||||
.orWhere(new Brackets(qb => { qb
|
|
||||||
// または フォロワー宛ての投稿であり、
|
|
||||||
.where("note.visibility = 'followers'")
|
|
||||||
.andWhere(new Brackets(qb => { qb
|
|
||||||
// 自分がフォロワーである
|
|
||||||
.where(`note.userId IN (${ followingQuery.getQuery() })`)
|
|
||||||
// または 自分の投稿へのリプライ
|
|
||||||
.orWhere('note.replyUserId = :meId');
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
|
|
||||||
q.setParameters({ meId: me.id });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return q;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||||
import { User } from '@/models/entities/user.js';
|
import { User } from '@/models/entities/user.js';
|
||||||
import { Note } from '@/models/entities/note.js';
|
import { Note } from '@/models/entities/note.js';
|
||||||
import { Notes, Users } from '@/models/index.js';
|
import { Notes, Users } from '@/models/index.js';
|
||||||
import { generateVisibilityQuery } from './generate-visibility-query.js';
|
import { visibilityQuery } from './generate-visibility-query.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get note for API processing, taking into account visibility.
|
* Get note for API processing, taking into account visibility.
|
||||||
|
@ -13,9 +13,7 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null)
|
||||||
id: noteId,
|
id: noteId,
|
||||||
});
|
});
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
const note = await visibilityQuery(query, me).getOne();
|
||||||
|
|
||||||
const note = await query.getOne();
|
|
||||||
|
|
||||||
if (note == null) {
|
if (note == null) {
|
||||||
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
|
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { readNote } from '@/services/note/read.js';
|
import { readNote } from '@/services/note/read.js';
|
||||||
import { Antennas, Notes, AntennaNotes } from '@/models/index.js';
|
import { Antennas, Notes, AntennaNotes } from '@/models/index.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
|
@ -65,11 +65,10 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||||
.andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
|
.andWhere('antennaNote.antennaId = :antennaId', { antennaId: antenna.id });
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
|
||||||
const notes = await query
|
const notes = await visibilityQuery(query, user)
|
||||||
.take(ps.limit)
|
.take(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { ClipNotes, Clips, Notes } from '@/models/index.js';
|
import { ClipNotes, Clips, Notes } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
@ -65,12 +65,11 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
|
.andWhere('clipNote.clipId = :clipId', { clipId: clip.id });
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await query
|
const notes = await visibilityQuery(query, user)
|
||||||
.take(ps.limit)
|
.take(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Notes } from '@/models/index.js';
|
import { Notes } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
|
@ -55,13 +55,12 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('user.avatar', 'avatar')
|
.leftJoinAndSelect('user.avatar', 'avatar')
|
||||||
.leftJoinAndSelect('user.banner', 'banner');
|
.leftJoinAndSelect('user.banner', 'banner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
if (user) {
|
if (user) {
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
const notes = await query.getMany();
|
const notes = await visibilityQuery(query, user).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(notes, user, { detail: false });
|
return await Notes.packMany(notes, user, { detail: false });
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { activeUsersChart } from '@/services/chart/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
||||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
||||||
|
@ -84,7 +84,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
|
||||||
generateChannelQuery(query, user);
|
generateChannelQuery(query, user);
|
||||||
generateRepliesQuery(query, user);
|
generateRepliesQuery(query, user);
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
@ -125,7 +124,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
activeUsersChart.read(user);
|
activeUsersChart.read(user);
|
||||||
|
|
|
@ -6,7 +6,7 @@ import define from '../../define.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
||||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
||||||
import { generateChannelQuery } from '../../common/generate-channel-query.js';
|
import { generateChannelQuery } from '../../common/generate-channel-query.js';
|
||||||
|
@ -77,7 +77,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
|
||||||
generateChannelQuery(query, user);
|
generateChannelQuery(query, user);
|
||||||
generateRepliesQuery(query, user);
|
generateRepliesQuery(query, user);
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateMutedNoteQuery(query, user);
|
if (user) generateMutedNoteQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
@ -103,7 +102,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
if (user) {
|
if (user) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { noteVisibilities } from 'foundkey-js';
|
||||||
import { readNote } from '@/services/note/read.js';
|
import { readNote } from '@/services/note/read.js';
|
||||||
import { Notes, Followings } from '@/models/index.js';
|
import { Notes, Followings } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
@ -63,7 +63,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteThreadQuery(query, user);
|
generateMutedNoteThreadQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
@ -77,7 +76,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
query.setParameters(followingQuery.getParameters());
|
query.setParameters(followingQuery.getParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
const mentions = await query.take(ps.limit).getMany();
|
const mentions = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
readNote(user.id, mentions);
|
readNote(user.id, mentions);
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Notes } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { getNote } from '../../common/getters.js';
|
import { getNote } from '../../common/getters.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
@ -57,11 +57,10 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
|
||||||
const renotes = await query.take(ps.limit).getMany();
|
const renotes = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(renotes, user);
|
return await Notes.packMany(renotes, user);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Notes } from '@/models/index.js';
|
import { Notes } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
|
@ -48,11 +48,10 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
if (user) generateMutedUserQuery(query, user);
|
if (user) generateMutedUserQuery(query, user);
|
||||||
if (user) generateBlockedUserQuery(query, user);
|
if (user) generateBlockedUserQuery(query, user);
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(timeline, user);
|
return await Notes.packMany(timeline, user);
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { normalizeForSearch } from '@/misc/normalize-for-search.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -80,7 +80,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
|
||||||
if (me) generateMutedUserQuery(query, me);
|
if (me) generateMutedUserQuery(query, me);
|
||||||
if (me) generateBlockedUserQuery(query, me);
|
if (me) generateBlockedUserQuery(query, me);
|
||||||
|
|
||||||
|
@ -134,7 +133,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search notes
|
// Search notes
|
||||||
const notes = await query.take(ps.limit).getMany();
|
const notes = await visibilityQuery(query, me).take(ps.limit).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(notes, me);
|
return await Notes.packMany(notes, me);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import config from '@/config/index.js';
|
||||||
import es from '@/db/elasticsearch.js';
|
import es from '@/db/elasticsearch.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
|
@ -68,11 +68,10 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
|
||||||
if (me) generateMutedUserQuery(query, me);
|
if (me) generateMutedUserQuery(query, me);
|
||||||
if (me) generateBlockedUserQuery(query, me);
|
if (me) generateBlockedUserQuery(query, me);
|
||||||
|
|
||||||
const notes = await query.take(ps.limit).getMany();
|
const notes = await visibilityQuery(query, me).take(ps.limit).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(notes, me);
|
return await Notes.packMany(notes, me);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Notes, Followings } from '@/models/index.js';
|
||||||
import { activeUsersChart } from '@/services/chart/index.js';
|
import { activeUsersChart } from '@/services/chart/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
import { generateRepliesQuery } from '../../common/generate-replies-query.js';
|
||||||
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js';
|
||||||
|
@ -82,7 +82,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
|
|
||||||
generateChannelQuery(query, user);
|
generateChannelQuery(query, user);
|
||||||
generateRepliesQuery(query, user);
|
generateRepliesQuery(query, user);
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
generateMutedUserQuery(query, user);
|
generateMutedUserQuery(query, user);
|
||||||
generateMutedNoteQuery(query, user);
|
generateMutedNoteQuery(query, user);
|
||||||
generateBlockedUserQuery(query, user);
|
generateBlockedUserQuery(query, user);
|
||||||
|
@ -123,7 +122,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
activeUsersChart.read(user);
|
activeUsersChart.read(user);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { activeUsersChart } from '@/services/chart/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['notes', 'lists'],
|
tags: ['notes', 'lists'],
|
||||||
|
@ -70,8 +70,6 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner')
|
||||||
.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
|
.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
|
||||||
|
|
||||||
generateVisibilityQuery(query, user);
|
|
||||||
|
|
||||||
if (ps.includeMyRenotes === false) {
|
if (ps.includeMyRenotes === false) {
|
||||||
query.andWhere(new Brackets(qb => {
|
query.andWhere(new Brackets(qb => {
|
||||||
qb.orWhere('note.userId != :meId', { meId: user.id });
|
qb.orWhere('note.userId != :meId', { meId: user.id });
|
||||||
|
@ -107,7 +105,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, user).take(ps.limit).getMany();
|
||||||
|
|
||||||
activeUsersChart.read(user);
|
activeUsersChart.read(user);
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import define from '../../define.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
import { getUser } from '../../common/getters.js';
|
import { getUser } from '../../common/getters.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js';
|
||||||
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
import { generateBlockedUserQuery } from '../../common/generate-block-query.js';
|
||||||
|
|
||||||
|
@ -69,7 +69,6 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
.leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
|
||||||
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
.leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
|
||||||
if (me) {
|
if (me) {
|
||||||
generateMutedUserQuery(query, me);
|
generateMutedUserQuery(query, me);
|
||||||
generateBlockedUserQuery(query, me);
|
generateBlockedUserQuery(query, me);
|
||||||
|
@ -110,7 +109,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
const timeline = await query.take(ps.limit).getMany();
|
const timeline = await visibilityQuery(query, me).take(ps.limit).getMany();
|
||||||
|
|
||||||
return await Notes.packMany(timeline, me);
|
return await Notes.packMany(timeline, me);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { NoteReactions, UserProfiles } from '@/models/index.js';
|
import { NoteReactions, UserProfiles } from '@/models/index.js';
|
||||||
import define from '../../define.js';
|
import define from '../../define.js';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
import { makePaginationQuery } from '../../common/make-pagination-query.js';
|
||||||
import { generateVisibilityQuery } from '../../common/generate-visibility-query.js';
|
import { visibilityQuery } from '../../common/generate-visibility-query.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -50,9 +50,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
||||||
.leftJoinAndSelect('reaction.note', 'note');
|
.leftJoinAndSelect('reaction.note', 'note');
|
||||||
|
|
||||||
generateVisibilityQuery(query, me);
|
const reactions = await visibilityQuery(query, me)
|
||||||
|
|
||||||
const reactions = await query
|
|
||||||
.take(ps.limit)
|
.take(ps.limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ export const httpCodes: Record<string, string> = {
|
||||||
'415': 'Unsupported Media Type',
|
'415': 'Unsupported Media Type',
|
||||||
'416': 'Range Not Satisfiable',
|
'416': 'Range Not Satisfiable',
|
||||||
'417': 'Expectation Failed',
|
'417': 'Expectation Failed',
|
||||||
'418': 'I\'m a Teapot',
|
'418': 'I'm a Teapot',
|
||||||
'421': 'Misdirected Request',
|
'421': 'Misdirected Request',
|
||||||
'422': 'Unprocessable Content',
|
'422': 'Unprocessable Content',
|
||||||
'423': 'Locked',
|
'423': 'Locked',
|
||||||
|
|
|
@ -15,14 +15,14 @@
|
||||||
|
|
||||||
<div v-else ref="rootEl">
|
<div v-else ref="rootEl">
|
||||||
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
<div v-show="pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
||||||
<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore(true)">
|
<MkButton v-if="!moreFetching" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMoreAhead">
|
||||||
{{ i18n.ts.loadMore }}
|
{{ i18n.ts.loadMore }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<MkLoading v-else class="loading"/>
|
<MkLoading v-else class="loading"/>
|
||||||
</div>
|
</div>
|
||||||
<slot :items="items"></slot>
|
<slot :items="items"></slot>
|
||||||
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
<div v-show="!pagination.reversed && more" key="_more_" class="cxiknjgy _gap">
|
||||||
<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore()">
|
<MkButton v-if="!moreFetching" v-appear="($store.state.enableInfiniteScroll && !disableAutoLoad) ? fetchMore : null" class="button" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }" primary @click="fetchMore">
|
||||||
{{ i18n.ts.loadMore }}
|
{{ i18n.ts.loadMore }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
<MkLoading v-else class="loading"/>
|
<MkLoading v-else class="loading"/>
|
||||||
|
@ -32,7 +32,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ComputedRef, isRef, onActivated, onDeactivated, watch } from 'vue';
|
import { computed, ComputedRef, isRef, onActivated, onDeactivated, ref, watch } from 'vue';
|
||||||
import * as foundkey from 'foundkey-js';
|
import * as foundkey from 'foundkey-js';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
|
import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
|
||||||
|
@ -45,13 +45,13 @@ export type Paging<E extends keyof foundkey.Endpoints = keyof foundkey.Endpoints
|
||||||
params?: foundkey.Endpoints[E]['req'] | ComputedRef<foundkey.Endpoints[E]['req']>;
|
params?: foundkey.Endpoints[E]['req'] | ComputedRef<foundkey.Endpoints[E]['req']>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When using non-pageable endpoints, such as the search API.
|
* 検索APIのような、ページング不可なエンドポイントを利用する場合
|
||||||
* (though it is slightly inconsistent to use such an API with this function)
|
* (そのようなAPIをこの関数で使うのは若干矛盾してるけど)
|
||||||
*/
|
*/
|
||||||
noPaging?: boolean;
|
noPaging?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* items Array contents in reverse order (newest first, last)
|
* items 配列の中身を逆順にする(新しい方が最後)
|
||||||
*/
|
*/
|
||||||
reversed?: boolean;
|
reversed?: boolean;
|
||||||
|
|
||||||
|
@ -76,175 +76,202 @@ const emit = defineEmits<{
|
||||||
|
|
||||||
type Item = { id: string; [another: string]: unknown; };
|
type Item = { id: string; [another: string]: unknown; };
|
||||||
|
|
||||||
let rootEl: HTMLElement | null = $ref(null);
|
const rootEl = ref<HTMLElement>();
|
||||||
let items: Item[] = $ref([]);
|
const items = ref<Item[]>([]);
|
||||||
let queue: Item[] = $ref([]);
|
const queue = ref<Item[]>([]);
|
||||||
let offset: number = $ref(0);
|
const offset = ref(0);
|
||||||
let fetching: boolean = $ref(true);
|
const fetching = ref(true);
|
||||||
let moreFetching: boolean = $ref(false);
|
const moreFetching = ref(false);
|
||||||
let more: boolean = $ref(false);
|
const more = ref(false);
|
||||||
let backed: boolean = $ref(false); // 遡り中か否か
|
const backed = ref(false); // 遡り中か否か
|
||||||
let isBackTop: boolean = $ref(false);
|
const isBackTop = ref(false);
|
||||||
const empty = $computed(() => items.length === 0);
|
const empty = computed(() => items.value.length === 0);
|
||||||
let error: boolean = $ref(false);
|
const error = ref(false);
|
||||||
|
|
||||||
const init = async (): Promise<void> => {
|
const init = async (): Promise<void> => {
|
||||||
queue = [];
|
queue.value = [];
|
||||||
fetching = true;
|
fetching.value = true;
|
||||||
const params = props.pagination.params
|
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||||
? isRef(props.pagination.params)
|
|
||||||
? props.pagination.params.value as Record<string, any>
|
|
||||||
: props.pagination.params
|
|
||||||
: {};
|
|
||||||
await os.api(props.pagination.endpoint, {
|
await os.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
|
limit: props.pagination.noPaging ? (props.pagination.limit || 10) : (props.pagination.limit || 10) + 1,
|
||||||
}).then((res: Item[]) => {
|
}).then(res => {
|
||||||
if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
|
if (!props.pagination.noPaging && (res.length > (props.pagination.limit || 10))) {
|
||||||
res.pop();
|
res.pop();
|
||||||
more = true;
|
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
||||||
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
more = false;
|
items.value = props.pagination.reversed ? [...res].reverse() : res;
|
||||||
|
more.value = false;
|
||||||
}
|
}
|
||||||
items = props.pagination.reversed ? [...res].reverse() : res;
|
offset.value = res.length;
|
||||||
offset = res.length;
|
error.value = false;
|
||||||
error = false;
|
fetching.value = false;
|
||||||
fetching = false;
|
|
||||||
emit('loaded');
|
emit('loaded');
|
||||||
}).catch(() => {
|
}, () => {
|
||||||
error = true;
|
error.value = true;
|
||||||
fetching = false;
|
fetching.value = false;
|
||||||
emit('error');
|
emit('error');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const reload = (): void => {
|
const reload = (): void => {
|
||||||
items = [];
|
items.value = [];
|
||||||
init();
|
init();
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchMore = async (ahead?: boolean): Promise<void> => {
|
const fetchMore = async (): Promise<void> => {
|
||||||
if (!more || fetching || moreFetching || items.length === 0) return;
|
if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
|
||||||
moreFetching = true;
|
moreFetching.value = true;
|
||||||
if (!ahead) {
|
backed.value = true;
|
||||||
backed = true;
|
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||||
}
|
|
||||||
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params : props.pagination.params : {};
|
|
||||||
await os.api(props.pagination.endpoint, {
|
await os.api(props.pagination.endpoint, {
|
||||||
...params,
|
...params,
|
||||||
limit: SECOND_FETCH_LIMIT + 1,
|
limit: SECOND_FETCH_LIMIT + 1,
|
||||||
...(props.pagination.offsetMode ? {
|
...(props.pagination.offsetMode ? {
|
||||||
offset,
|
offset: offset.value,
|
||||||
} : ahead ? (
|
} : props.pagination.reversed ? {
|
||||||
props.pagination.reversed ? {
|
sinceId: items.value[0].id,
|
||||||
untilId: items[0].id,
|
} : {
|
||||||
} : {
|
untilId: items.value[items.value.length - 1].id,
|
||||||
sinceId: items[items.length - 1].id,
|
}),
|
||||||
}
|
|
||||||
) : (
|
|
||||||
props.pagination.reversed ? {
|
|
||||||
sinceId: items[0].id,
|
|
||||||
} : {
|
|
||||||
untilId: items[items.length - 1].id,
|
|
||||||
}
|
|
||||||
)),
|
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (res.length > SECOND_FETCH_LIMIT) {
|
if (res.length > SECOND_FETCH_LIMIT) {
|
||||||
res.pop();
|
res.pop();
|
||||||
more = true;
|
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
|
||||||
|
more.value = true;
|
||||||
} else {
|
} else {
|
||||||
more = false;
|
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
|
||||||
|
more.value = false;
|
||||||
}
|
}
|
||||||
items = props.pagination.reversed ? [...res].reverse().concat(items) : items.concat(res);
|
offset.value += res.length;
|
||||||
offset += res.length;
|
moreFetching.value = false;
|
||||||
moreFetching = false;
|
|
||||||
}, () => {
|
}, () => {
|
||||||
moreFetching = false;
|
moreFetching.value = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchMoreAhead = async (): Promise<void> => {
|
||||||
|
if (!more.value || fetching.value || moreFetching.value || items.value.length === 0) return;
|
||||||
|
moreFetching.value = true;
|
||||||
|
const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
|
||||||
|
await os.api(props.pagination.endpoint, {
|
||||||
|
...params,
|
||||||
|
limit: SECOND_FETCH_LIMIT + 1,
|
||||||
|
...(props.pagination.offsetMode ? {
|
||||||
|
offset: offset.value,
|
||||||
|
} : props.pagination.reversed ? {
|
||||||
|
untilId: items.value[0].id,
|
||||||
|
} : {
|
||||||
|
sinceId: items.value[items.value.length - 1].id,
|
||||||
|
}),
|
||||||
|
}).then(res => {
|
||||||
|
if (res.length > SECOND_FETCH_LIMIT) {
|
||||||
|
res.pop();
|
||||||
|
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
|
||||||
|
more.value = true;
|
||||||
|
} else {
|
||||||
|
items.value = props.pagination.reversed ? [...res].reverse().concat(items.value) : items.value.concat(res);
|
||||||
|
more.value = false;
|
||||||
|
}
|
||||||
|
offset.value += res.length;
|
||||||
|
moreFetching.value = false;
|
||||||
|
}, () => {
|
||||||
|
moreFetching.value = false;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const prepend = (item: Item): void => {
|
const prepend = (item: Item): void => {
|
||||||
if (props.pagination.reversed) {
|
if (props.pagination.reversed) {
|
||||||
if (rootEl) {
|
if (rootEl.value) {
|
||||||
const container = getScrollContainer(rootEl);
|
const container = getScrollContainer(rootEl.value);
|
||||||
if (container == null) {
|
if (container == null) {
|
||||||
// TODO?
|
// TODO?
|
||||||
} else {
|
} else {
|
||||||
const pos = getScrollPosition(rootEl);
|
const pos = getScrollPosition(rootEl.value);
|
||||||
const viewHeight = container.clientHeight;
|
const viewHeight = container.clientHeight;
|
||||||
const height = container.scrollHeight;
|
const height = container.scrollHeight;
|
||||||
const isBottom = (pos + viewHeight > height - 32);
|
const isBottom = (pos + viewHeight > height - 32);
|
||||||
// Discard old items if they overflow.
|
|
||||||
if (isBottom) {
|
if (isBottom) {
|
||||||
while (items.length >= props.displayLimit) {
|
// オーバーフローしたら古いアイテムは捨てる
|
||||||
items.shift();
|
if (items.value.length >= props.displayLimit) {
|
||||||
|
// このやり方だとVue 3.2以降アニメーションが動かなくなる
|
||||||
|
//items.value = items.value.slice(-props.displayLimit);
|
||||||
|
while (items.value.length >= props.displayLimit) {
|
||||||
|
items.value.shift();
|
||||||
|
}
|
||||||
|
more.value = true;
|
||||||
}
|
}
|
||||||
more = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
items.push(item);
|
items.value.push(item);
|
||||||
// TODO
|
// TODO
|
||||||
} else {
|
} else {
|
||||||
// Only unshift is required for initial display.
|
// 初回表示時はunshiftだけでOK
|
||||||
if (!rootEl) {
|
if (!rootEl.value) {
|
||||||
items.unshift(item);
|
items.value.unshift(item);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isTop = isBackTop || (document.body.contains(rootEl) && isTopVisible(rootEl));
|
const isTop = isBackTop.value || (document.body.contains(rootEl.value) && isTopVisible(rootEl.value));
|
||||||
|
|
||||||
if (isTop) {
|
if (isTop) {
|
||||||
// Prepend the item
|
// Prepend the item
|
||||||
items.unshift(item);
|
items.value.unshift(item);
|
||||||
|
|
||||||
// Discard old items if they overflow.
|
// オーバーフローしたら古いアイテムは捨てる
|
||||||
while (items.length >= props.displayLimit) {
|
if (items.value.length >= props.displayLimit) {
|
||||||
items.pop();
|
// このやり方だとVue 3.2以降アニメーションが動かなくなる
|
||||||
|
//this.items = items.value.slice(0, props.displayLimit);
|
||||||
|
while (items.value.length >= props.displayLimit) {
|
||||||
|
items.value.pop();
|
||||||
|
}
|
||||||
|
more.value = true;
|
||||||
}
|
}
|
||||||
more = true;
|
|
||||||
} else {
|
} else {
|
||||||
queue.push(item);
|
queue.value.push(item);
|
||||||
onScrollTop(rootEl, () => {
|
onScrollTop(rootEl.value, () => {
|
||||||
for (const queueItem of queue) {
|
for (const queueItem of queue.value) {
|
||||||
prepend(queueItem);
|
prepend(queueItem);
|
||||||
}
|
}
|
||||||
queue = [];
|
queue.value = [];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const append = (item: Item): void => {
|
const append = (item: Item): void => {
|
||||||
items.push(item);
|
items.value.push(item);
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeItem = (finder: (item: Item) => boolean): void => {
|
const removeItem = (finder: (item: Item) => boolean): void => {
|
||||||
const i = items.findIndex(finder);
|
const i = items.value.findIndex(finder);
|
||||||
items.splice(i, 1);
|
items.value.splice(i, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
|
const updateItem = (id: Item['id'], replacer: (old: Item) => Item): void => {
|
||||||
const i = items.findIndex(item => item.id === id);
|
const i = items.value.findIndex(item => item.id === id);
|
||||||
items[i] = replacer(items[i]);
|
items.value[i] = replacer(items.value[i]);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (props.pagination.params && isRef(props.pagination.params)) {
|
if (props.pagination.params && isRef(props.pagination.params)) {
|
||||||
watch(props.pagination.params, init, { deep: true });
|
watch(props.pagination.params, init, { deep: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
watch($$(queue), (a, b) => {
|
watch(queue, (a, b) => {
|
||||||
if (a.length !== 0 || b.length !== 0) emit('queue', queue.length);
|
if (a.length === 0 && b.length === 0) return;
|
||||||
|
emit('queue', queue.value.length);
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
onActivated(() => {
|
onActivated(() => {
|
||||||
isBackTop = false;
|
isBackTop.value = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
isBackTop = window.scrollY === 0;
|
isBackTop.value = window.scrollY === 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
|
Loading…
Reference in a new issue