diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56c2552145..683c0a85a5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,8 @@
- ローカリゼーションの更新
### Client
+- 任意のユーザーリストをタイムラインページにピン留めできるように
+ - 設定->クライアント設定->全般 から設定可能です
- ノート詳細ページを改修
- 読み込み時のパフォーマンスが向上しました
- リノート一覧、リアクション一覧がタブとして追加されました
diff --git a/locales/index.d.ts b/locales/index.d.ts
index ac714258e2..bf7aff75df 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1114,6 +1114,7 @@ export interface Locale {
"renotes": string;
"loadReplies": string;
"loadConversation": string;
+ "pinnedList": string;
"_announcement": {
"forExistingUsers": string;
"forExistingUsersDescription": string;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index d97b09f63c..2f88128c98 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1111,6 +1111,7 @@ replies: "返信"
renotes: "リノート"
loadReplies: "返信を見る"
loadConversation: "会話を見る"
+pinnedList: "ピン留めされたリスト"
_announcement:
forExistingUsers: "既存ユーザーのみ"
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index b486e6d80f..ff498e4164 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -30,6 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.showFixedPostForm }}
{{ i18n.ts.showFixedPostFormInChannel }}
{{ i18n.ts.flagShowTimelineReplies }}{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}
+
+ {{ i18n.ts.pinnedList }}
+
+ {{ i18n.ts.add }}
+ {{ i18n.ts.remove }}
+
@@ -307,6 +313,23 @@ function removeEmojiIndex(lang: string) {
os.promiseDialog(main());
}
+async function setPinnedList() {
+ const lists = await os.api('users/lists/list');
+ const { canceled, result: list } = await os.select({
+ title: i18n.ts.selectList,
+ items: lists.map(x => ({
+ value: x, text: x.name,
+ })),
+ });
+ if (canceled) return;
+
+ defaultStore.set('pinnedUserLists', [list]);
+}
+
+function removePinnedList() {
+ defaultStore.set('pinnedUserLists', []);
+}
+
let smashCount = 0;
let smashTimer: number | null = null;
function testNotification(): void {
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 307d77882b..c0b1f03ff0 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -16,7 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
@@ -102,10 +103,15 @@ async function chooseChannel(ev: MouseEvent): Promise {
os.popupMenu(items, ev.currentTarget ?? ev.target);
}
-function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global'): void {
+function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | `list:${string}`): void {
+ let userList = null;
+ if (newSrc.startsWith('userList:')) {
+ const id = newSrc.substring('userList:'.length);
+ userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id);
+ }
defaultStore.set('tl', {
- ...defaultStore.state.tl,
src: newSrc,
+ userList,
});
srcWhenNotSignin = newSrc;
}
@@ -125,7 +131,12 @@ function focus(): void {
const headerActions = $computed(() => []);
-const headerTabs = $computed(() => [{
+const headerTabs = $computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({
+ key: 'list:' + l.id,
+ title: l.name,
+ icon: 'ti ti-star',
+ iconOnly: true,
+}))), {
key: 'home',
title: i18n.ts._timelines.home,
icon: 'ti ti-home',
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 787a584f83..4b8001bfb4 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -4,7 +4,7 @@
*/
import { markRaw, ref } from 'vue';
-import misskey from 'misskey-js';
+import * as Misskey from 'misskey-js';
import { Storage } from './pizzax';
interface PostFormAction {
@@ -163,10 +163,14 @@ export const defaultStore = markRaw(new Storage('base', {
tl: {
where: 'deviceAccount',
default: {
- src: 'home' as 'home' | 'local' | 'social' | 'global',
- arg: null,
+ src: 'home' as 'home' | 'local' | 'social' | 'global' | `list:${string}`,
+ userList: null as Misskey.entities.UserList | null,
},
},
+ pinnedUserLists: {
+ where: 'deviceAccount',
+ default: [] as Misskey.entities.UserList[],
+ },
overridedDeviceKind: {
where: 'device',