Compare commits

...

50 commits

Author SHA1 Message Date
dakkar
c0490d10d2 merge: more timeline filters - #228 (!455)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/455
2024-04-11 21:12:39 +00:00
dakkar
e0afeff248 merge: hide images/videos in og cards, when under a CW - fixes #487 (!488)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/488

Closes #487

Approved-by: Marie <marie@kaifa.ch>
Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-04-11 20:40:38 +00:00
Marie
cfc8081cec merge: bump tmp@0.2.3 - fixes #464 (!475)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/475

Closes #464

Approved-by: Marie <marie@kaifa.ch>
Approved-by: Luna <her@mint.lgbt>
Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-04-11 18:00:40 +00:00
Marie
011ccd3a9a merge: bump devel version (!486)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/486

Approved-by: Marie <marie@kaifa.ch>
Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-04-11 17:21:32 +00:00
Amelia Yukii
28065fc1d1 merge: handle ranged requests for proxied files - fixes #494 (!490)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/490

Closes #494

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: Marie <marie@kaifa.ch>
2024-04-11 10:07:25 +00:00
dakkar
960f4fcff7 detect size of remote files - fixes #494
without this, remote files are assumed to have size 0 (even if we just
downloaded them!) and the range-related code won't run
2024-04-09 16:21:30 +01:00
dakkar
92eec2178f return 206 for every ranged response - fixes #494 2024-04-09 15:42:29 +01:00
dakkar
56dca6dbf5 hide images/videos in og cards, when under a CW - fixes #487 2024-04-07 16:58:13 +01:00
dakkar
2a634e0309 bump devel version 2024-03-30 12:48:03 +00:00
dakkar
e6970a0e7c Merge branch 'stable' into bump-devel-version 2024-03-30 12:44:31 +00:00
Amelia Yukii
571272a564 merge: release 2024.3.2 (!485)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/485

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-03-30 11:19:08 +00:00
dakkar
30bb0f60a2 version bump 2024-03-30 11:09:00 +00:00
dakkar
328546c4cd Merge branch 'develop' into release/2024-03-30 2024-03-30 11:08:26 +00:00
dakkar
f4e89f2e6b bump tmp@0.2.3 - fixes #464
see also https://github.com/raszi/node-tmp/issues/295
2024-03-19 17:13:43 +00:00
dakkar
2071e72b2b use all filters to dbFallback for channel timeline 2024-03-04 20:48:27 +00:00
dakkar
3bb8a91124 add boost / files filters to antenna timeline - #228 2024-03-03 14:03:21 +00:00
dakkar
84abe50f84 add boost / files filters to channel timeline - #228 2024-03-03 13:33:32 +00:00
dakkar
2cad97c1ab merge: release 2024.3.1 (!449)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/449

Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
Approved-by: Marie <marie@kaifa.ch>
2024-03-02 17:43:24 +00:00
dakkar
6ecfe7c7c3 remove duplicate method 2024-03-02 17:34:31 +00:00
dakkar
23f476dbf3 Merge branch 'develop' into release/2024.3.1 2024-03-02 17:28:34 +00:00
Amelia Yukii
7a1251423f merge: Add missing IMPORTANT_NOTES.md from Sharkey/OldJoinSharkey (!443)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/443

Approved-by: dakkar <dakkar@thenautilus.net>
Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-02-24 18:20:48 +00:00
Marie
7f5492a395 Add missing IMPORTANT_NOTES.md from Sharkey/OldJoinSharkey 2024-02-24 18:20:48 +00:00
Amelia Yukii
11d9fd9199 merge: import upstream ssrf fix on our stable (!425)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/425

