From 535d316839f8dc3bddb9a473f07d980b0f974d7a Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 10 May 2025 21:39:25 -0400 Subject: [PATCH 001/333] add warning banner about word mute limitations --- locales/index.d.ts | 4 ++++ packages/frontend/src/pages/settings/mute-block.vue | 5 +++++ sharkey-locales/en-US.yml | 1 + 3 files changed, 10 insertions(+) diff --git a/locales/index.d.ts b/locales/index.d.ts index df6efe0d6a..f062dfe6e3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13053,6 +13053,10 @@ export interface Locale extends ILocale { * Timeout in milliseconds for translation API requests. */ "translationTimeoutCaption": string; + /** + * Except for regular expressions, all word mutes are *case-sensitive* and match on any substring, including part of a longer word or name. + */ + "wordMuteWarning": string; } declare const locales: { [lang: string]: Locale; diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index 257ed3edd8..8cc3945df8 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -22,6 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.wordMuteDescription }} + {{ i18n.ts.wordMuteWarning }} + {{ i18n.ts.hardWordMuteDescription }} + + {{ i18n.ts.wordMuteWarning }} +
diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index 93e178c636..b9ec49753a 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -560,6 +560,7 @@ wordMuteTestTest: "Test" wordMuteTestMatch: "Matched words: {words}" wordMuteTestNoResults: "No results yet, enter some text and click \"Test\" to check it." wordMuteTestNoMatch: "Text does not match any patterns." +wordMuteWarning: "Except for regular expressions, all word mutes are *case-sensitive* and match on any substring, including part of a longer word or name." bubbleTimeline: "Bubble timeline" bubbleTimelineDescription: "Choose which instances should be displayed in the bubble." From c47a6937e55ea1faeda4a0f7af3beadbdf02b67f Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 12 May 2025 13:43:41 -0400 Subject: [PATCH 002/333] clarify word mute warning --- locales/index.d.ts | 8 ++++---- sharkey-locales/en-US.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index f062dfe6e3..89426a52f0 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13033,6 +13033,10 @@ export interface Locale extends ILocale { * Text does not match any patterns. */ "wordMuteTestNoMatch": string; + /** + * All word mutes are *case-sensitive* and match on any substring, including part of a longer word or name. You can use regular expressions for more precise control. + */ + "wordMuteWarning": string; /** * Bubble timeline */ @@ -13053,10 +13057,6 @@ export interface Locale extends ILocale { * Timeout in milliseconds for translation API requests. */ "translationTimeoutCaption": string; - /** - * Except for regular expressions, all word mutes are *case-sensitive* and match on any substring, including part of a longer word or name. - */ - "wordMuteWarning": string; } declare const locales: { [lang: string]: Locale; diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index b9ec49753a..e85edf4425 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -560,7 +560,7 @@ wordMuteTestTest: "Test" wordMuteTestMatch: "Matched words: {words}" wordMuteTestNoResults: "No results yet, enter some text and click \"Test\" to check it." wordMuteTestNoMatch: "Text does not match any patterns." -wordMuteWarning: "Except for regular expressions, all word mutes are *case-sensitive* and match on any substring, including part of a longer word or name." +wordMuteWarning: "All word mutes are *case-sensitive* and match on any substring, including part of a longer word or name. You can use regular expressions for more precise control." bubbleTimeline: "Bubble timeline" bubbleTimelineDescription: "Choose which instances should be displayed in the bubble." From a3dc2e85620e6b8e4ae71f5ac6f3cc5ed1ede7e6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Fri, 9 May 2025 11:53:29 -0400 Subject: [PATCH 003/333] add new role conditions for local/remote followers/followees --- locales/index.d.ts | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/locales/index.d.ts b/locales/index.d.ts index 3316a679e0..a83f5f317a 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -7729,6 +7729,38 @@ export interface Locale extends ILocale { * Follows X or more remote accounts */ "remoteFollowingMoreThanOrEq": string; + /** + * Has X or fewer local followers + */ + "localFollowersLessThanOrEq": string; + /** + * Has X or more local followers + */ + "localFollowersMoreThanOrEq": string; + /** + * Follows X or fewer local accounts + */ + "localFollowingLessThanOrEq": string; + /** + * Follows X or more local accounts + */ + "localFollowingMoreThanOrEq": string; + /** + * Has X or fewer remote followers + */ + "remoteFollowersLessThanOrEq": string; + /** + * Has X or more remote followers + */ + "remoteFollowersMoreThanOrEq": string; + /** + * Follows X or fewer remote accounts + */ + "remoteFollowingLessThanOrEq": string; + /** + * Follows X or more remote accounts + */ + "remoteFollowingMoreThanOrEq": string; }; /** * This condition may be incorrect for remote users. From fd339717c7b93cae5aad8e7574e7454cd8f8fc29 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Sat, 10 May 2025 12:12:43 -0400 Subject: [PATCH 004/333] respect animation prefs in MkNumber --- packages/frontend/src/components/MkNumber.vue | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/components/MkNumber.vue b/packages/frontend/src/components/MkNumber.vue index a278205b61..7c2393bf5c 100644 --- a/packages/frontend/src/components/MkNumber.vue +++ b/packages/frontend/src/components/MkNumber.vue @@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue index 47d813ea3f..608722def0 100644 --- a/packages/frontend/src/components/SkOldNoteWindow.vue +++ b/packages/frontend/src/components/SkOldNoteWindow.vue @@ -42,14 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only RN: -
- -
- {{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: - -
-
{{ i18n.ts.translationFailed }}
-
+
@@ -99,6 +92,7 @@ import { deepClone } from '@/utility/clone.js'; import { dateTimeFormat } from '@/utility/intl-const.js'; import { prefer } from '@/preferences'; import { getPluginHandlers } from '@/plugin.js'; +import SkNoteTranslation from '@/components/SkNoteTranslation.vue'; const props = defineProps<{ note: Misskey.entities.Note; @@ -260,13 +254,6 @@ const showTicker = (prefer.s.instanceTicker === 'always') || (prefer.s.instanceT color: var(--MI_THEME-renote); } -.translation { - border: solid 0.5px var(--MI_THEME-divider); - border-radius: var(--MI-radius); - padding: 12px; - margin-top: 8px; -} - .poll { font-size: 80%; } From 793ef45bea61ffa532fc10a4b87717b338b28c7e Mon Sep 17 00:00:00 2001 From: Marie Date: Sun, 18 May 2025 19:18:29 +0000 Subject: [PATCH 024/333] fix having double scrollbars --- packages/frontend/src/style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index 18e67eaa95..7b2638903b 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -235,6 +235,12 @@ rt { contain: strict; overflow: auto; overscroll-behavior: contain; + -ms-overflow-style: none; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } } ._pageScrollable { From e57616830d24aaddfb60577bf0d47dac14955425 Mon Sep 17 00:00:00 2001 From: Ruby Iris Juric Date: Mon, 19 May 2025 14:48:22 +1000 Subject: [PATCH 025/333] fix MK_CONFIG_MEILISEARCH_APIKEY not correctly setting config --- packages/backend/src/config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index a48fa7e646..e02f09bbe1 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -631,7 +631,7 @@ function applyEnvOverrides(config: Source) { ['host', 'port', 'username', 'pass', 'db', 'prefix'], ]); _apply_top(['fulltextSearch', 'provider']); - _apply_top(['meilisearch', ['host', 'port', 'apikey', 'ssl', 'index', 'scope']]); + _apply_top(['meilisearch', ['host', 'port', 'apiKey', 'ssl', 'index', 'scope']]); _apply_top([['sentryForFrontend', 'sentryForBackend'], 'options', ['dsn', 'profileSampleRate', 'serverName', 'includeLocalVariables', 'proxy', 'keepAlive', 'caCerts']]); _apply_top(['sentryForBackend', 'enableNodeProfiling']); _apply_top(['sentryForFrontend', 'vueIntegration', ['attachProps', 'attachErrorHandler']]); From f8c53466ef8e6163433bbbe8734b824849d00c1b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Mon, 19 May 2025 10:57:42 -0400 Subject: [PATCH 026/333] make sure that the "fetch linked note" button actually remembers that the note is fetched --- .../src/server/web/UrlPreviewService.ts | 178 ++++++++++++++---- .../frontend/src/components/MkUrlPreview.vue | 98 ++++++---- 2 files changed, 204 insertions(+), 72 deletions(-) diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts index 0cab657c23..737cf9e536 100644 --- a/packages/backend/src/server/web/UrlPreviewService.ts +++ b/packages/backend/src/server/web/UrlPreviewService.ts @@ -15,7 +15,6 @@ import type Logger from '@/logger.js'; import { query } from '@/misc/prelude/url.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; -import { ApiError } from '@/server/api/error.js'; import { MiMeta } from '@/models/Meta.js'; import { RedisKVCache } from '@/misc/cache.js'; import { UtilityService } from '@/core/UtilityService.js'; @@ -24,6 +23,8 @@ import type { NotesRepository } from '@/models/_.js'; import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js'; import { ApRequestService } from '@/core/activitypub/ApRequestService.js'; import { SystemAccountService } from '@/core/SystemAccountService.js'; +import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js'; +import { AuthenticateService, AuthenticationError } from '@/server/api/AuthenticateService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; export type LocalSummalyResult = SummalyResult & { @@ -33,6 +34,15 @@ export type LocalSummalyResult = SummalyResult & { // Increment this to invalidate cached previews after a major change. const cacheFormatVersion = 2; +type PreviewRoute = { + Querystring: { + url?: string + lang?: string, + fetch?: string, + i?: string, + }, +}; + @Injectable() export class UrlPreviewService { private logger: Logger; @@ -58,6 +68,8 @@ export class UrlPreviewService { private readonly apDbResolverService: ApDbResolverService, private readonly apRequestService: ApRequestService, private readonly systemAccountService: SystemAccountService, + private readonly apNoteService: ApNoteService, + private readonly authenticateService: AuthenticateService, ) { this.logger = this.loggerService.getLogger('url-preview'); this.previewCache = new RedisKVCache(this.redisClient, 'summaly', { @@ -85,9 +97,9 @@ export class UrlPreviewService { @bindThis public async handle( - request: FastifyRequest<{ Querystring: { url?: string; lang?: string; } }>, + request: FastifyRequest, reply: FastifyReply, - ): Promise { + ): Promise { const url = request.query.url; if (typeof url !== 'string' || !URL.canParse(url)) { reply.code(400); @@ -101,38 +113,48 @@ export class UrlPreviewService { } if (!this.meta.urlPreviewEnabled) { - reply.code(403); - return { - error: new ApiError({ + return reply.code(403).send({ + error: { message: 'URL preview is disabled', code: 'URL_PREVIEW_DISABLED', id: '58b36e13-d2f5-0323-b0c6-76aa9dabefb8', - }), - }; + }, + }); } if (this.utilityService.isBlockedHost(this.meta.blockedHosts, new URL(url).host)) { - reply.code(403); - return { - error: new ApiError({ + return reply.code(403).send({ + error: { message: 'URL is blocked', code: 'URL_PREVIEW_BLOCKED', id: '50294652-857b-4b13-9700-8e5c7a8deae8', - }), - }; + }, + }); + } + + const fetch = !!request.query.fetch; + if (fetch && !await this.hasFetchPermissions(request, reply)) { + return; } const cacheKey = `${url}@${lang}@${cacheFormatVersion}`; const cached = await this.previewCache.get(cacheKey); if (cached !== undefined) { - // Cache 1 day (matching redis) - reply.header('Cache-Control', 'public, max-age=86400'); + if (cached.activityPub && !cached.haveNoteLocally) { + cached.haveNoteLocally = await this.hasNoteLocally(cached.activityPub, fetch); - if (cached.activityPub) { - cached.haveNoteLocally = !! await this.apDbResolverService.getNoteFromApId(cached.activityPub); + // Persist the result once we manage to fetch the note + if (cached.haveNoteLocally) { + await this.previewCache.set(cacheKey, cached); + } } - return cached; + // Cache 1 day (matching redis), but not if the note could be fetched later + if (!cached.activityPub || cached.haveNoteLocally) { + reply.header('Cache-Control', 'public, max-age=86400'); + } + + return reply.code(200).send(cached); } try { @@ -144,14 +166,13 @@ export class UrlPreviewService { // Repeat check, since redirects are allowed. if (this.utilityService.isBlockedHost(this.meta.blockedHosts, new URL(summary.url).host)) { - reply.code(403); - return { - error: new ApiError({ + return reply.code(403).send({ + error: { message: 'URL is blocked', code: 'URL_PREVIEW_BLOCKED', id: '50294652-857b-4b13-9700-8e5c7a8deae8', - }), - }; + }, + }); } this.logger.info(`Got preview of ${url} in ${lang}: ${summary.title}`); @@ -166,28 +187,29 @@ export class UrlPreviewService { if (summary.activityPub) { // Avoid duplicate checks in case inferActivityPubLink already set this. - summary.haveNoteLocally ||= !!await this.apDbResolverService.getNoteFromApId(summary.activityPub); + summary.haveNoteLocally ||= await this.hasNoteLocally(summary.activityPub, fetch); } // Await this to avoid hammering redis when a bunch of URLs are fetched at once await this.previewCache.set(cacheKey, summary); - // Cache 1 day (matching redis) - reply.header('Cache-Control', 'public, max-age=86400'); + // Cache 1 day (matching redis), but not if the note could be fetched later + if (!summary.activityPub || summary.haveNoteLocally) { + reply.header('Cache-Control', 'public, max-age=86400'); + } - return summary; + return reply.code(200).send(summary); } catch (err) { this.logger.warn(`Failed to get preview of ${url} for ${lang}: ${err}`); - reply.code(422); reply.header('Cache-Control', 'max-age=3600'); - return { - error: new ApiError({ + return reply.code(422).send({ + error: { message: 'Failed to get preview', code: 'URL_PREVIEW_FAILED', id: '09d01cb5-53b9-4856-82e5-38a50c290a3b', - }), - }; + }, + }); } } @@ -211,6 +233,7 @@ export class UrlPreviewService { } private fetchSummaryFromProxy(url: string, meta: MiMeta, lang?: string): Promise { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const proxy = meta.urlPreviewSummaryProxyUrl!; const queryStr = query({ followRedirects: true, @@ -302,4 +325,95 @@ export class UrlPreviewService { return; } } + + private async hasNoteLocally(uri: string, fetch = false): Promise { + try { + // Local or cached remote notes + if (await this.apDbResolverService.getNoteFromApId(uri)) { + return true; + } + + // Un-cached remote notes + if (fetch && await this.apNoteService.resolveNote(uri)) { + return true; + } + + // Everything else + return false; + } catch { + // Errors, including invalid notes and network errors + return false; + } + } + + // Adapted from ApiCallService + private async hasFetchPermissions(request: FastifyRequest<{ Querystring?: { i?: string | string[] }, Body?: { i?: string | string[] } }>, reply: FastifyReply): Promise { + const body = request.method === 'GET' ? request.query : request.body; + + // https://datatracker.ietf.org/doc/html/rfc6750.html#section-2.1 (case sensitive) + const token = request.headers.authorization?.startsWith('Bearer ') + ? request.headers.authorization.slice(7) + : body?.['i']; + if (token != null && typeof token !== 'string') { + reply.code(400); + return false; + } + + const auth = await this.authenticateService.authenticate(token).catch(async (err) => { + if (err instanceof AuthenticationError) { + return null; + } else { + throw err; + } + }); + + // Authentication + if (!auth) { + reply.code(401).send({ + error: { + message: 'Authentication failed. Please ensure your token is correct.', + code: 'AUTHENTICATION_FAILED', + id: 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14', + }, + }); + return false; + } + const [user, app] = auth; + if (user == null) { + reply.code(401).send({ + error: { + message: 'Credential required.', + code: 'CREDENTIAL_REQUIRED', + id: '1384574d-a912-4b81-8601-c7b1c4085df1', + }, + }); + return false; + } + + // Authorization + if (user.isSuspended || user.isDeleted) { + reply.code(403).send({ + error: { + message: 'Your account has been suspended.', + code: 'YOUR_ACCOUNT_SUSPENDED', + kind: 'permission', + id: 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370', + }, + }); + return false; + } + if (app && !app.permission.includes('read:account')) { + reply.code(403).send({ + error: { + message: 'Your app does not have the necessary permissions to use this endpoint.', + code: 'PERMISSION_DENIED', + kind: 'permission', + id: '1370e5b7-d4eb-4566-bb1d-7748ee6a1838', + }, + }); + return false; + } + + return true; + } } diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 21de04b844..5a5099d8e0 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -71,8 +71,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.expandTweet }} -
- +
+ {{ i18n.ts.fetchLinkedNote }}
@@ -93,6 +93,7 @@ import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue'; import { url as local } from '@@/js/config.js'; import { versatileLang } from '@@/js/intl-const.js'; import * as Misskey from 'misskey-js'; +import { maybeMakeRelative } from '@@/js/url.js'; import type { summaly } from '@misskey-dev/summaly'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; @@ -104,7 +105,7 @@ import { prefer } from '@/preferences.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import { warningExternalWebsite } from '@/utility/warning-external-website.js'; import DynamicNoteSimple from '@/components/DynamicNoteSimple.vue'; -import { maybeMakeRelative } from '@@/js/url.js'; +import { $i } from '@/i'; type SummalyResult = Awaited>; @@ -131,7 +132,7 @@ const maybeRelativeUrl = maybeMakeRelative(props.url, local); const self = maybeRelativeUrl !== props.url; const attr = self ? 'to' : 'href'; const target = self ? null : '_blank'; -const fetching = ref(true); +const fetching = ref | null>(null); const title = ref(null); const description = ref(null); const thumbnail = ref(null); @@ -139,11 +140,12 @@ const icon = ref(null); const sitename = ref(null); const sensitive = ref(false); const activityPub = ref(null); -const player = ref({ +const player = ref({ url: null, width: null, height: null, -} as SummalyResult['player']); + allow: [], +}); const playerEnabled = ref(false); const tweetId = ref(null); const tweetExpanded = ref(props.detail); @@ -173,14 +175,14 @@ async function fetchNote() { return; } theNote.value = response['object']; - fetchingTheNote.value = false; } catch (err) { if (_DEV_) { console.error(`failed to extract note for preview of ${activityPub.value}`, err); } activityPub.value = null; - fetchingTheNote.value = false; theNote.value = null; + } finally { + fetchingTheNote.value = false; } } @@ -198,39 +200,52 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/ requestUrl.hash = ''; -window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`) - .then(res => { - if (!res.ok) { - if (_DEV_) { - console.warn(`[HTTP${res.status}] Failed to fetch url preview`); - } - return null; - } - - return res.json(); - }) - .then((info: SummalyResult & { haveNoteLocally?: boolean } | null) => { - if (!info || info.url == null) { - fetching.value = false; - unknownUrl.value = true; - return; - } - - fetching.value = false; - unknownUrl.value = false; - - title.value = info.title; - description.value = info.description; - thumbnail.value = info.thumbnail; - icon.value = info.icon; - sitename.value = info.sitename; - player.value = info.player; - sensitive.value = info.sensitive ?? false; - activityPub.value = info.activityPub; - if (info.haveNoteLocally) { - fetchNote(); - } +function refresh(withFetch = false) { + const params = new URLSearchParams({ + url: requestUrl.href, + lang: versatileLang, }); + if (withFetch) { + params.set('fetch', 'true'); + } + + const headers = $i ? { Authorization: `Bearer ${$i.token}` } : undefined; + return fetching.value ??= window.fetch(`/url?${params.toString()}`, { headers }) + .then(res => { + if (!res.ok) { + if (_DEV_) { + console.warn(`[HTTP${res.status}] Failed to fetch url preview`); + } + return null; + } + + return res.json(); + }) + .then(async (info: SummalyResult & { haveNoteLocally?: boolean } | null) => { + unknownUrl.value = info != null; + title.value = info?.title ?? null; + description.value = info?.description ?? null; + thumbnail.value = info?.thumbnail ?? null; + icon.value = info?.icon ?? null; + sitename.value = info?.sitename ?? null; + player.value = info?.player ?? { + url: null, + width: null, + height: null, + allow: [], + }; + sensitive.value = info?.sensitive ?? false; + activityPub.value = info?.activityPub ?? null; + + theNote.value = null; + if (info?.haveNoteLocally) { + await fetchNote(); + } + }) + .finally(() => { + fetching.value = null; + }); +} function adjustTweetHeight(message: MessageEvent) { if (message.origin !== 'https://platform.twitter.com') return; @@ -256,6 +271,9 @@ window.addEventListener('message', adjustTweetHeight); onUnmounted(() => { window.removeEventListener('message', adjustTweetHeight); }); + +// Load initial data +refresh(); + + From 529014e7f248bb42b30f52037f1f3f7f4e8e4aa7 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 18:12:38 -0400 Subject: [PATCH 141/333] move moderation note into collapsible section --- packages/frontend/src/pages/instance-info.vue | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index d76b6e355a..caccc0a080 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -78,6 +78,16 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + + + + + + + + @@ -101,10 +111,6 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.mediaSilencedByBase }} {{ i18n.ts.mediaSilenceThisInstance }} Refresh metadata - - - - From afbe3a95c071db4d7aa95be7289b3de47df75ce3 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 18:13:25 -0400 Subject: [PATCH 142/333] translate "Refresh metadata" and use same icon as user controls --- packages/frontend/src/pages/instance-info.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index caccc0a080..b2dba6c1f3 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.rejectReports }} {{ i18n.ts.mediaSilencedByBase }} {{ i18n.ts.mediaSilenceThisInstance }} - Refresh metadata + {{ i18n.ts.updateRemoteUser }} From 23f0748e60ff9bad30b87fb07f8b42038e2c0a0b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 18:27:23 -0400 Subject: [PATCH 143/333] move instance description into a section with divider --- packages/frontend/src/pages/instance-info.vue | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index b2dba6c1f3..cc15149086 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -88,10 +88,12 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - + + + + + + From 9595997b933ada9ec2a6d4cd0b2420eb40b20db0 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:05:33 -0400 Subject: [PATCH 144/333] move well-known resources to a collapsible section --- packages/frontend/src/pages/instance-info.vue | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index cc15149086..da743d776c 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -79,6 +79,19 @@ SPDX-License-Identifier: AGPL-3.0-only + + + +
    + +
  • /.well-known/host-meta
  • +
  • /.well-known/host-meta.json
  • +
  • /.well-known/nodeinfo
  • +
  • /robots.txt
  • +
  • /manifest.json
  • +
+
+ @@ -115,15 +128,6 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.updateRemoteUser }}
- - - - host-meta - host-meta.json - nodeinfo - robots.txt - manifest.json -
@@ -572,4 +576,9 @@ definePage(() => ({ opacity: 1.0; } } + +.linksList { + margin: 0; + padding-left: 1.5em; +} From b082797c69224355788fce9dc8f35c21f8f63c55 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:08:19 -0400 Subject: [PATCH 145/333] improve styling of Description section --- packages/frontend/src/pages/instance-info.vue | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index da743d776c..5a003176f0 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -102,10 +102,8 @@ SPDX-License-Identifier: AGPL-3.0-only - - - - + + {{ instance.description }} From 43495fbd0d724eb60c5ddf338596b1e634e230d8 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:11:13 -0400 Subject: [PATCH 146/333] move all buttons to a group --- packages/frontend/src/pages/admin-user.vue | 1 + packages/frontend/src/pages/instance-info.vue | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index d3c0de3040..63352f6ca3 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -782,6 +782,7 @@ definePage(() => ({ cursor: pointer; } +// Sync with instance-info.vue .buttonStrip { margin: calc(var(--MI-margin) / 2 * -1); diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 5a003176f0..ce60f2f9ad 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -108,11 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
- {{ i18n.ts.deleteAllFiles }} - {{ i18n.ts.severAllFollowRelations }} -
+
{{ i18n.ts._delivery.stop }} {{ i18n.ts.blockedByBase }} {{ i18n.ts.blockThisInstance }} @@ -123,7 +119,12 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.rejectReports }} {{ i18n.ts.mediaSilencedByBase }} {{ i18n.ts.mediaSilenceThisInstance }} - {{ i18n.ts.updateRemoteUser }} + +
+ {{ i18n.ts.updateRemoteUser }} + {{ i18n.ts.deleteAllFiles }} + {{ i18n.ts.severAllFollowRelations }} +
@@ -579,4 +580,13 @@ definePage(() => ({ margin: 0; padding-left: 1.5em; } + +// Sync with admin-user.vue +.buttonStrip { + margin: calc(var(--MI-margin) / 2 * -1); + + >* { + margin: calc(var(--MI-margin) / 2); + } +} From d86920d2f6bee6613aba13eaa8c01f346643a2b6 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:12:13 -0400 Subject: [PATCH 147/333] match gap size to admin-user --- packages/frontend/src/pages/instance-info.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index ce60f2f9ad..33548b50b8 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-
+
From 586c9974a4b0b52068f8fa7a911240ca44de6938 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:13:51 -0400 Subject: [PATCH 148/333] add icons to buttons --- packages/frontend/src/pages/instance-info.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 33548b50b8..78947a771e 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -122,8 +122,8 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts.updateRemoteUser }} - {{ i18n.ts.deleteAllFiles }} - {{ i18n.ts.severAllFollowRelations }} + {{ i18n.ts.deleteAllFiles }} + {{ i18n.ts.severAllFollowRelations }}
From db7e0d9353d92e12c56203f9fac60050ab0b6aff Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:14:42 -0400 Subject: [PATCH 149/333] add missing oneline flag to delivery status --- packages/frontend/src/pages/instance-info.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 78947a771e..5d884e4985 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + From 756096e498bc54e42aa8dbeef9d364afb9fd6893 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 21:16:58 -0400 Subject: [PATCH 150/333] re-order moderation toggles to match admin-user --- packages/frontend/src/pages/instance-info.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 5d884e4985..5d14b6bf2c 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -109,13 +109,13 @@ SPDX-License-Identifier: AGPL-3.0-only
+ {{ i18n.ts.silencedByBase }} + {{ i18n.ts.silenceThisInstance }} {{ i18n.ts._delivery.stop }} {{ i18n.ts.blockedByBase }} {{ i18n.ts.blockThisInstance }} - {{ i18n.ts.silencedByBase }} - {{ i18n.ts.silenceThisInstance }} - {{ i18n.ts.markInstanceAsNSFW }} {{ i18n.ts.rejectQuotesInstance }} + {{ i18n.ts.markInstanceAsNSFW }} {{ i18n.ts.rejectReports }} {{ i18n.ts.mediaSilencedByBase }} {{ i18n.ts.mediaSilenceThisInstance }} From f303cb1171ad1914c3ec4d16a648322006da55d2 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 22:19:51 -0400 Subject: [PATCH 151/333] implement SkBadgeStrip --- .../frontend/src/components/SkBadgeStrip.vue | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 packages/frontend/src/components/SkBadgeStrip.vue diff --git a/packages/frontend/src/components/SkBadgeStrip.vue b/packages/frontend/src/components/SkBadgeStrip.vue new file mode 100644 index 0000000000..6611d35b07 --- /dev/null +++ b/packages/frontend/src/components/SkBadgeStrip.vue @@ -0,0 +1,84 @@ + + + + + + + + + From 979c7628b1d2b21bd9dd9d13ec0110bde883f074 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Thu, 29 May 2025 22:20:21 -0400 Subject: [PATCH 152/333] disable status badge strip in admin-user and instance-info --- locales/index.d.ts | 28 +++++ packages/backend/src/core/UtilityService.ts | 9 ++ .../core/entities/InstanceEntityService.ts | 1 + .../models/json-schema/federation-instance.ts | 4 + .../server/api/endpoints/admin/show-user.ts | 6 + packages/frontend/src/pages/admin-user.vue | 103 ++++++++++++++++-- packages/frontend/src/pages/instance-info.vue | 58 +++++++++- packages/misskey-js/src/autogen/types.ts | 2 + sharkey-locales/en-US.yml | 9 +- 9 files changed, 211 insertions(+), 9 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 2392d51c45..2f3bc664ff 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13145,10 +13145,38 @@ export interface Locale extends ILocale { * Last posted: {at} */ "lastPosted": ParameterizedString<"at">; + /** + * NSFW + */ + "nsfw": string; /** * Raw */ "raw": string; + /** + * CW + */ + "cw": string; + /** + * Media Silenced + */ + "mediaSilenced": string; + /** + * Bubble + */ + "bubble": string; + /** + * Verified + */ + "verified": string; + /** + * Not Verified + */ + "notVerified": string; + /** + * Hibernated + */ + "hibernated": string; } declare const locales: { [lang: string]: Locale; diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index 170afc72dc..ee17906d55 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -67,6 +67,15 @@ export class UtilityService { return silencedHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`)); } + @bindThis + public isBubbledHost(host: string | null): boolean { + if (host == null) return false; + + // TODO remove null conditional after merging lab/persisted-instance-blocks + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + return this.meta.bubbleInstances?.includes(host); + } + @bindThis public concatNoteContentsForKeyWordCheck(content: { cw?: string | null; diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index fcc9bed3bd..08a7765d40 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -62,6 +62,7 @@ export class InstanceEntityService { rejectReports: instance.rejectReports, rejectQuotes: instance.rejectQuotes, moderationNote: iAmModerator ? instance.moderationNote : null, + isBubbled: this.utilityService.isBubbledHost(instance.host), }; } diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 57d4466ffa..fd6eddf594 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -135,5 +135,9 @@ export const packedFederationInstanceSchema = { type: 'string', optional: true, nullable: true, }, + isBubbled: { + type: 'boolean', + optional: false, nullable: false, + }, }, } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts index 1579719246..6a77fc177f 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-user.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts @@ -122,6 +122,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + isAdministrator: { + type: 'boolean', + optional: false, nullable: false, + }, isSystem: { type: 'boolean', optional: false, nullable: false, @@ -257,6 +261,7 @@ export default class extends Endpoint { // eslint- } const isModerator = await this.roleService.isModerator(user); + const isAdministrator = await this.roleService.isAdministrator(user); const isSilenced = user.isSilenced || !(await this.roleService.getUserPolicies(user.id)).canPublicNote; const _me = await this.usersRepository.findOneByOrFail({ id: me.id }); @@ -289,6 +294,7 @@ export default class extends Endpoint { // eslint- mutedInstances: profile.mutedInstances, notificationRecieveConfig: profile.notificationRecieveConfig, isModerator: isModerator, + isAdministrator: isAdministrator, isSystem: isSystemAccount(user), isSilenced: isSilenced, isSuspended: user.isSuspended, diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 63352f6ca3..f7db436bc8 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -20,16 +20,11 @@ SPDX-License-Identifier: AGPL-3.0-only {{ user.id }} - - {{ i18n.ts.notApproved }} - {{ i18n.ts.approved }} - {{ i18n.ts.suspended }} - {{ i18n.ts.silenced }} - {{ i18n.ts.moderator }} -
+ + {{ i18n.ts.isSystemAccount }} @@ -248,6 +243,7 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, defineAsyncComponent, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; import { url } from '@@/js/config.js'; +import type { Badge } from '@/components/SkBadgeStrip.vue'; import MkChart from '@/components/MkChart.vue'; import MkObjectView from '@/components/MkObjectView.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -272,6 +268,7 @@ import MkPagination from '@/components/MkPagination.vue'; import MkInput from '@/components/MkInput.vue'; import MkNumber from '@/components/MkNumber.vue'; import { copyToClipboard } from '@/utility/copy-to-clipboard'; +import SkBadgeStrip from '@/components/SkBadgeStrip.vue'; const props = withDefaults(defineProps<{ userId: string; @@ -304,6 +301,98 @@ const filesPagination = { })), }; +const badges = computed(() => { + const arr: Badge[] = []; + if (info.value && user.value) { + if (info.value.isSuspended) { + arr.push({ + key: 'suspended', + label: i18n.ts.suspended, + style: 'error', + }); + } + + if (info.value.isSilenced) { + arr.push({ + key: 'silenced', + label: i18n.ts.silenced, + style: 'warning', + }); + } + + if (info.value.alwaysMarkNsfw) { + arr.push({ + key: 'nsfw', + label: i18n.ts.nsfw, + style: 'warning', + }); + } + + if (user.value.mandatoryCW) { + arr.push({ + key: 'cw', + label: i18n.ts.cw, + style: 'warning', + }); + } + + if (info.value.isHibernated) { + arr.push({ + key: 'hibernated', + label: i18n.ts.hibernated, + style: 'neutral', + }); + } + + if (info.value.isAdministrator) { + arr.push({ + key: 'admin', + label: i18n.ts.administrator, + style: 'success', + }); + } else if (info.value.isModerator) { + arr.push({ + key: 'mod', + label: i18n.ts.moderator, + style: 'success', + }); + } + + if (user.value.host == null) { + if (info.value.email) { + if (info.value.emailVerified) { + arr.push({ + key: 'verified', + label: i18n.ts.verified, + style: 'success', + }); + } else { + arr.push({ + key: 'not_verified', + label: i18n.ts.notVerified, + style: 'success', + }); + } + } + + if (info.value.approved) { + arr.push({ + key: 'approved', + label: i18n.ts.approved, + style: 'success', + }); + } else { + arr.push({ + key: 'not_approved', + label: i18n.ts.notApproved, + style: 'warning', + }); + } + } + } + return arr; +}); + const announcementsStatus = ref<'active' | 'archived'>('active'); const announcementsPagination = { diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index 5d14b6bf2c..356bb4273b 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -24,6 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + @@ -200,10 +203,11 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue index a31c5eff28..9f9feeed49 100644 --- a/packages/frontend/src/components/page/page.vue +++ b/packages/frontend/src/components/page/page.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only From 5e6c6fccc482283bfd0fc369557ffab6cf8617bd Mon Sep 17 00:00:00 2001 From: Outvi V Date: Sat, 31 May 2025 10:09:46 +0800 Subject: [PATCH 196/333] chore: lint --- .../src/components/page/page.note.vue | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index 98f72f0d9d..f459a437d2 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -25,52 +25,54 @@ const props = defineProps<{ const note = ref(null); -let timeoutId = null; +// eslint-disable-next-line id-denylist +let timeoutId: ReturnType | null = null; async function sleep(ms: number): Promise { - return new Promise((resolve) => { - setTimeout(() => { - resolve() + return new Promise((resolve) => { + window.setTimeout(() => { + resolve(); }, ms); }); } -async function retryOnThrottle(f: ()=>Promise, retryCount: number = 5): Promise { - let lastResult: T = undefined!; - const r = Math.random(); - for(let i=0; i(f: ()=>Promise, retryCount = 5): Promise { + let lastResult: T; + for (let i = 0; i < retryCount; i++) { const [ok, resultOrError] = await f() .then(result => [true, result]) .catch(err => [false, err]); - - lastResult = resultOrError; + + lastResult = resultOrError; if (ok) { break; } // RATE_LIMIT_EXCEEDED - if (resultOrError?.id === "d5826d14-3982-4d2e-8011-b9e9f02499ef") { + if (resultOrError?.id === 'd5826d14-3982-4d2e-8011-b9e9f02499ef') { await sleep(resultOrError?.info?.fullResetMs ?? 1000); continue; } throw resultOrError; } - return lastResult; + return lastResult; } onMounted(() => { if (props.block.note == null) return; - timeoutId = setTimeout(() => { + timeoutId = window.setTimeout(() => { retryOnThrottle(() => misskeyApi('notes/show', { noteId: props.block.note })).then(result => { - note.value = result; + note.value = result; }); }, 500 * props.index); // rate limit is 2 reqs per sec }); onUnmounted(() => { - if (timeoutId !== null) clearTimeout(timeoutId); + if (timeoutId !== null) { + window.clearTimeout(timeoutId); + } }); From dcb62a3fcc2c781bc4571adc4adb5523c2cf962e Mon Sep 17 00:00:00 2001 From: piuvas Date: Fri, 30 May 2025 23:18:37 -0300 Subject: [PATCH 197/333] Update navbar.vue --- packages/frontend/src/ui/_common_/navbar.vue | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 6bf0dfc17c..503bfb1602 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
+
@@ -299,6 +300,18 @@ function menuEdit() { backdrop-filter: var(--MI-blur, blur(8px)); } + .banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + } + .instance { position: relative; display: block; From 44f9be3efb62b5b9a40fb1e6024b9f05d2bc54d4 Mon Sep 17 00:00:00 2001 From: piuvas Date: Sat, 31 May 2025 09:46:43 -0300 Subject: [PATCH 198/333] fix indentation. --- packages/frontend/src/ui/_common_/navbar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 503bfb1602..5cf06cd59d 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -300,7 +300,7 @@ function menuEdit() { backdrop-filter: var(--MI-blur, blur(8px)); } - .banner { + .banner { position: absolute; top: 0; left: 0; From e36ea27517c43352fe3173f54e558ad549388f0e Mon Sep 17 00:00:00 2001 From: Outvi V Date: Sat, 31 May 2025 22:50:21 +0800 Subject: [PATCH 199/333] fix(page.note): throw (not return) on all attempts throttled --- .../frontend/src/components/page/page.note.vue | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index f459a437d2..cc1075d42a 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -37,13 +37,15 @@ async function sleep(ms: number): Promise { } async function retryOnThrottle(f: ()=>Promise, retryCount = 5): Promise { - let lastResult: T; + let lastOk: boolean; + let lastResultOrError: T; for (let i = 0; i < retryCount; i++) { const [ok, resultOrError] = await f() .then(result => [true, result]) .catch(err => [false, err]); - lastResult = resultOrError; + lastOk = ok; + lastResultOrError = resultOrError; if (ok) { break; @@ -55,9 +57,16 @@ async function retryOnThrottle(f: ()=>Promise, retryCount = 5): Promise continue; } + // Throw for non-throttling errors throw resultOrError; } - return lastResult; + + if (lastOk) { + return lastResultOrError; + } else { + // Give up after getting throttled too many times + throw lastResultOrError; + } } onMounted(() => { From 0c49f9daf117ac36fface97aee1772562323ad02 Mon Sep 17 00:00:00 2001 From: Marie Date: Sat, 31 May 2025 14:50:53 +0000 Subject: [PATCH 200/333] Add back in tossface --- packages/frontend/src/pages/settings/preferences.vue | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue index 683fe3ee32..8b89325db7 100644 --- a/packages/frontend/src/pages/settings/preferences.vue +++ b/packages/frontend/src/pages/settings/preferences.vue @@ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
@@ -107,6 +107,7 @@ SPDX-License-Identifier: AGPL-3.0-only +
From 6fcc6ba17c2aa159c2820da2720a417774ee00a9 Mon Sep 17 00:00:00 2001 From: Marie Date: Sat, 31 May 2025 19:35:02 +0000 Subject: [PATCH 201/333] fix indenting --- packages/frontend/src/ui/_common_/navbar.vue | 22 ++++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 5cf06cd59d..f0c62aa8e2 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -300,17 +300,17 @@ function menuEdit() { backdrop-filter: var(--MI-blur, blur(8px)); } - .banner { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-size: cover; - background-position: center center; - -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); - } + .banner { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + -webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%); + } .instance { position: relative; From de7f7984cdd0255e6f84414b0861d547511248a4 Mon Sep 17 00:00:00 2001 From: Outvi V Date: Sun, 1 Jun 2025 06:30:28 +0800 Subject: [PATCH 202/333] chore: move `retryOnThrottled` to frontend-shared --- .../frontend-shared/js/retry-on-throttled.ts | 40 +++++++++++++++++ .../src/components/page/page.note.vue | 44 +------------------ 2 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 packages/frontend-shared/js/retry-on-throttled.ts diff --git a/packages/frontend-shared/js/retry-on-throttled.ts b/packages/frontend-shared/js/retry-on-throttled.ts new file mode 100644 index 0000000000..4234a18b51 --- /dev/null +++ b/packages/frontend-shared/js/retry-on-throttled.ts @@ -0,0 +1,40 @@ +async function sleep(ms: number): Promise { + return new Promise((resolve) => { + window.setTimeout(() => { + resolve(); + }, ms); + }); +} + +export async function retryOnThrottled(f: ()=>Promise, retryCount = 5): Promise { + let lastOk = false; + let lastResultOrError: T; + for (let i = 0; i < retryCount; i++) { + const [ok, resultOrError] = await f() + .then(result => [true, result]) + .catch(err => [false, err]); + + lastOk = ok; + lastResultOrError = resultOrError; + + if (ok) { + break; + } + + // RATE_LIMIT_EXCEEDED + if (resultOrError?.id === 'd5826d14-3982-4d2e-8011-b9e9f02499ef') { + await sleep(resultOrError?.info?.fullResetMs ?? 1000); + continue; + } + + // Throw for non-throttling errors + throw resultOrError; + } + + if (lastOk) { + return lastResultOrError!; + } else { + // Give up after getting throttled too many times + throw lastResultOrError!; + } +} \ No newline at end of file diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index cc1075d42a..3a3c0e4c7b 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + + diff --git a/packages/frontend/src/utility/timeline-date-separate.ts b/packages/frontend/src/utility/timeline-date-separate.ts index e1bc9790b9..ef946b11d6 100644 --- a/packages/frontend/src/utility/timeline-date-separate.ts +++ b/packages/frontend/src/utility/timeline-date-separate.ts @@ -4,7 +4,7 @@ */ import { computed } from 'vue'; -import type { Ref } from 'vue'; +import type { Ref, ComputedRef } from 'vue'; export function getDateText(dateInstance: Date) { const date = dateInstance.getDate(); @@ -25,7 +25,7 @@ export type DateSeparetedTimelineItem = { nextText: string; }; -export function makeDateSeparatedTimelineComputedRef(items: Ref) { +export function makeDateSeparatedTimelineComputedRef(items: Ref | ComputedRef) { return computed[]>(() => { const tl: DateSeparetedTimelineItem[] = []; for (let i = 0; i < items.value.length; i++) { From 645e27fc9e8fcb1c20edd44f4ab5083db319b173 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:05:31 -0400 Subject: [PATCH 301/333] add date separation to report UI --- packages/frontend/src/pages/admin/abuses.vue | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index 4ec4372492..6531a3e49d 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -48,9 +48,9 @@ SPDX-License-Identifier: AGPL-3.0-only --> -
- -
+ + +
@@ -67,6 +67,7 @@ import { definePage } from '@/page.js'; import MkButton from '@/components/MkButton.vue'; import MkInfo from '@/components/MkInfo.vue'; import { store } from '@/store.js'; +import SkDateSeparatedList from '@/components/SkDateSeparatedList.vue'; const reports = useTemplateRef('reports'); From ffa0f06ea032ff6c5fd9daf5baa216cc8431ae94 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:06:20 -0400 Subject: [PATCH 302/333] allow callers to pass in hint objects to admin-user and instance-info --- packages/frontend/src/pages/admin-user.vue | 35 +++++++++++++------ packages/frontend/src/pages/instance-info.vue | 13 ++++--- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 0596b165fd..f629078aac 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -273,8 +273,14 @@ import SkBadgeStrip from '@/components/SkBadgeStrip.vue'; const props = withDefaults(defineProps<{ userId: string; initialTab?: string; + userHint?: Misskey.entities.UserDetailed; + infoHint?: Misskey.entities.AdminShowUserResponse; + ipsHint?: Misskey.entities.AdminGetUserIpsResponse; }>(), { initialTab: 'overview', + userHint: undefined, + infoHint: undefined, + ipsHint: undefined, }); const tab = ref(props.initialTab); @@ -405,16 +411,23 @@ const announcementsPagination = { }; const expandedRoles = ref([]); -function createFetcher() { - return () => Promise.all([misskeyApi('users/show', { - userId: props.userId, - }), misskeyApi('admin/show-user', { - userId: props.userId, - }), iAmAdmin ? misskeyApi('admin/get-user-ips', { - userId: props.userId, - }) : Promise.resolve(null), iAmAdmin ? misskeyApi('ap/get', { - uri: `${url}/users/${props.userId}`, - }).catch(() => null) : null]).then(([_user, _info, _ips, _ap]) => { +function createFetcher(withHint = true) { + return () => Promise.all([ + (withHint && props.userHint) ? props.userHint : misskeyApi('users/show', { + userId: props.userId, + }), + (withHint && props.infoHint) ? props.infoHint : misskeyApi('admin/show-user', { + userId: props.userId, + }), + iAmAdmin + ? (withHint && props.ipsHint) ? props.ipsHint : misskeyApi('admin/get-user-ips', { + userId: props.userId, + }) + : null, + iAmAdmin ? misskeyApi('ap/get', { + uri: `${url}/users/${props.userId}`, + }).catch(() => null) : null], + ).then(([_user, _info, _ips, _ap]) => { user.value = _user; info.value = _info; ips.value = _ips; @@ -432,7 +445,7 @@ function createFetcher() { async function refreshUser() { // Not a typo - createFetcher() returns a function() - await createFetcher()(); + await createFetcher(false)(); } async function onMandatoryCWChanged(value: string) { diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index b60bdf3a72..93f673288c 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -238,9 +238,14 @@ import SkBadgeStrip from '@/components/SkBadgeStrip.vue'; const $style = useCssModule(); -const props = defineProps<{ +const props = withDefaults(defineProps<{ host: string; -}>(); + metaHint?: Misskey.entities.AdminMetaResponse; + instanceHint?: Misskey.entities.FederationInstance; +}>(), { + metaHint: undefined, + instanceHint: undefined, +}); const tab = ref('overview'); @@ -365,8 +370,8 @@ async function saveModerationNote() { async function fetch(): Promise { const [m, i] = await Promise.all([ - iAmAdmin ? misskeyApi('admin/meta') : null, - misskeyApi('federation/show-instance', { + props.metaHint ?? (iAmAdmin ? misskeyApi('admin/meta') : null), + props.instanceHint ?? misskeyApi('federation/show-instance', { host: props.host, }), ]); From 82ec78ef73f69da3a0412150b4bc97215f233364 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:08:07 -0400 Subject: [PATCH 303/333] improvements to MkAbuseReport: * Improved styling for user/instance IDs * Show target instance as a section * Load reports and sections faster * Rename "moderation note" to "staff notes" for clarity * Preview reported notes directly --- locales/index.d.ts | 8 +++ .../frontend/src/components/MkAbuseReport.vue | 66 ++++++++++++++++--- sharkey-locales/en-US.yml | 3 + 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index cee973a0a2..84001cb3c5 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -13157,6 +13157,14 @@ export interface Locale extends ILocale { * Timeout in milliseconds for translation API requests. */ "translationTimeoutCaption": string; + /** + * Staff notes + */ + "staffNotes": string; + /** + * Icon of {name} + */ + "instanceIconAlt": ParameterizedString<"name">; /** * Attribution Domains */ diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index c52fdb898e..b2999d3899 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -30,13 +30,31 @@ SPDX-License-Identifier: AGPL-3.0-only
- + - +
- + +
+
+ + + + + + +
+
@@ -45,22 +63,23 @@ SPDX-License-Identifier: AGPL-3.0-only
+
- + - +
- +
- +
@@ -78,8 +97,9 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/sharkey-locales/en-US.yml b/sharkey-locales/en-US.yml index 4196e3ea09..2856923771 100644 --- a/sharkey-locales/en-US.yml +++ b/sharkey-locales/en-US.yml @@ -598,6 +598,9 @@ roleAutomatic: "automatic" translationTimeoutLabel: "Translation timeout" translationTimeoutCaption: "Timeout in milliseconds for translation API requests." +staffNotes: "Staff notes" +instanceIconAlt: "Icon of {name}" + attributionDomains: "Attribution Domains" attributionDomainsDescription: "A list of domains whose content can be attributed to you on link previews, separated by new-line. Any subdomain will also be valid. The following needs to be on the webpage:" writtenBy: "Written by {user}" From f17e4641888d82e28daaf125280a68fc13277cc9 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:19:30 -0400 Subject: [PATCH 304/333] fix WebhookTestService again --- packages/backend/src/core/WebhookTestService.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index 8c1508df24..7c85a32427 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -77,6 +77,7 @@ function generateDummyUser(override?: Partial): MiUser { mandatoryCW: null, rejectQuotes: false, allowUnsignedFetch: 'staff', + userProfile: null, attributionDomains: [], ...override, }; @@ -363,10 +364,15 @@ export class WebhookTestService { id: 'dummy-abuse-report1', targetUserId: 'dummy-target-user', targetUser: null, + targetUserProfile: null, + targetUserInstance: null, reporterId: 'dummy-reporter-user', reporter: null, + reporterProfile: null, + reporterInstance: null, assigneeId: null, assignee: null, + assigneeProfile: null, resolved: false, forwarded: false, comment: 'This is a dummy report for testing purposes.', From ee3cd216f7cdb3f9e57eca717f547e60c6bd89e5 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:19:40 -0400 Subject: [PATCH 305/333] fix TS errors in ReversiService --- packages/backend/src/core/ReversiService.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts index e31d9e5b1a..b57ab6d9cb 100644 --- a/packages/backend/src/core/ReversiService.ts +++ b/packages/backend/src/core/ReversiService.ts @@ -588,6 +588,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null, movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null, instance: null, + userProfile: null, } : null, user2: parsed.user2 != null ? { ...parsed.user2, @@ -599,6 +600,7 @@ export class ReversiService implements OnApplicationShutdown, OnModuleInit { lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null, movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null, instance: null, + userProfile: null, } : null, }; } else { From 838ac6daa9a90983cc19670cf3b5b786bddfe01b Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:19:46 -0400 Subject: [PATCH 306/333] fix unit tests --- packages/backend/test/unit/AbuseReportNotificationService.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts index 6d555326fb..55ec755dc7 100644 --- a/packages/backend/test/unit/AbuseReportNotificationService.ts +++ b/packages/backend/test/unit/AbuseReportNotificationService.ts @@ -367,10 +367,15 @@ describe('AbuseReportNotificationService', () => { id: idService.gen(), targetUserId: alice.id, targetUser: alice, + targetUserProfile: null, + targetUserInstance: null, reporterId: bob.id, reporter: bob, + reporterProfile: null, + reporterInstance: null, assigneeId: null, assignee: null, + assigneeProfile: null, resolved: false, forwarded: false, comment: 'test', From 067c5d450006052556f37adb39172dc976c26b10 Mon Sep 17 00:00:00 2001 From: Hazelnoot Date: Wed, 28 May 2025 02:22:44 -0400 Subject: [PATCH 307/333] pass index through SkDateSeparatedList.vue --- packages/frontend/src/components/SkDateSeparatedList.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/frontend/src/components/SkDateSeparatedList.vue b/packages/frontend/src/components/SkDateSeparatedList.vue index 8c62347c0c..239d0c1939 100644 --- a/packages/frontend/src/components/SkDateSeparatedList.vue +++ b/packages/frontend/src/components/SkDateSeparatedList.vue @@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only