summaryrefslogtreecommitdiff
path: root/packages/frontend
diff options
context:
space:
mode:
authorHazelnoot <acomputerdog@gmail.com>2024-10-15 18:01:57 -0400
committerHazelnoot <acomputerdog@gmail.com>2024-10-15 18:09:11 -0400
commit8a34d8e9d25546f7ef42f072a69f9923d5ba2e84 (patch)
tree4523856296102c786f724243375d198f3fada789 /packages/frontend
parentFix indentation on locales/generateDTS.js (diff)
parentmerge: Refresh locales after any change, not just a version update (resolves ... (diff)
downloadsharkey-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.ts1
-rw-r--r--packages/frontend/src/boot/common.ts9
-rw-r--r--packages/frontend/src/components/SkUserRecentNotes.vue22
-rw-r--r--packages/frontend/src/navbar.ts2
-rw-r--r--packages/frontend/src/pages/follow-requests.vue92
-rw-r--r--packages/frontend/src/pages/following-feed.vue150
-rw-r--r--packages/frontend/src/store.ts11
-rw-r--r--packages/frontend/vite.config.ts3
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:'),