Approved-by: Leah <kevinlukej@gmail.com>
Approved-by: Amelia Yukii <amelia.yukii@shourai.de>
2024-02-17 13:06:47 +00:00
syuilo
6132bc3b3e fix of 9a70ce8f5e
Co-Authored-By: RyotaK <49341894+Ry0taK@users.noreply.github.com>
2024-02-17 12:54:45 +00:00
dakkar
fef7a7b99a bump version 2024-02-17 12:38:01 +00:00
tamaina
1948ca9aa8 Merge pull request from GHSA-qqrm-9grj-6v32 2024-02-17 12:36:44 +00:00
Amelia Yukii
848e1f9a56 version is better
(cherry picked from commit fb455e4fd9)
2024-02-01 16:11:48 +00:00
Amelia Yukii
9c4353ee79 Update .gitlab-ci.yml
(cherry picked from commit 8c5818acf0)
2024-02-01 16:10:47 +00:00
Amelia Yukii
a6e257f502 Merge branch 'feture/code-injection-fix' into 'develop'
CVE: Fixed code injection from twitter import

See merge request TransFem-org/Sharkey!390

(cherry picked from commit 127f8556d4)

2a8e93e4 Fixed code injection from twitter import
2024-02-01 15:07:35 +00:00
Amelia Yukii
310e1a1262 Merge branch 'Amelia-stable-patch-29368' into 'stable'
Update docker-compose_example.yml

See merge request TransFem-org/Sharkey!389
2024-02-01 14:44:14 +00:00
Amelia Yukii
15f3c046d1 Update docker-compose_example.yml 2024-02-01 14:42:19 +00:00
Amelia Yukii
01d695428a Revert "build stable with stable tag"
This reverts commit acf3e3460f
2024-02-01 14:15:10 +00:00
Amelia Yukii
acf3e3460f build stable with stable tag 2024-02-01 14:00:56 +00:00
Amelia Yukii
4c8116859c Revert "Merge branch 'cherry-pick-3b2d47b1' into 'stable'"
This reverts merge request !386
2024-02-01 13:55:44 +00:00
Amelia Yukii
0e13397db7 Merge branch 'cherry-pick-3b2d47b1' into 'stable'
build stable with stable tag

See merge request TransFem-org/Sharkey!386
2024-02-01 13:41:34 +00:00
Amelia Yukii
ad8818508f Update file .gitlab-ci.yml
(cherry picked from commit 3b2d47b1e3)
2024-02-01 13:38:19 +00:00
Amelia Yukii
d444ee662f Merge branch 'cherry-pick-522ab39d' into 'stable'
Merge branch 'gitlab-ci' into 'develop'

See merge request TransFem-org/Sharkey!383
2024-02-01 10:23:23 +00:00
Amelia Yukii
4c354fff2d Merge branch 'gitlab-ci' into 'develop' 2024-02-01 10:23:23 +00:00
Marie
b81448edf6 merge: release 2023.12.0 2023-12-31 23:19:41 +01:00
Marie
134d2895f0 fix: merge conflict 2023-12-31 23:11:15 +01:00
Marie
7ba8fde9b9 chore: change version 2023-12-31 22:49:43 +01:00
Marie
1022280465
release: 2023.11.2 2023-12-01 00:01:19 +01:00
Marie
021d3924e6
chore: change version 2023-11-30 23:57:04 +01:00
Mar0xy
b6d50d781f
Merge branch 'stable' of https://github.com/transfem-org/Sharkey into stable 2023-11-26 18:47:44 +01:00
Mar0xy
1d411bb885
chore: fix locales 2023-11-26 18:47:20 +01:00
Marie
f7afd1ae4a
release: 2023.11.1 2023-11-26 17:28:42 +01:00
Marie
1ef1f2a03c
Merge branch 'stable' into release/2023.11.1 2023-11-26 17:26:30 +01:00
Marie
829ce4f86a
merge: 2023.11.0 2023-11-07 20:16:20 +01:00
Mar0xy
6d5d863150
merge: last minute changes 2023-11-07 20:07:53 +01:00
Marie
fc7d4bc420
chore: set release version 2023-11-07 19:39:18 +01:00
14 changed files with 168 additions and 19 deletions

View file

@ -55,6 +55,8 @@ getImageTag:
only: only:
- stable - stable
- develop - develop
- tags
buildDocker: buildDocker:
stage: deploy stage: deploy
needs: needs:
@ -78,6 +80,8 @@ buildDocker:
only: only:
- stable - stable
- develop - develop
- tags
mergeManifests: mergeManifests:
stage: deploy stage: deploy
needs: needs:
@ -103,3 +107,4 @@ mergeManifests:
only: only:
- stable - stable
- develop - develop
- tags

