diff --git a/CHANGELOG_YOJO.md b/CHANGELOG_YOJO.md index f47e502fcd..0e52f14901 100644 --- a/CHANGELOG_YOJO.md +++ b/CHANGELOG_YOJO.md @@ -33,6 +33,7 @@ - リモートユーザー高度な検索画面で照会しますか?のダイアログが出ない問題 - ユーザー検索画面で照会しますか?のダイアログが2つ出る問題 - Fix: 更新情報を確認のCherryPickの項目へのリンクを修正 +- Feat: お気に入りのタグリストを作成できるように ### Server - Fix: ユーザーnull(System)の場合forceがfalseでも新規追加されるのを修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index 65808d30ba..ecef123d81 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5575,6 +5575,14 @@ export interface Locale extends ILocale { * プロフィールを翻訳する */ "translateProfile": string; + /** + * タグ名を入力 + */ + "enterTagName": string; + /** + * タグに使用できない文字が含まれています + */ + "invalidTagName": string; "_official_tag": { /** * 公式タグ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 2d5350f859..0fc3f38434 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1388,6 +1388,8 @@ additionalPermissionsForFlash: "Playへの追加許可" thisFlashRequiresTheFollowingPermissions: "このPlayは以下の権限を要求しています" doYouWantToAllowThisPlayToAccessYourAccount: "このPlayによるアカウントへのアクセスを許可しますか?" translateProfile: "プロフィールを翻訳する" +enterTagName: "タグ名を入力" +invalidTagName: "タグに使用できない文字が含まれています" _official_tag: title: "公式タグ" diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index 46a01d47f8..9ed393da56 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -93,6 +93,12 @@ export const navbarItemDef = reactive({ show: computed(() => $i != null), to: '/my/antennas', }, + tags: { + title: i18n.ts.tags, + icon: 'ti ti-hash', + show: computed(() => $i != null), + to: '/my/tags', + }, favorites: { title: i18n.ts.favorites, icon: 'ti ti-star', diff --git a/packages/frontend/src/pages/my-tags/index.vue b/packages/frontend/src/pages/my-tags/index.vue new file mode 100644 index 0000000000..107826e9f6 --- /dev/null +++ b/packages/frontend/src/pages/my-tags/index.vue @@ -0,0 +1,150 @@ + + + + + + + diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 27a35d35cb..f5fa4400ce 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -127,6 +127,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [ 'enableListTimeline', 'enableAntennaTimeline', 'enableMediaTimeline', + 'enableTagTimeline', 'useEnterToSend', 'postFormVisibilityHotkey', 'showRenoteConfirmPopup', diff --git a/packages/frontend/src/pages/settings/timeline.vue b/packages/frontend/src/pages/settings/timeline.vue index 7e26b680b7..8cee762924 100644 --- a/packages/frontend/src/pages/settings/timeline.vue +++ b/packages/frontend/src/pages/settings/timeline.vue @@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.lists }} {{ i18n.ts.antennas }} + {{ i18n.ts.tags }}
@@ -59,6 +60,7 @@ const enableGlobalTimeline = computed(defaultStore.makeGetterSetter('enableGloba const enableListTimeline = computed(defaultStore.makeGetterSetter('enableListTimeline')); const enableAntennaTimeline = computed(defaultStore.makeGetterSetter('enableAntennaTimeline')); const enableMediaTimeline = computed(defaultStore.makeGetterSetter('enableMediaTimeline')); +const enableTagTimeline = computed(defaultStore.makeGetterSetter('enableTagTimeline')); const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 3336c49f10..880911df71 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -230,6 +230,47 @@ async function chooseAntenna(ev: MouseEvent): Promise { os.popupMenu(items, ev.currentTarget ?? ev.target); } +async function chooseHashTag(ev: MouseEvent): Promise { + let tags: string[]; + try { + tags = await misskeyApi('i/registry/get', { + scope: ['client', 'base'], + key: 'hashTag', + }); + } catch (err) { + if (err.code === 'NO_SUCH_KEY') { + tags = []; + await misskeyApi('i/registry/set', { + scope: ['client', 'base'], + key: 'hashTag', + value: [], + }); + tags = await misskeyApi('i/registry/get', { + scope: ['client', 'base'], + key: 'hashTag', + }); + } else { + throw err; + } + } + + const items: MenuItem[] = [ + ...tags.map(tag => ({ + type: 'link' as const, + text: tag, + to: `/tags/${encodeURIComponent(tag)}`, + })), + (tags.length === 0 ? undefined : { type: 'divider' }), + { + type: 'link' as const, + icon: 'ti ti-plus', + text: i18n.ts.createNew, + to: '/my/tags', + }, + ]; + os.popupMenu(items, ev.currentTarget ?? ev.target); +} + async function chooseChannel(ev: MouseEvent): Promise { const channels = await favoritedChannelsCache.fetch(); const items: MenuItem[] = [ @@ -380,6 +421,11 @@ const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserList title: i18n.ts.antennas, iconOnly: true, onClick: chooseAntenna, +}] : []), ...(defaultStore.state.enableTagTimeline ? [{ + icon: 'ti ti-hash', + title: i18n.ts.tags, + iconOnly: true, + onClick: chooseHashTag, }] : [])] as Tab[]); const headerTabsWhenNotLogin = computed(() => [...availableBasicTimelines().map(tl => ({ diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index a207099c92..17e639627c 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -252,7 +252,7 @@ const routes: RouteDef[] = [{ origin: 'origin', }, }, { - // Legacy Compatibility + // Legacy Compatibility path: '/authorize-follow', redirect: '/lookup', loginRequired: true, @@ -552,6 +552,10 @@ const routes: RouteDef[] = [{ path: '/my/clips', component: page(() => import('@/pages/my-clips/index.vue')), loginRequired: true, +}, { + path: '/my/tags', + component: page(() => import('@/pages/my-tags/index.vue')), + loginRequired: true, }, { path: '/my/antennas/create', component: page(() => import('@/pages/my-antennas/create.vue')), diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index bd018ef8f8..87af7c6e64 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -632,6 +632,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: true, }, + enableTagTimeline: { + where: 'device', + default: true, + }, // - Settings/Sounds & Vibrations vibrate: {