diff options
| author | Hazelnoot <acomputerdog@gmail.com> | 2024-10-15 18:01:57 -0400 |
|---|---|---|
| committer | Hazelnoot <acomputerdog@gmail.com> | 2024-10-15 18:09:11 -0400 |
| commit | 8a34d8e9d25546f7ef42f072a69f9923d5ba2e84 (patch) | |
| tree | 4523856296102c786f724243375d198f3fada789 /packages/frontend | |
| parent | Fix indentation on locales/generateDTS.js (diff) | |
| parent | merge: Refresh locales after any change, not just a version update (resolves ... (diff) | |
| download | sharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.tar.gz sharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.tar.bz2 sharkey-8a34d8e9d25546f7ef42f072a69f9923d5ba2e84.zip | |
Merge branch 'develop' into feature/2024.9.0
# Conflicts:
# locales/en-US.yml
# locales/ja-JP.yml
# packages/backend/src/core/NoteCreateService.ts
# packages/backend/src/core/NoteDeleteService.ts
# packages/backend/src/core/NoteEditService.ts
# packages/frontend-shared/js/config.ts
# packages/frontend/src/boot/common.ts
# packages/frontend/src/pages/following-feed.vue
# packages/misskey-js/src/autogen/endpoint.ts
Diffstat (limited to 'packages/frontend')
| -rw-r--r-- | packages/frontend/@types/global.d.ts | 1 | ||||
| -rw-r--r-- | packages/frontend/src/boot/common.ts | 9 | ||||
| -rw-r--r-- | packages/frontend/src/components/SkUserRecentNotes.vue | 22 | ||||
| -rw-r--r-- | packages/frontend/src/navbar.ts | 2 | ||||
| -rw-r--r-- | packages/frontend/src/pages/follow-requests.vue | 92 | ||||
| -rw-r--r-- | packages/frontend/src/pages/following-feed.vue | 150 | ||||
| -rw-r--r-- | packages/frontend/src/store.ts | 11 | ||||
| -rw-r--r-- | packages/frontend/vite.config.ts | 3 |
8 files changed, 181 insertions, 109 deletions
diff --git a/packages/frontend/@types/global.d.ts b/packages/frontend/@types/global.d.ts index 1025d1bedb..15373cbd2d 100644 --- a/packages/frontend/@types/global.d.ts +++ b/packages/frontend/@types/global.d.ts @@ -6,6 +6,7 @@ type FIXME = any; declare const _LANGS_: string[][]; +declare const _LANGS_VERSION_: string; declare const _VERSION_: string; declare const _ENV_: string; declare const _DEV_: boolean; diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index 487eefe60a..af8bbf57d2 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -5,7 +5,7 @@ import { computed, watch, version as vueVersion, App } from 'vue'; import { compareVersions } from 'compare-versions'; -import { version, lang, updateLocale, locale } from '@@/js/config.js'; +import { version, lang, langsVersion, updateLocale, locale } from '@@/js/config.js'; import widgets from '@/widgets/index.js'; import directives from '@/directives/index.js'; import components from '@/components/index.js'; @@ -81,14 +81,15 @@ export async function common(createVue: () => App<Element>) { //#region Detect language & fetch translations const localeVersion = miLocalStorage.getItem('localeVersion'); - const localeOutdated = (localeVersion == null || localeVersion !== version || locale == null); + const localeOutdated = (localeVersion == null || localeVersion !== langsVersion || locale == null); if (localeOutdated) { - const res = await window.fetch(`/assets/locales/${lang}.${version}.json`); + console.info(`Updating locales from version ${localeVersion ?? 'N/A'} to ${langsVersion}`); + const res = await window.fetch(`/assets/locales/${lang}.${langsVersion}.json`); if (res.status === 200) { const newLocale = await res.text(); const parsedNewLocale = JSON.parse(newLocale); miLocalStorage.setItem('locale', newLocale); - miLocalStorage.setItem('localeVersion', version); + miLocalStorage.setItem('localeVersion', langsVersion); updateLocale(parsedNewLocale); updateI18n(parsedNewLocale); } diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue index 1d124b4932..2cdb4b6586 100644 --- a/packages/frontend/src/components/SkUserRecentNotes.vue +++ b/packages/frontend/src/components/SkUserRecentNotes.vue @@ -24,16 +24,14 @@ import MkPullToRefresh from '@/components/MkPullToRefresh.vue'; import { Paging } from '@/components/MkPagination.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; -const props = withDefaults(defineProps<{ +const props = defineProps<{ userId: string; - withRenotes?: boolean; - withReplies?: boolean; - onlyFiles?: boolean; -}>(), { - withRenotes: false, - withReplies: true, - onlyFiles: false, -}); + withNonPublic: boolean; + withQuotes: boolean; + withReplies: boolean; + withBots: boolean; + onlyFiles: boolean; +}>(); const loadError: Ref<string | null> = ref(null); const user: Ref<Misskey.entities.UserDetailed | null> = ref(null); @@ -43,9 +41,13 @@ const pagination: Paging<'users/notes'> = { limit: 10, params: computed(() => ({ userId: props.userId, - withRenotes: props.withRenotes, + withNonPublic: props.withNonPublic, + withRenotes: false, + withQuotes: props.withQuotes, withReplies: props.withReplies, + withRepliesToSelf: props.withReplies, withFiles: props.onlyFiles, + allowPartial: true, })), }; diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts index b92fdb17b9..8b45eb50e7 100644 --- a/packages/frontend/src/navbar.ts +++ b/packages/frontend/src/navbar.ts @@ -41,7 +41,7 @@ export const navbarItemDef = reactive({ followRequests: { title: i18n.ts.followRequests, icon: 'ti ti-user-plus', - show: computed(() => $i != null && $i.isLocked), + show: computed(() => $i != null && ($i.isLocked || $i.hasPendingReceivedFollowRequest || $i.hasPendingSentFollowRequest)), indicated: computed(() => $i != null && $i.hasPendingReceivedFollowRequest), to: '/my/follow-requests', }, diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index d50887b2e9..400dfdbe6d 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -5,39 +5,43 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkStickyContainer> - <template #header><MkPageHeader/></template> + <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="800"> - <MkPagination ref="paginationComponent" :pagination="pagination"> - <template #empty> - <div class="_fullinfo"> - <img :src="infoImageUrl" class="_ghost"/> - <div>{{ i18n.ts.noFollowRequests }}</div> - </div> - </template> - <template #default="{items}"> - <div class="mk-follow-requests"> - <div v-for="req in items" :key="req.id" class="user _panel"> - <MkAvatar class="avatar" :user="req.follower" indicator link preview/> - <div class="body"> - <div class="name"> - <MkA v-user-preview="req.follower.id" class="name" :to="userPage(req.follower)"><MkUserName :user="req.follower"/></MkA> - <p class="acct">@{{ acct(req.follower) }}</p> - </div> - <div class="commands"> - <MkButton class="command" rounded primary @click="accept(req.follower)"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton> - <MkButton class="command" rounded danger @click="reject(req.follower)"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> + <MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs"> + <div :key="tab" class="_gaps"> + <MkPagination ref="paginationComponent" :pagination="pagination"> + <template #empty> + <div class="_fullinfo"> + <img :src="infoImageUrl" class="_ghost"/> + <div>{{ i18n.ts.noFollowRequests }}</div> + </div> + </template> + <template #default="{items}"> + <div class="mk-follow-requests"> + <div v-for="req in items" :key="req.id" class="user _panel"> + <MkAvatar class="avatar" :user="displayUser(req)" indicator link preview/> + <div class="body"> + <div class="name"> + <MkA v-user-preview="displayUser(req).id" class="name" :to="userPage(displayUser(req))"><MkUserName :user="displayUser(req)"/></MkA> + <p class="acct">@{{ acct(displayUser(req)) }}</p> + </div> + <div v-if="tab === 'list'" class="commands"> + <MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton> + <MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> + </div> + </div> </div> </div> - </div> - </div> - </template> - </MkPagination> + </template> + </MkPagination> + </div> + </MkHorizontalSwipe> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { shallowRef, computed } from 'vue'; +import { shallowRef, computed, ref } from 'vue'; import MkPagination from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import { userPage, acct } from '@/filters/user.js'; @@ -45,29 +49,53 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { infoImageUrl } from '@/instance.js'; +import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; +import { $i } from '@/account'; const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); -const pagination = { - endpoint: 'following/requests/list' as const, - limit: 10, -}; +const pagination = computed(() => tab.value === 'list' + ? { + endpoint: 'following/requests/list' as const, + limit: 10, + } + : { + endpoint: 'following/requests/sent' as const, + limit: 10, + }, +); function accept(user) { misskeyApi('following/requests/accept', { userId: user.id }).then(() => { - paginationComponent.value.reload(); + paginationComponent.value?.reload(); }); } function reject(user) { misskeyApi('following/requests/reject', { userId: user.id }).then(() => { - paginationComponent.value.reload(); + paginationComponent.value?.reload(); }); } +function displayUser(req) { + return tab.value === 'list' ? req.follower : req.followee; +} + const headerActions = computed(() => []); -const headerTabs = computed(() => []); +const headerTabs = computed(() => [ + { + key: 'list', + title: i18n.ts.followRequests, + icon: 'ph-envelope ph-bold ph-lg', + }, { + key: 'sent', + title: i18n.ts.pendingFollowRequests, + icon: 'ph-paper-plane-tilt ph-bold ph-lg', + }, +]); + +const tab = ref($i?.isLocked || !$i.hasPendingSentFollowRequest ? 'list' : 'sent'); definePageMetadata(() => ({ title: i18n.ts.followRequests, diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue index 8f5a980e42..f49cafb52f 100644 --- a/packages/frontend/src/pages/following-feed.vue +++ b/packages/frontend/src/pages/following-feed.vue @@ -30,18 +30,12 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="isWideViewport" ref="userScroll" :class="$style.user"> <MkHorizontalSwipe v-if="selectedUserId" v-model:tab="currentTab" :tabs="headerTabs"> - <SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withRenotes="withUserRenotes" :withReplies="withUserReplies" :onlyFiles="withOnlyFiles"/> + <SkUserRecentNotes ref="userRecentNotes" :userId="selectedUserId" :withNonPublic="withNonPublic" :withQuotes="withQuotes" :withBots="withBots" :withReplies="withReplies" :onlyFiles="onlyFiles"/> </MkHorizontalSwipe> </div> </div> </template> -<script lang="ts"> -export type FollowingFeedTab = typeof followingTab | typeof mutualsTab; -export const followingTab = 'following' as const; -export const mutualsTab = 'mutuals' as const; -</script> - <script lang="ts" setup> import { computed, Ref, ref, shallowRef } from 'vue'; import * as Misskey from 'misskey-js'; @@ -63,20 +57,49 @@ import { checkWordMute } from '@/scripts/check-word-mute.js'; import SkUserRecentNotes from '@/components/SkUserRecentNotes.vue'; import { useScrollPositionManager } from '@/nirax.js'; import { getScrollContainer } from '@@/js/scroll.js'; +import { defaultStore } from '@/store.js'; +import { deepMerge } from '@/scripts/merge.js'; -const props = withDefaults(defineProps<{ - initialTab?: FollowingFeedTab, -}>(), { - initialTab: followingTab, +const withNonPublic = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.withNonPublic, + set: value => saveFollowingFilter('withNonPublic', value), +}); +const withQuotes = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.withQuotes, + set: value => saveFollowingFilter('withQuotes', value), +}); +const withBots = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.withBots, + set: value => saveFollowingFilter('withBots', value), +}); +const withReplies = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.withReplies, + set: value => saveFollowingFilter('withReplies', value), +}); +const onlyFiles = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.onlyFiles, + set: value => saveFollowingFilter('onlyFiles', value), +}); +const onlyMutuals = computed({ + get: () => defaultStore.reactiveState.followingFeed.value.onlyMutuals, + set: value => saveFollowingFilter('onlyMutuals', value), }); +// Based on timeline.saveTlFilter() +function saveFollowingFilter(key: keyof typeof defaultStore.state.followingFeed, value: boolean) { + const out = deepMerge({ [key]: value }, defaultStore.state.followingFeed); + defaultStore.set('followingFeed', out); +} + const router = useRouter(); -// Vue complains, but we *want* to lose reactivity here. -// Otherwise, the user would be unable to change the tab. -// eslint-disable-next-line vue/no-setup-props-reactivity-loss -const currentTab: Ref<FollowingFeedTab> = ref(props.initialTab); -const mutualsOnly: Ref<boolean> = computed(() => currentTab.value === mutualsTab); +const followingTab = 'following' as const; +const mutualsTab = 'mutuals' as const; +const currentTab = computed({ + get: () => onlyMutuals.value ? mutualsTab : followingTab, + set: value => onlyMutuals.value = (value === mutualsTab), +}); + const userRecentNotes = shallowRef<InstanceType<typeof SkUserRecentNotes>>(); const userScroll = shallowRef<HTMLElement>(); const noteScroll = shallowRef<HTMLElement>(); @@ -161,55 +184,60 @@ const latestNotesPagination: Paging<'notes/following'> = { endpoint: 'notes/following' as const, limit: 20, params: computed(() => ({ - mutualsOnly: mutualsOnly.value, + mutualsOnly: onlyMutuals.value, + filesOnly: onlyFiles.value, + includeNonPublic: withNonPublic.value, + includeReplies: withReplies.value, + includeQuotes: withQuotes.value, + includeBots: withBots.value, })), }; -const withUserRenotes = ref(false); -const withUserReplies = ref(true); -const withOnlyFiles = ref(false); - -const headerActions = computed(() => { - const actions: PageHeaderItem[] = [ - { - icon: 'ti ti-refresh', - text: i18n.ts.reload, - handler: () => reload(), +const headerActions: PageHeaderItem[] = [ + { + icon: 'ti ti-refresh', + text: i18n.ts.reload, + handler: () => reload(), + }, + { + icon: 'ti ti-dots', + text: i18n.ts.options, + handler: (ev) => { + os.popupMenu([ + { + type: 'switch', + text: i18n.ts.showNonPublicNotes, + ref: withNonPublic, + }, + { + type: 'switch', + text: i18n.ts.showQuotes, + ref: withQuotes, + }, + { + type: 'switch', + text: i18n.ts.showBots, + ref: withBots, + }, + { + type: 'switch', + text: i18n.ts.showReplies, + ref: withReplies, + disabled: onlyFiles, + }, + { + type: 'divider', + }, + { + type: 'switch', + text: i18n.ts.fileAttachedOnly, + ref: onlyFiles, + disabled: withReplies, + }, + ], ev.currentTarget ?? ev.target); }, - ]; - - if (isWideViewport.value) { - actions.push({ - icon: 'ti ti-dots', - text: i18n.ts.options, - handler: (ev) => { - os.popupMenu([ - { - type: 'switch', - text: i18n.ts.showRenotes, - ref: withUserRenotes, - }, { - type: 'switch', - text: i18n.ts.showRepliesToOthersInTimeline, - ref: withUserReplies, - disabled: withOnlyFiles, - }, - { - type: 'divider', - }, - { - type: 'switch', - text: i18n.ts.fileAttachedOnly, - ref: withOnlyFiles, - disabled: withUserReplies, - }, - ], ev.currentTarget ?? ev.target); - }, - }); - } - - return actions; -}); + }, +]; const headerTabs = computed(() => [ { diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index b8ef11ccb1..a00afcd79a 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -241,6 +241,17 @@ export const defaultStore = markRaw(new Storage('base', { where: 'deviceAccount', default: [] as Misskey.entities.UserList[], }, + followingFeed: { + where: 'account', + default: { + withNonPublic: false, + withQuotes: false, + withBots: true, + withReplies: false, + onlyFiles: false, + onlyMutuals: false, + }, + }, overridedDeviceKind: { where: 'device', diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 6f3246c16f..518175c925 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -2,7 +2,7 @@ import path from 'path'; import pluginReplace from '@rollup/plugin-replace'; import pluginVue from '@vitejs/plugin-vue'; import { type UserConfig, defineConfig } from 'vite'; - +import { localesVersion } from '../../locales/version.js'; import locales from '../../locales/index.js'; import meta from '../../package.json'; import packageInfo from './package.json' with { type: 'json' }; @@ -114,6 +114,7 @@ export function getConfig(): UserConfig { define: { _VERSION_: JSON.stringify(meta.version), _LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])), + _LANGS_VERSION_: JSON.stringify(localesVersion), _ENV_: JSON.stringify(process.env.NODE_ENV), _DEV_: process.env.NODE_ENV !== 'production', _PERF_PREFIX_: JSON.stringify('Misskey:'), |