View file

@ -1,6 +1,6 @@
{ {
"name": "sharkey", "name": "sharkey",
"version": "2024.3.1", "version": "2024.3.2-devel",
"codename": "shonk", "codename": "shonk",
"repository": { "repository": {
"type": "git", "type": "git",

View file

@ -172,7 +172,7 @@
"stringz": "2.1.0", "stringz": "2.1.0",
"systeminformation": "5.22.0", "systeminformation": "5.22.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.2", "tmp": "0.2.3",
"tsc-alias": "1.8.8", "tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.20", "typeorm": "0.3.20",

View file

@ -192,6 +192,7 @@ export class FileServerService {
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes'); reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize); reply.header('Content-Length', chunksize);
reply.code(206);
} else { } else {
image = { image = {
data: fs.createReadStream(file.path), data: fs.createReadStream(file.path),
@ -261,7 +262,6 @@ export class FileServerService {
const parts = range.replace(/bytes=/, '').split('-'); const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10); const start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1; let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
console.log(end);
if (end > file.file.size) { if (end > file.file.size) {
end = file.file.size - 1; end = file.file.size - 1;
} }
@ -431,6 +431,7 @@ export class FileServerService {
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`); reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
reply.header('Accept-Ranges', 'bytes'); reply.header('Accept-Ranges', 'bytes');
reply.header('Content-Length', chunksize); reply.header('Content-Length', chunksize);
reply.code(206);
} else { } else {
image = { image = {
data: fs.createReadStream(file.path), data: fs.createReadStream(file.path),
@ -527,6 +528,9 @@ export class FileServerService {
if (!file.storedInternal) { if (!file.storedInternal) {
if (!(file.isLink && file.uri)) return '204'; if (!(file.isLink && file.uri)) return '204';
const result = await this.downloadAndDetectTypeFromUrl(file.uri); const result = await this.downloadAndDetectTypeFromUrl(file.uri);
if (!file.size) {
file.size = (await fs.promises.stat(result.path)).size;
}
return { return {
...result, ...result,
url: file.uri, url: file.uri,

View file

@ -51,6 +51,12 @@ export const paramDef = {
sinceDate: { type: 'integer' }, sinceDate: { type: 'integer' },
untilDate: { type: 'integer' }, untilDate: { type: 'integer' },
allowPartial: { type: 'boolean', default: false }, // true is recommended but for compatibility false by default allowPartial: { type: 'boolean', default: false }, // true is recommended but for compatibility false by default
withRenotes: { type: 'boolean', default: true },
withFiles: {
type: 'boolean',
default: false,
description: 'Only show notes that have attached files.',
},
}, },
required: ['channelId'], required: ['channelId'],
} as const; } as const;
@ -89,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (me) this.activeUsersChart.read(me); if (me) this.activeUsersChart.read(me);
if (!serverSettings.enableFanoutTimeline) { if (!serverSettings.enableFanoutTimeline) {
return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id }, me), me); return await this.noteEntityService.packMany(await this.getFromDb({ untilId, sinceId, limit: ps.limit, channelId: channel.id, withFiles: ps.withFiles, withRenotes: ps.withRenotes }, me), me);
} }
return await this.fanoutTimelineEndpointService.timeline({ return await this.fanoutTimelineEndpointService.timeline({
@ -100,9 +106,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
me, me,
useDbFallback: true, useDbFallback: true,
redisTimelines: [`channelTimeline:${channel.id}`], redisTimelines: [`channelTimeline:${channel.id}`],
excludePureRenotes: false, excludePureRenotes: !ps.withRenotes,
excludeNoFiles: ps.withFiles,
dbFallback: async (untilId, sinceId, limit) => { dbFallback: async (untilId, sinceId, limit) => {
return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id }, me); return await this.getFromDb({ untilId, sinceId, limit, channelId: channel.id, withFiles: ps.withFiles, withRenotes: ps.withRenotes }, me);
}, },
}); });
}); });
@ -112,7 +119,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
untilId: string | null, untilId: string | null,
sinceId: string | null, sinceId: string | null,
limit: number, limit: number,
channelId: string channelId: string,
withFiles: boolean,
withRenotes: boolean,
}, me: MiLocalUser | null) { }, me: MiLocalUser | null) {
//#region fallback to database //#region fallback to database
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
@ -128,6 +137,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.queryService.generateMutedUserQuery(query, me); this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me); this.queryService.generateBlockedUserQuery(query, me);
} }
if (ps.withRenotes === false) {
query.andWhere(new Brackets(qb => {
qb.orWhere('note.renoteId IS NULL');
qb.orWhere(new Brackets(qb => {
qb.orWhere('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\'');
}));
}));
}
if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
}
//#endregion //#endregion
return await query.limit(ps.limit).getMany(); return await query.limit(ps.limit).getMany();

View file

@ -15,6 +15,8 @@ class ChannelChannel extends Channel {
public static shouldShare = false; public static shouldShare = false;
public static requireCredential = false as const; public static requireCredential = false as const;
private channelId: string; private channelId: string;
private withFiles: boolean;
private withRenotes: boolean;
constructor( constructor(
private noteEntityService: NoteEntityService, private noteEntityService: NoteEntityService,
@ -29,6 +31,8 @@ class ChannelChannel extends Channel {
@bindThis @bindThis
public async init(params: any) { public async init(params: any) {
this.channelId = params.channelId as string; this.channelId = params.channelId as string;
this.withFiles = params.withFiles ?? false;
this.withRenotes = params.withRenotes ?? true;
// Subscribe stream // Subscribe stream
this.subscriber.on('notesStream', this.onNote); this.subscriber.on('notesStream', this.onNote);
@ -38,6 +42,10 @@ class ChannelChannel extends Channel {
private async onNote(note: Packed<'Note'>) { private async onNote(note: Packed<'Note'>) {
if (note.channelId !== this.channelId) return; if (note.channelId !== this.channelId) return;
if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoMeMuting)) return; if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する

View file

@ -5,8 +5,8 @@ block vars
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`; - const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`; - const url = `${config.url}/notes/${note.id}`;
- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null; - const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
- const images = (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive) - const images = note.cw ? [] : (note.files || []).filter(file => file.type.startsWith('image/') && !file.isSensitive)
- const videos = (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive) - const videos = note.cw ? [] : (note.files || []).filter(file => file.type.startsWith('video/') && !file.isSensitive)
block title block title
= `${title} | ${instanceName}` = `${title} | ${instanceName}`

View file

@ -154,6 +154,8 @@ function connectChannel() {
} else if (props.src === 'channel') { } else if (props.src === 'channel') {
if (props.channel == null) return; if (props.channel == null) return;
connection = stream.useChannel('channel', { connection = stream.useChannel('channel', {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
channelId: props.channel, channelId: props.channel,
}); });
} else if (props.src === 'role') { } else if (props.src === 'role') {
@ -234,6 +236,8 @@ function updatePaginationQuery() {
} else if (props.src === 'channel') { } else if (props.src === 'channel') {
endpoint = 'channels/timeline'; endpoint = 'channels/timeline';
query = { query = {
withRenotes: props.withRenotes,
withFiles: props.onlyFiles ? true : undefined,
channelId: props.channel, channelId: props.channel,
}; };
} else if (props.src === 'role') { } else if (props.src === 'role') {

View file

@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<!-- スマホタブレットの場合キーボードが表示されると投稿が見づらくなるのでデスクトップ場合のみ自動でフォーカスを当てる --> <!-- スマホタブレットの場合キーボードが表示されると投稿が見づらくなるのでデスクトップ場合のみ自動でフォーカスを当てる -->
<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/> <MkPostForm v-if="$i && defaultStore.reactiveState.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())"/> <MkTimeline :key="channelId + withRenotes + onlyFiles" src="channel" :channel="channelId" :withRenotes="withRenotes" :onlyFiles="onlyFiles" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
</div> </div>
<div v-else-if="tab === 'featured'" key="featured"> <div v-else-if="tab === 'featured'" key="featured">
<MkNotes :pagination="featuredPagination"/> <MkNotes :pagination="featuredPagination"/>
@ -95,6 +95,7 @@ import { isSupportShare } from '@/scripts/navigator.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js'; import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { deepMerge } from '@/scripts/merge.js';
const router = useRouter(); const router = useRouter();
@ -116,6 +117,15 @@ const featuredPagination = computed(() => ({
channelId: props.channelId, channelId: props.channelId,
}, },
})); }));
const withRenotes = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
set: (x) => saveTlFilter('withRenotes', x),
});
const onlyFiles = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles,
set: (x) => saveTlFilter('onlyFiles', x),
});
watch(() => props.channelId, async () => { watch(() => props.channelId, async () => {
channel.value = await misskeyApi('channels/show', { channel.value = await misskeyApi('channels/show', {
@ -136,6 +146,13 @@ watch(() => props.channelId, async () => {
} }
}, { immediate: true }); }, { immediate: true });
function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) {
if (key !== 'withReplies' || $i) {
const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl);
defaultStore.set('tl', out);
}
}
function edit() { function edit() {
router.push(`/channels/${channel.value?.id}/edit`); router.push(`/channels/${channel.value?.id}/edit`);
} }
@ -192,7 +209,21 @@ async function search() {
const headerActions = computed(() => { const headerActions = computed(() => {
if (channel.value && channel.value.userId) { if (channel.value && channel.value.userId) {
const headerItems: PageHeaderItem[] = []; const headerItems: PageHeaderItem[] = [{
icon: 'ph-dots-three ph-bold ph-lg',
text: i18n.ts.options,
handler: (ev) => {
os.popupMenu([{
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
}, {
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
}], ev.currentTarget ?? ev.target);
},
}];
headerItems.push({ headerItems.push({
icon: 'ph-share-network ph-bold ph-lg', icon: 'ph-share-network ph-bold ph-lg',

View file

@ -11,10 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <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"> <div :class="$style.tl">
<MkTimeline <MkTimeline
ref="tlEl" :key="listId" ref="tlEl" :key="listId + withRenotes + onlyFiles"
src="list" src="list"
:list="listId" :list="listId"
:sound="true" :sound="true"
:withRenotes="withRenotes"
:onlyFiles="onlyFiles"
@queue="queueUpdated" @queue="queueUpdated"
/> />
</div> </div>
@ -32,6 +34,9 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
import { definePageMetadata } from '@/scripts/page-metadata.js'; import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { useRouter } from '@/router/supplier.js'; import { useRouter } from '@/router/supplier.js';
import { defaultStore } from '@/store.js';
import { deepMerge } from '@/scripts/merge.js';
import * as os from '@/os.js';
const router = useRouter(); const router = useRouter();
@ -43,6 +48,21 @@ const list = ref<Misskey.entities.UserList | null>(null);
const queue = ref(0); const queue = ref(0);
const tlEl = shallowRef<InstanceType<typeof MkTimeline>>(); const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
const rootEl = shallowRef<HTMLElement>(); const rootEl = shallowRef<HTMLElement>();
const withRenotes = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
set: (x) => saveTlFilter('withRenotes', x),
});
const onlyFiles = computed<boolean>({
get: () => defaultStore.reactiveState.tl.value.filter.onlyFiles,
set: (x) => saveTlFilter('onlyFiles', x),
});
function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) {
if (key !== 'withReplies' || $i) {
const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl);
defaultStore.set('tl', out);
}
}
watch(() => props.listId, async () => { watch(() => props.listId, async () => {
list.value = await misskeyApi('users/lists/show', { list.value = await misskeyApi('users/lists/show', {
@ -63,6 +83,20 @@ function settings() {
} }
const headerActions = computed(() => list.value ? [{ const headerActions = computed(() => list.value ? [{
icon: 'ph-dots-three ph-bold ph-lg',
text: i18n.ts.options,
handler: (ev) => {
os.popupMenu([{
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
}, {
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
}], ev.currentTarget ?? ev.target);
},
}, {
icon: 'ph-gear ph-bold ph-lg', icon: 'ph-gear ph-bold ph-lg',
text: i18n.ts.settings, text: i18n.ts.settings,
handler: settings, handler: settings,

View file

@ -13,13 +13,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<div style="padding: 8px; text-align: center;"> <div style="padding: 8px; text-align: center;">
<MkButton primary gradate rounded inline small @click="post"><i class="ph-pencil-simple ph-bold ph-lg"></i></MkButton> <MkButton primary gradate rounded inline small @click="post"><i class="ph-pencil-simple ph-bold ph-lg"></i></MkButton>
</div> </div>
<MkTimeline ref="timeline" src="channel" :channel="column.channelId"/> <MkTimeline ref="timeline" src="channel" :channel="column.channelId" :key="column.channelId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/>
</template> </template>
</XColumn> </XColumn>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { shallowRef } from 'vue'; import { watch, ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import XColumn from './column.vue'; import XColumn from './column.vue';
import { updateColumn, Column } from './deck-store.js'; import { updateColumn, Column } from './deck-store.js';
@ -36,6 +36,20 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const channel = shallowRef<Misskey.entities.Channel>(); const channel = shallowRef<Misskey.entities.Channel>();
const withRenotes = ref(props.column.withRenotes ?? true);
const onlyFiles = ref(props.column.onlyFiles ?? false);
watch(withRenotes, v => {
updateColumn(props.column.id, {
withRenotes: v,
});
});
watch(onlyFiles, v => {
updateColumn(props.column.id, {
onlyFiles: v,
});
});
if (props.column.channelId == null) { if (props.column.channelId == null) {
setChannel(); setChannel();
@ -75,5 +89,13 @@ const menu = [{
icon: 'ph-pencil-simple ph-bold ph-lg', icon: 'ph-pencil-simple ph-bold ph-lg',
text: i18n.ts.selectChannel, text: i18n.ts.selectChannel,
action: setChannel, action: setChannel,
}, {
type: 'switch',
text: i18n.ts.showRenotes,
ref: withRenotes,
}, {
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
}]; }];
</script> </script>

View file

@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<i class="ph-list ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span> <i class="ph-list ph-bold ph-lg"></i><span style="margin-left: 8px;">{{ column.name }}</span>
</template> </template>
<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/> <MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :key="column.listId + column.withRenotes + column.onlyFiles" :withRenotes="withRenotes" :onlyFiles="onlyFiles"/>
</XColumn> </XColumn>
</template> </template>
@ -29,6 +29,7 @@ const props = defineProps<{
const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
const withRenotes = ref(props.column.withRenotes ?? true); const withRenotes = ref(props.column.withRenotes ?? true);
const onlyFiles = ref(props.column.onlyFiles ?? false);
if (props.column.listId == null) { if (props.column.listId == null) {
setList(); setList();
@ -40,6 +41,12 @@ watch(withRenotes, v => {
}); });
}); });
watch(onlyFiles, v => {
updateColumn(props.column.id, {
onlyFiles: v,
});
});
async function setList() { async function setList() {
const lists = await misskeyApi('users/lists/list'); const lists = await misskeyApi('users/lists/list');
const { canceled, result: list } = await os.select({ const { canceled, result: list } = await os.select({
@ -75,5 +82,10 @@ const menu = [
text: i18n.ts.showRenotes, text: i18n.ts.showRenotes,
ref: withRenotes, ref: withRenotes,
}, },
{
type: 'switch',
text: i18n.ts.fileAttachedOnly,
ref: onlyFiles,
},
]; ];
</script> </script>

View file

@ -392,8 +392,8 @@ importers:
specifier: 1.6.0 specifier: 1.6.0
version: 1.6.0 version: 1.6.0
tmp: tmp:
specifier: 0.2.2 specifier: 0.2.3
version: 0.2.2 version: 0.2.3
tsc-alias: tsc-alias:
specifier: 1.8.8 specifier: 1.8.8
version: 1.8.8 version: 1.8.8
@ -18813,6 +18813,12 @@ packages:
engines: {node: '>=14'} engines: {node: '>=14'}
dependencies: dependencies:
rimraf: 5.0.5 rimraf: 5.0.5
dev: true
/tmp@0.2.3:
resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==}
engines: {node: '>=14.14'}
dev: false
/tmpl@1.0.5: /tmpl@1.0.5:
resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}