···7171 run: |
7272 export TITLE="Pending osu-web $PRODUCTION_TRACK Deployment: $GITHUB_REF_NAME"
7373 export URL="https://github.com/ppy/osu-web/actions/runs/$GITHUB_RUN_ID"
7474- export DESCRIPTION="Docker image was built for tag $GITHUB_REF_NAME and awaiting approval for production deployment:
7474+ export DESCRIPTION="Docker image was built for tag $GITHUB_REF_NAME and awaiting approval for $PRODUCTION_TRACK deployment:
7575 [View Workflow Run]($URL)"
7676 export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"
7777
···106106 throw new Exception("Indexable and indexed score counts still don't match. Queue runner is probably either having problem, not running, or too slow");
107107 }
108108109109- public function queueForIndex(?array $schemas = null, array $ids): void
109109+ public function queueForIndex(?array $schemas, array $ids): void
110110 {
111111 $count = count($ids);
112112
+6-1
app/Libraries/Session/Store.php
···1515{
1616 const SESSION_ID_LENGTH = 40;
17171818- public static function destroy($userId)
1818+ public static function destroy($userId, ?string $excludedSessionId = null)
1919 {
2020 if (!static::isUsingRedis()) {
2121 return;
2222 }
23232424 $keys = static::keys($userId);
2525+ if ($excludedSessionId !== null) {
2626+ $keys = array_filter($keys, fn ($key) => $key !== $excludedSessionId);
2727+ }
2528 UserSessionEvent::newLogout($userId, $keys)->broadcast();
2629 Redis::del(array_merge([static::listKey($userId)], $keys));
2730 }
···143146144147 $userId = Auth::user()->user_id;
145148149149+ // prevent the following save from clearing up current flash data
150150+ $this->reflash();
146151 // flush the current session data to redis early, otherwise we will get stale metadata for the current session
147152 $this->save();
148153
+46
app/Libraries/User/CountryChange.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+declare(strict_types=1);
77+88+namespace App\Libraries\User;
99+1010+use App\Exceptions\InvariantException;
1111+use App\Models\Beatmap;
1212+use App\Models\Country;
1313+use App\Models\User;
1414+use App\Models\UserAccountHistory;
1515+1616+class CountryChange
1717+{
1818+ public static function handle(User $user, string $newCountry, string $reason): void
1919+ {
2020+ // Assert valid country acronym
2121+ $country = Country::find($newCountry);
2222+ if ($country === null) {
2323+ throw new InvariantException('invalid country specified');
2424+ }
2525+2626+ if ($user->country_acronym === $newCountry) {
2727+ return;
2828+ }
2929+3030+ $user->getConnection()->transaction(function () use ($newCountry, $reason, $user) {
3131+ $oldCountry = $user->country_acronym;
3232+ $user->update(['country_acronym' => $newCountry]);
3333+ foreach (Beatmap::MODES as $ruleset => $_rulesetId) {
3434+ $user->statistics($ruleset, true)->update(['country_acronym' => $newCountry]);
3535+ $user->scoresBest($ruleset, true)->update(['country_acronym' => $newCountry]);
3636+ }
3737+ UserAccountHistory::addNote($user, "Changing country from {$oldCountry} to {$newCountry} ({$reason})");
3838+ });
3939+4040+ \Artisan::queue('es:index-scores:queue', [
4141+ '--all' => true,
4242+ '--no-interaction' => true,
4343+ '--user' => $user->getKey(),
4444+ ]);
4545+ }
4646+}
+106
app/Libraries/User/CountryChangeTarget.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+declare(strict_types=1);
77+88+namespace App\Libraries\User;
99+1010+use App\Models\Tournament;
1111+use App\Models\TournamentRegistration;
1212+use App\Models\User;
1313+use App\Models\UserCountryHistory;
1414+use Carbon\CarbonImmutable;
1515+1616+class CountryChangeTarget
1717+{
1818+ const MIN_DAYS_MONTH = 15;
1919+2020+ public static function currentMonth(): CarbonImmutable
2121+ {
2222+ $now = CarbonImmutable::now();
2323+ $subMonths = $now->day > static::MIN_DAYS_MONTH ? 0 : 1;
2424+2525+ return $now->startOfMonth()->subMonths($subMonths);
2626+ }
2727+2828+ public static function get(User $user): ?string
2929+ {
3030+ $minMonths = static::minMonths();
3131+ $now = CarbonImmutable::now();
3232+ $until = static::currentMonth();
3333+ $since = $until->subMonths($minMonths - 1);
3434+3535+ if (static::isUserInTournament($user)) {
3636+ return null;
3737+ }
3838+3939+ $history = $user
4040+ ->userCountryHistory()
4141+ ->whereBetween('year_month', [
4242+ UserCountryHistory::formatDate($since),
4343+ UserCountryHistory::formatDate($until),
4444+ ])->whereHas('country')
4545+ ->get();
4646+4747+ // First group countries by year_month
4848+ $byMonth = [];
4949+ foreach ($history as $entry) {
5050+ $byMonth[$entry->year_month] ??= [];
5151+ $byMonth[$entry->year_month][] = $entry->country_acronym;
5252+ }
5353+5454+ // For each year_month, summarise each countries
5555+ $byCountry = [];
5656+ foreach ($byMonth as $countries) {
5757+ $mixed = count($countries) > 1;
5858+ foreach ($countries as $country) {
5959+ $byCountry[$country] ??= [
6060+ 'total' => 0,
6161+ 'mixed' => 0,
6262+ ];
6363+ $byCountry[$country]['total']++;
6464+ if ($mixed) {
6565+ $byCountry[$country]['mixed']++;
6666+ }
6767+ }
6868+ }
6969+7070+ // Finally find the first country which fulfills the requirement
7171+ foreach ($byCountry as $country => $data) {
7272+ if ($data['total'] === $minMonths && $data['mixed'] <= static::maxMixedMonths()) {
7373+ if ($user->country_acronym === $country) {
7474+ return null;
7575+ } else {
7676+ return $country;
7777+ }
7878+ }
7979+ }
8080+8181+ return null;
8282+ }
8383+8484+ public static function maxMixedMonths(): int
8585+ {
8686+ return config('osu.user.country_change.max_mixed_months');
8787+ }
8888+8989+ public static function minMonths(): int
9090+ {
9191+ return config('osu.user.country_change.min_months');
9292+ }
9393+9494+ private static function isUserInTournament(User $user): bool
9595+ {
9696+ return TournamentRegistration
9797+ ::where('user_id', $user->getKey())
9898+ ->whereIn(
9999+ 'tournament_id',
100100+ Tournament
101101+ ::where('end_date', '>', CarbonImmutable::now())
102102+ ->orWhereNull('end_date')
103103+ ->select('tournament_id'),
104104+ )->exists();
105105+ }
106106+}
···27272828 const MAX_DIMENSIONS = [2400, 580];
29293030+ // To be passed to transformer for generating url for initial cover upload
3131+ public ?int $newForumId = null;
3232+3033 protected $table = 'forum_topic_covers';
31343235 private $_owner = [false, null];
···114117 } catch (Exception $_e) {
115118 // do nothing
116119 }
120120+ }
121121+122122+ public function getForumId(): ?int
123123+ {
124124+ return $this->topic?->forum_id ?? $this->newForumId;
117125 }
118126}
+24
app/Models/Model.php
···174174 $query->whereNotNull($column)->where($column, '<>', '');
175175 }
176176177177+ /**
178178+ * Just like decrement but only works on saved instance instead of falling back to entire model
179179+ */
180180+ public function decrementInstance()
181181+ {
182182+ if (!$this->exists) {
183183+ return false;
184184+ }
185185+186186+ return $this->decrement(...func_get_args());
187187+ }
188188+177189 public function delete()
178190 {
179191 return $this->runAfterCommitWrapper(function () {
180192 return parent::delete();
181193 });
194194+ }
195195+196196+ /**
197197+ * Just like increment but only works on saved instance instead of falling back to entire model
198198+ */
199199+ public function incrementInstance()
200200+ {
201201+ if (!$this->exists) {
202202+ return false;
203203+ }
204204+205205+ return $this->increment(...func_get_args());
182206 }
183207184208 public function save(array $options = [])
+1-6
app/Models/Multiplayer/Room.php
···598598 return $this->getConnection()->transaction(function () use ($user, $playlistItem) {
599599 $agg = UserScoreAggregate::new($user, $this);
600600 if ($agg->isNew) {
601601- // sanity; if the object isn't saved, laravel will increment the entire table.
602602- if (!$this->exists) {
603603- $this->save();
604604- }
605605-606606- $this->increment('participant_count');
601601+ $this->incrementInstance('participant_count');
607602 }
608603609604 $agg->updateUserAttempts();
+10-11
app/Models/Multiplayer/UserScoreAggregate.php
···5353 ]);
5454 }
55555656- public static function lookupOrDefault(User $user, Room $room): self
5656+ public static function lookupOrDefault(User $user, Room $room): static
5757 {
5858- $obj = static::firstOrNew([
5858+ return static::firstOrNew([
5959+ 'room_id' => $room->getKey(),
5960 'user_id' => $user->getKey(),
6060- 'room_id' => $room->getKey(),
6161+ ], [
6262+ 'accuracy' => 0,
6363+ 'attempts' => 0,
6464+ 'completed' => 0,
6565+ 'pp' => 0,
6666+ 'total_score' => 0,
6167 ]);
6262-6363- foreach (['total_score', 'accuracy', 'pp', 'attempts', 'completed'] as $key) {
6464- // init if required
6565- $obj->$key = $obj->$key ?? 0;
6666- }
6767-6868- return $obj;
6968 }
70697170 public static function updatePlaylistItemUserHighScore(PlaylistItemUserHighScore $highScore, Score $score)
···175174176175 public function updateUserAttempts()
177176 {
178178- $this->increment('attempts');
177177+ $this->incrementInstance('attempts');
179178 }
180179181180 public function user()
+2
app/Models/NewsPost.php
···3333 // in minutes
3434 const CACHE_DURATION = 86400;
3535 const VERSION = 3;
3636+ // should be higher than landing limit
3637 const DASHBOARD_LIMIT = 8;
3838+ // also for number of large posts in user dashboard
3739 const LANDING_LIMIT = 4;
38403941 const SORTS = [
···405405406406 public function includeUnreadPmCount(User $user)
407407 {
408408- return $this->primitive($user->notificationCount());
408408+ // legacy pm has been turned off
409409+ return $this->primitive(0);
409410 }
410411411412 public function includeUserAchievements(User $user)
···11# Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22# See the LICENCE file in the repository root for full licence text.
3344-import { pageChange } from 'utils/page-change'
54import { currentUrl, navigate } from 'utils/turbolinks'
6576export default class ForumTopicReply
···113112 else
114113 @forum.setTotalPosts(@forum.totalPosts() + 1)
115114 @forum.endPost().insertAdjacentHTML 'afterend', data
116116- pageChange()
117115118116 @forum.endPost().scrollIntoView()
119117···169167 target.insertBefore(box, target.firstChild)
170168171169 $input.focus() if inputFocused
172172- pageChange() # sync reply box height
···3344import { blackoutHide, blackoutShow } from 'utils/blackout'
55import { fadeToggle } from 'utils/fade'
66-import { pageChangeImmediate } from 'utils/page-change'
7687export default class Nav2
98 constructor: (@clickMenu) ->
···3837 @centerPopup currentPopup, link
39384039 $(window).on 'resize.nav2-center-popup', doCenter
4141- pageChangeImmediate() if @loginBoxVisible()
4240 doCenter()
4341 currentPopup.querySelector('.js-nav2--autofocus')?.focus()
4442
-2
resources/js/core-legacy/post-preview.coffee
···22# See the LICENCE file in the repository root for full licence text.
3344import { route } from 'laroute'
55-import { pageChange } from 'utils/page-change'
6576export default class PostPreview
87 constructor: ->
···3837 $preview.html data
3938 $preview.attr 'data-raw', body
4039 $previewBox.removeClass 'hidden'
4141- pageChange()
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22+// See the LICENCE file in the repository root for full licence text.
33+44+import Main from 'beatmap-discussions-history/main';
55+import BeatmapsetDiscussionsBundleJson from 'interfaces/beatmapset-discussions-bundle-json';
66+import core from 'osu-core-singleton';
77+import React from 'react';
88+import { parseJson } from 'utils/json';
99+1010+core.reactTurbolinks.register('beatmap-discussions-history', () => (
1111+ <Main bundle={parseJson<BeatmapsetDiscussionsBundleJson>('json-index')} />
1212+));
···11+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22+// See the LICENCE file in the repository root for full licence text.
33+44+import BeatmapExtendedJson from './beatmap-extended-json';
55+import { BeatmapsetDiscussionJsonForBundle } from './beatmapset-discussion-json';
66+import BeatmapsetExtendedJson from './beatmapset-extended-json';
77+import UserJson from './user-json';
88+99+export default interface BeatmapsetDiscussionsBundleJson {
1010+ beatmaps: BeatmapExtendedJson[];
1111+ beatmapsets: BeatmapsetExtendedJson[];
1212+ discussions: BeatmapsetDiscussionJsonForBundle[];
1313+ included_discussions: BeatmapsetDiscussionJsonForBundle[];
1414+ users: UserJson[];
1515+}
+2-5
resources/js/modding-profile/main.tsx
···2020import { action, computed, makeObservable, observable } from 'mobx';
2121import { observer } from 'mobx-react';
2222import Kudosu from 'modding-profile/kudosu';
2323-import { deletedUser } from 'models/user';
2323+import { deletedUserJson } from 'models/user';
2424import core from 'osu-core-singleton';
2525import Badges from 'profile-page/badges';
2626import Cover from 'profile-page/cover';
···2828import headerLinks from 'profile-page/header-links';
2929import * as React from 'react';
3030import { bottomPage } from 'utils/html';
3131-import { pageChange } from 'utils/page-change';
3231import { nextVal } from 'utils/seq';
3332import { switchNever } from 'utils/switch-never';
3433import { currentUrl } from 'utils/turbolinks';
···122121 private get users() {
123122 const values = keyBy(this.props.users, 'id');
124123 // eslint-disable-next-line id-blacklist
125125- values.null = values.undefined = deletedUser.toJson();
124124+ values.null = values.undefined = deletedUserJson;
126125127126 return values;
128127 }
···136135 componentDidMount() {
137136 // pageScan does not need to run at 144 fps...
138137 $(window).on(`scroll.${this.eventId}`, throttle(() => this.pageScan(), 20));
139139-140140- pageChange();
141138142139 const page = validPage(currentUrl().hash.slice(1)) ?? 'main';
143140
+2-2
resources/js/modding-profile/posts.tsx
···66import { BeatmapsetDiscussionMessagePostJson } from 'interfaces/beatmapset-discussion-post-json';
77import UserJson from 'interfaces/user-json';
88import { route } from 'laroute';
99-import { deletedUser } from 'models/user';
99+import { deletedUserJson } from 'models/user';
1010import * as React from 'react';
1111import { makeUrl } from 'utils/beatmapset-discussion-helper';
1212import { classWithModifiers } from 'utils/css';
···8484 readonly
8585 resolvedSystemPostId={-1} // TODO: Can probably move to context after refactoring state?
8686 type='reply'
8787- user={this.props.users[post.user_id] ?? deletedUser.toJson()}
8787+ user={this.props.users[post.user_id] ?? deletedUserJson}
8888 users={this.props.users}
8989 />
9090 </div>
-23
resources/js/models/legacy-pm-notification.ts
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22-// See the LICENCE file in the repository root for full licence text.
33-44-import Notification from 'models/notification';
55-import { newEmptyNotificationDetails } from 'models/notification-details';
66-import core from 'osu-core-singleton';
77-88-export default class LegacyPmNotification extends Notification {
99- details = newEmptyNotificationDetails();
1010- declare isRead: false;
1111- name = 'legacy_pm';
1212- objectId = -1;
1313-1414- get count() {
1515- return core.currentUser?.unread_pm_count ?? 0;
1616- }
1717-1818- constructor() {
1919- super(-1, null);
2020-2121- this.isRead = false;
2222- }
2323-}
···5566// FIXME: this mapping is not used anymore as it has been replaced by "filters"/objectType/NotificationType
77export function displayType(item: Notification) {
88- if (item.name === 'legacy_pm') {
99- return 'legacy_pm';
1010- }
1111-128 if (item.objectType == null || item.objectId == null) {
139 return;
1410 }
-8
resources/js/notification-widget/main.tsx
···77import { observer } from 'mobx-react';
88import { Name, typeNames } from 'models/notification-type';
99import { NotificationContext } from 'notifications-context';
1010-import LegacyPm from 'notifications/legacy-pm';
1110import NotificationController from 'notifications/notification-controller';
1211import NotificationReadButton from 'notifications/notification-read-button';
1312import core from 'osu-core-singleton';
···7877 {this.renderMarkAsReadButton()}
7978 </div>
8079 </div>
8181- {this.renderLegacyPm()}
8280 <div className='notification-stacks'>
8381 {this.renderStacks()}
8482 {this.renderShowMore()}
···142140 {trans(`notifications.see_${this.props.only ?? 'all'}`)}
143141 </a>
144142 );
145145- }
146146-147147- private renderLegacyPm() {
148148- if (this.controller.currentFilter != null) return;
149149-150150- return <LegacyPm />;
151143 }
152144153145 private renderMarkAsReadButton() {
-9
resources/js/notifications-index/main.tsx
···1010import { Name as NotificationTypeName, typeNames } from 'models/notification-type';
1111import Stack from 'notification-widget/stack';
1212import { NotificationContext, NotificationContextData } from 'notifications-context';
1313-import LegacyPm from 'notifications/legacy-pm';
1413import NotificationController from 'notifications/notification-controller';
1514import NotificationDeleteButton from 'notifications/notification-delete-button';
1615import NotificationReadButton from 'notifications/notification-read-button';
···6564 </div>
6665 }
67666868- {this.renderLegacyPm()}
6969-7067 {this.type.isEmpty
7168 ? this.type.isLoading
7269 ? null
···7976 </div>
8077 </div>
8178 );
8282- }
8383-8484- renderLegacyPm() {
8585- if (this.controller.currentFilter != null) return;
8686-8787- return <LegacyPm />;
8879 }
89809081 renderShowMore() {
-30
resources/js/notifications/legacy-pm.tsx
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22-// See the LICENCE file in the repository root for full licence text.
33-44-import { observer } from 'mobx-react';
55-import LegacyPmNotification from 'models/legacy-pm-notification';
66-import { nameToIcons } from 'notification-maps/icons';
77-import Item from 'notification-widget/item';
88-import * as React from 'react';
99-import { transChoice } from 'utils/lang';
1010-1111-@observer
1212-export default class LegacyPm extends React.Component {
1313- render() {
1414- const item = new LegacyPmNotification();
1515-1616- if (item.count === 0) return null;
1717-1818- return (
1919- <Item
2020- icons={nameToIcons.legacy_pm}
2121- item={item}
2222- message={transChoice('notifications.item.legacy_pm.legacy_pm.legacy_pm', item.count)}
2323- modifiers={['one']}
2424- url='/forum/ucp.php?i=pm&folder=inbox'
2525- withCategory
2626- withCoverImage
2727- />
2828- );
2929- }
3030-}
+14-9
resources/js/profile-page/badges.tsx
···11// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22// See the LICENCE file in the repository root for full licence text.
3344+import Img2x from 'components/img2x';
45import UserBadgeJson from 'interfaces/user-badge-json';
56import * as React from 'react';
66-import { urlPresence } from 'utils/css';
77import { present } from 'utils/string';
8899interface Props {
···1919 return (
2020 <div className='profile-badges'>
2121 {this.props.badges.map((badge) => {
2222- const props = {
2323- className: 'profile-badges__badge',
2424- key: badge.image_url,
2525- style: { backgroundImage: urlPresence(badge.image_url) },
2626- title: badge.description,
2727- };
2222+ const img = (
2323+ <Img2x
2424+ className='profile-badges__badge'
2525+ src={badge.image_url}
2626+ title={badge.description}
2727+ />
2828+ );
28292930 return present(badge.url) ? (
3030- <a href={badge.url} {...props} />
3131+ <a key={badge.image_url} href={badge.url}>
3232+ {img}
3333+ </a>
3134 ) : (
3232- <span {...props} />
3535+ <span key={badge.image_url}>
3636+ {img}
3737+ </span>
3338 );
3439 })}
3540 </div>
···1313import { classWithModifiers } from 'utils/css';
1414import { bottomPage } from 'utils/html';
1515import { hideLoadingOverlay, showLoadingOverlay } from 'utils/loading-overlay';
1616-import { pageChange } from 'utils/page-change';
1716import { nextVal } from 'utils/seq';
1817import { present } from 'utils/string';
1918import { switchNever } from 'utils/switch-never';
···152151 update: this.updateOrder,
153152 });
154153 }
155155-156156- pageChange();
157154158155 // preserve scroll if existing saved state but force position to reset
159156 // on refresh to avoid browser setting scroll position at the bottom on reload.
+2-4
resources/js/profile-page/medals.tsx
···2828 // group by .grouping and then further group by .ordering
2929 const ret = new Map<string, Map<number, UserAchievementData[]>>();
30303131- for (const achievement of Object.values(this.props.controller.achievements)) {
3232- if (achievement == null) continue;
3333-3131+ for (const achievement of [...this.props.controller.achievements.values()]) {
3432 const userAchievement = this.userAchievements[achievement.id.toString()];
3533 const visible = this.currentModeFilter(achievement) && (isCurrentUser || userAchievement != null);
3634···5856 const ret: Required<UserAchievementData>[] = [];
59576058 for (const ua of this.props.controller.state.user.user_achievements) {
6161- const achievement = this.props.controller.achievements[ua.achievement_id];
5959+ const achievement = this.props.controller.achievements.get(ua.achievement_id);
62606361 if (achievement != null && this.currentModeFilter(achievement)) {
6462 ret.push({
···11# Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22# See the LICENCE file in the repository root for full licence text.
3344-import { pageChange } from 'utils/page-change'
55-64$(document).on 'click', '.js-spoilerbox__link', (e) ->
75 e.preventDefault()
8697 $link = $(e.target).closest('.js-spoilerbox')
108119 $link.toggleClass 'js-spoilerbox--open'
1212- pageChange()
1010+ $.publish 'sync-height:force'
+2-2
resources/js/stores/channel-store.ts
···243243 const message = event.message;
244244 const channel = this.get(message.channelId);
245245 if (channel == null) {
246246- console.debug('channel missing');
246246+ console.error('channel missing');
247247 return;
248248 }
249249···254254 const userId = channel.pmTarget;
255255256256 if (userId == null) {
257257- console.debug('sendMessage:: userId not found?? this shouldn\'t happen');
257257+ console.error('sendMessage:: userId not found?? this shouldn\'t happen');
258258 return;
259259 }
260260
-2
resources/js/stores/notification-stack-store.ts
···77import DispatchListener from 'dispatch-listener';
88import NotificationJson, { NotificationBundleJson, NotificationStackJson, NotificationTypeJson } from 'interfaces/notification-json';
99import { action, computed, makeObservable, observable } from 'mobx';
1010-import LegacyPmNotification from 'models/legacy-pm-notification';
1110import Notification from 'models/notification';
1211import NotificationStack, { idFromJson } from 'models/notification-stack';
1312import NotificationType, { Name as NotificationTypeName } from 'models/notification-type';
···19182019@dispatchListener
2120export default class NotificationStackStore implements DispatchListener {
2222- @observable readonly legacyPm = new LegacyPmNotification();
2321 @observable readonly types = new Map<string | null, NotificationType>();
2422 private deletedStacks = new Set<string>();
2523 private readonly resolver = new NotificationResolver();
-10
resources/js/utils/page-change.ts
···11-// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
22-// See the LICENCE file in the repository root for full licence text.
33-44-export function pageChange() {
55- return window.setTimeout(pageChangeImmediate, 0);
66-}
77-88-export function pageChangeImmediate() {
99- $.publish('osu:page:change');
1010-}
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Pouze vlastník skóre může připnout skóre.',
175176 'too_many' => 'Připnuto příliš mnoho skóre.',
176177 ],
+3-3
resources/lang/cs/beatmaps.php
···106106 'unsaved' => 'Neuloženo',
107107 'timestamp' => [
108108 'all-diff' => 'Příspěvky na "Všechny obtížnosti" nemohou být časovány.',
109109- 'diff' => 'Pokud tento :type začíná časovou značkou, zobrazí se v Časové ose.',
109109+ 'diff' => 'Pokud tento příspěvek začíná časovou značkou, zobrazí se v Časové ose.',
110110 ],
111111 ],
112112 'insert-block' => [
···176176177177 'nominations' => [
178178 'already_nominated' => 'Už jsi tuto beatmapu nominoval.',
179179- 'cannot_nominate' => '',
179179+ 'cannot_nominate' => 'Nemůžeš nominovat tento herní mód beatmapy.',
180180 'delete' => 'Vymazat',
181181 'delete_own_confirm' => 'Jste si jistý? Tahle beatmapa bude smazána a pošleme vás zpátky na váš profil.',
182182 'delete_other_confirm' => 'Jste si jistý? Tahle beatmapa bude smazána a pošleme vás zpátky na profil uživatele.',
···251251 ],
252252 'supporter_filter_quote' => [
253253 '_' => 'Filtrování podle :filters vyžaduje aktivní :link',
254254- 'link_text' => 'stítek podporovatele',
254254+ 'link_text' => 'osu!supporter tag',
255255 ],
256256 ],
257257 ],
+4-4
resources/lang/cs/beatmapsets.php
···67676868 'details' => [
6969 'by_artist' => 'od :artist',
7070- 'favourite' => 'Přidat do mých oblíbených',
7171- 'favourite_login' => 'Pro přidání beatmapy do oblíbených se přihlas',
7272- 'logged-out' => 'Pro stahování beatmap musíš být přihlášen!',
7070+ 'favourite' => 'přidat do mých oblíbených',
7171+ 'favourite_login' => 'pro přidání beatmapy do oblíbených se přihlas',
7272+ 'logged-out' => 'před stahováním beatmap se musíš nejprve přihlásit!',
7373 'mapped_by' => 'beatmapu vytvořil :mapper',
7474 'mapped_by_guest' => 'obtížnost hosta od :mapper',
7575- 'unfavourite' => 'Odebrat z mých oblíbených',
7575+ 'unfavourite' => 'odebrat z mých oblíbených',
7676 'updated_timeago' => 'naposledy aktualizováno :timeago',
77777878 'download' => [
+1-1
resources/lang/cs/comments.php
···1111 'edited' => 'upraveno před :timeago uživatelem :user',
1212 'pinned' => 'připnuto',
1313 'empty' => 'Zatím zde nejsou žádné komentáře.',
1414- 'empty_other' => '',
1414+ 'empty_other' => 'Zatím zde nejsou žádné další komentáře.',
1515 'load_replies' => 'načíst odpovědi',
1616 'replies_count' => ':count_delimited odpověď|:count_delimited odpovědi|:count_delimited odpovědí',
1717 'title' => 'Komentáře',
+1-1
resources/lang/cs/events.php
···1515 'rank' => '<strong><em>:user</em></strong> získal pozici #:rank na mapě <em>:beatmap</em> (:mode)',
1616 'rank_lost' => '<strong><em>:user</em></strong> ztratil první místo na mapě <em>:beatmap</em> (:mode)',
1717 'user_support_again' => '<strong>:user</strong> se opět rozhodl podpořit osu! - díky za tvou štědrost!',
1818- 'user_support_first' => '<strong>:user</strong> se stal osu! supporterem - díky za tvou štědrost!',
1818+ 'user_support_first' => '<strong>:user</strong> podpořil osu! - díky za tvou štědrost!',
1919 'user_support_gift' => '<strong>:user</strong> obdržel dar osu! supporteru!',
2020 'username_change' => '<strong>:previousUsername</strong> se přejmenoval na <strong><em>:user</strong></em>!',
2121
+1-1
resources/lang/cs/forum.php
···102102 'preview' => 'Náhled',
103103 // TL note: this is used in the topic reply preview, when
104104 // the user goes back from previewing to editing the reply
105105- 'preview_hide' => 'Psát',
105105+ 'preview_hide' => 'Editovat',
106106 'submit' => 'Odeslat',
107107108108 'necropost' => [
···4949 ],
50505151 'card' => [
5252- 'gift_supporter' => '',
5252+ 'gift_supporter' => 'Darovat supporter tag',
5353 'loading' => 'Načítání...',
5454 'send_message' => 'Odeslat zprávu',
5555 ],
···159159 ],
160160 'restricted_banner' => [
161161 'title' => 'Tvůj účet byl omezen!',
162162- 'message' => 'Zatímco jsi omezený, nebudeš moci komunikovat s ostatními hráči a tvá skóre budou viditelná pouze pro tebe. Toto je obvykle výsledkem automatického procesu, který by se měl sám vyřešit nejpozději do 24 hodin. Pokud si přeješ odvolat své omezení, prosím <a href="mailto:accounts@ppy.sh">kontaktuj podporu</a>.',
162162+ 'message' => 'Zatímco jsi omezený, nebudeš moci komunikovat s ostatními hráči a tvá skóre budou viditelná pouze pro tebe. Toto je obvykle výsledkem automatického procesu a většinou jsou tato omezení zrušena do 24 hodin. :link',
163163 'message_link' => 'Na této stránce se dozvíte více.',
164164 ],
165165 'show' => [
···417417 'title' => 'Uživatel nebyl nalezen! ;_;',
418418 ],
419419 'page' => [
420420- 'button' => 'Upravit stránku profilu',
420420+ 'button' => 'upravit stránku profilu',
421421 'description' => '<strong>já!</strong> je osobní přizpůsobitelná plocha na vašem profilu.',
422422 'edit_big' => 'Uprav mě!',
423423 'placeholder' => 'Zde napiš obsah stánky',
···441441 'stats' => [
442442 'hit_accuracy' => 'Přesnost zásahů',
443443 'level' => 'Úroveň :level',
444444- 'level_progress' => 'Postup do dalšího levelu',
444444+ 'level_progress' => 'postup do dalšího levelu',
445445 'maximum_combo' => 'Maximální Combo',
446446 'medals' => 'Medaile',
447447 'play_count' => 'Počet zahrání',
+1
resources/lang/da/authorization.php
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Kun score ejer kan pin score.',
175176 'too_many' => 'Fastgjort for mange score.',
176177 ],
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Nur der Spieler, der den Score eingereicht hat, kann ihn anpinnen.',
175176 'too_many' => 'Zu viele Scores angepinnt.',
176177 ],
+1-1
resources/lang/de/beatmappacks.php
···2727 'not_cleared' => 'nicht geschafft',
2828 ],
2929 'no_diff_reduction' => [
3030- '_' => ':link darf zum Absolvieren dieses Beatmap-Packs nicht verwendet werden.',
3030+ '_' => ':link dürfen zum Absolvieren dieses Beatmap-Pakets nicht verwendet werden.',
3131 'link' => 'Mods zur Vereinfachung der Schwierigkeit',
3232 ],
3333 ],
···3434 ],
35353636 'profile' => [
3737+ 'country' => 'country',
3738 'title' => 'Profile',
3939+4040+ 'country_change' => [
4141+ '_' => "It looks like your account country doesn't match your country of residence. :update_link.",
4242+ 'update_link' => 'Update to :country',
4343+ ],
38443945 'user' => [
4046 'user_discord' => 'discord',
+3-3
resources/lang/en/artist.php
···44// See the LICENCE file in the repository root for full licence text.
5566return [
77- 'page_description' => 'Featured artists on osu!',
77+ 'page_description' => 'Featured Artists on osu!',
88 'title' => 'Featured Artists',
991010 'admin' => [
···1818 ],
19192020 'index' => [
2121- 'description' => 'Featured artists are artists that we are working in collaboration with in order to bring new and original music to osu!. These artists and a selection of their tracks have been hand-picked by the osu! team as being awesomesauce and suitable for mapping. Some of these featured artists have also created exclusive new tracks for use in osu!.<br><br>All tracks in this section are provided as pre-timed .osz files and have been officially licensed for use in osu! and osu!-related content.',
2121+ 'description' => 'Featured Artists are artists that we are working in collaboration with in order to bring new and original music to osu!. These artists and a selection of their tracks have been hand-picked by the osu! team as being awesomesauce and suitable for mapping. Some of these Featured Artists have also created exclusive new tracks for use in osu!.<br><br>All tracks in this section are provided as pre-timed .osz files and have been officially licensed for use in osu! and osu!-related content.',
2222 ],
23232424 'links' => [
···43434444 'tracks' => [
4545 'index' => [
4646- '_' => 'tracks search',
4646+ '_' => 'track search',
47474848 'form' => [
4949 'advanced' => 'Advanced Search',
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Vain tuloksen omistaja voi kiinnittää tuloksen.',
175176 'too_many' => 'Kiinnitit liian monta tulosta.',
176177 ],
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Tanging ang may-ari ng iskor ang maaaring mag-pin ng iskor.',
175176 'too_many' => 'Nag-pin ng masyadong maraming mga iskor.',
176177 ],
-9
resources/lang/fil/notifications.php
···153153 ],
154154 ],
155155156156- 'legacy_pm' => [
157157- '_' => 'Lumang Forum PM',
158158-159159- 'legacy_pm' => [
160160- '_' => '',
161161- 'legacy_pm' => ':count_delimited na hindi pa nabasang mensahe|:count_delimited na hindi pa nabasang mga mensahe',
162162- ],
163163- ],
164164-165156 'user' => [
166157 'user_beatmapset_new' => [
167158 '_' => 'Bagong beatmap',
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Hanya pemilik skor yang dapat menyematkan skor.',
175176 'too_many' => 'Skor yang disematkan sudah terlalu banyak.',
176177 ],
+1-1
resources/lang/id/beatmaps.php
···163163 'confirm' => "Apakah kamu yakin? Dengan ini, kamu akan memberikan 1 hype kepada beatmap ini dari :n hype yang kamu miliki saat ini. Tindakan ini tidak dapat diurungkan.",
164164 'explanation' => 'Berikan hype-mu untuk membawa beatmap ini lebih dekat menuju Ranked!',
165165 'explanation_guest' => 'Masuk dan berikan hype kepada beatmap ini agar beatmap ini dapat segera dinominasikan dan di-rank!',
166166- 'new_time' => "Anda akan mendapatkan hype tambahan :new_time.",
166166+ 'new_time' => "Kamu akan memperoleh lebih banyak hype :new_time.",
167167 'remaining' => 'Kamu memiliki :remaining hype tersisa.',
168168 'required_text' => 'Hype: :current/:required',
169169 'section_title' => 'Hype Train',
···329329 'title' => 'saya!',
330330 ],
331331 'medals' => [
332332- 'empty' => "Pengguna ini belum membuka medali apapun. ;_;",
332332+ 'empty' => "Pengguna ini belum membuka medali apa pun. ;_;",
333333 'recent' => 'Terbaru',
334334 'title' => 'Medali',
335335 ],
+1
resources/lang/it/authorization.php
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Solo il proprietario del punteggio può fissarlo.',
175176 'too_many' => 'Hai già fissato troppi punteggi.',
176177 ],
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'index' => [
88+ 'title' => 'Карта пікірталас дауыстары',
99+ ],
1010+1111+ 'item' => [
1212+ 'score' => 'Нәтиже',
1313+ ],
1414+];
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'title_compact' => '',
88+ 'too_many' => '',
99+1010+ 'buttons' => [
1111+ 'add' => '',
1212+ 'disabled' => '',
1313+ 'remove' => '',
1414+ ],
1515+];
+6
resources/lang/kk-KZ/help.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [];
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'insufficient_stock' => '',
88+ 'must_separate' => '',
99+ 'not_available' => '',
1010+ 'too_many' => '',
1111+];
+30
resources/lang/kk-KZ/multiplayer.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'empty' => [
88+ '_' => '',
99+ 'playlists' => '',
1010+ 'realtime' => '',
1111+ ],
1212+1313+ 'room' => [
1414+ 'hosted_by' => '',
1515+ 'invalid_password' => '',
1616+ 'map_count' => '',
1717+ 'player_count' => '',
1818+ 'time_left' => '',
1919+2020+ 'errors' => [
2121+ 'duration_too_long' => '',
2222+ ],
2323+2424+ 'status' => [
2525+ 'active' => '',
2626+ 'ended' => '',
2727+ 'soon' => '',
2828+ ],
2929+ ],
3030+];
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'show' => [
88+ 'title' => '',
99+1010+ 'beatmap' => [
1111+ 'by' => '',
1212+ ],
1313+1414+ 'player' => [
1515+ 'by' => '',
1616+ 'submitted_on' => '',
1717+1818+ 'rank' => [
1919+ 'country' => '',
2020+ 'global' => '',
2121+ ],
2222+ ],
2323+ ],
2424+2525+ 'status' => [
2626+ 'non_best' => '',
2727+ 'non_passing' => '',
2828+ 'processing' => '',
2929+ ],
3030+];
+12
resources/lang/kk-KZ/sessions.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'create' => [
88+ 'download' => '',
99+ 'label' => '',
1010+ 'title' => '',
1111+ ],
1212+];
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'months' => '',
88+99+ 'user_search' => [
1010+ 'searching' => '',
1111+ 'not_found' => "",
1212+ ],
1313+];
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'mixture' => '',
88+ 'required' => '',
99+];
+30
resources/lang/kk-KZ/wiki.php
···11+<?php
22+33+// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
44+// See the LICENCE file in the repository root for full licence text.
55+66+return [
77+ 'show' => [
88+ 'fallback_translation' => '',
99+ 'incomplete_or_outdated' => '',
1010+ 'missing' => '',
1111+ 'missing_title' => '',
1212+ 'missing_translation' => '',
1313+ 'needs_cleanup_or_rewrite' => '',
1414+ 'search' => '',
1515+ 'stub' => '',
1616+ 'toc' => '',
1717+1818+ 'edit' => [
1919+ 'link' => '',
2020+ 'refresh' => '',
2121+ ],
2222+2323+ 'translation' => [
2424+ 'legal' => '',
2525+ 'outdated' => '',
2626+2727+ 'default' => '',
2828+ ],
2929+ ],
3030+];
+1
resources/lang/ko/authorization.php
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => '점수 소유자만 점수를 고정할 수 있습니다.',
175176 'too_many' => '너무 많은 점수를 고정했습니다.',
176177 ],
···44// See the LICENCE file in the repository root for full licence text.
5566return [
77- 'page_description' => 'Wyróżnieni artyści osu!',
77+ 'page_description' => 'Wyróżnieni wykonawcy osu!',
88 'title' => 'Wyróżnieni artyści',
991010 'admin' => [
···1818 ],
19192020 'index' => [
2121- 'description' => 'Wyróżnieni artyści to osoby z którymi współpracujemy, by zapewnić społeczności osu! oryginalną muzykę - część artystów stworzyła utwory wyłączne dla naszej gry! Zarówno oni sami, jak i ich utwory, zostali starannie wybrani przez członków zespołu osu!, głównie za ich odlotowość oraz łatwość w tworzeniu beatmap.<br><br>Wszystkie utwory w tej sekcji występują w formie plików o rozszerzeniu .osz z odpowiednio ustawionym rytmem. Utwory są oficjalnie licencjonowane dla osu! i rzeczy z nim powiązanych.',
2121+ 'description' => 'Wyróżnieni wykonawcy to osoby z którymi współpracujemy, by zapewnić społeczności osu! oryginalną muzykę - część artystów stworzyła utwory wyłączne dla naszej gry! Zarówno oni sami, jak i ich utwory, zostali starannie wybrani przez członków zespołu osu!, głównie za ich odlotowość oraz łatwość w tworzeniu beatmap.<br><br>Wszystkie utwory w tej sekcji występują w formie plików o rozszerzeniu .osz z odpowiednio ustawionym rytmem. Utwory są oficjalnie licencjonowane dla osu! i rzeczy z nim powiązanych.',
2222 ],
23232424 'links' => [
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Só o dono da pontuação é que a pode fixar.',
175176 'too_many' => 'Afixaste demasiadas pontuações.',
176177 ],
+3-3
resources/lang/pt/legacy_irc_key.php
···44// See the LICENCE file in the repository root for full licence text.
5566return [
77- 'confirm_new' => '',
88- 'new' => '',
99- 'none' => '',
77+ 'confirm_new' => 'Criar nova palavra-passe de IRC?',
88+ 'new' => 'Nova palavra-passe de IRC legado',
99+ 'none' => 'A palavra-passe de IRC não foi definida.',
10101111 'form' => [
1212 'server_host' => 'servidor',
···171171172172 'score' => [
173173 'pin' => [
174174+ 'disabled_type' => "",
174175 'not_owner' => 'Numai creatorul scorului poate fixa acest scor.',
175176 'too_many' => 'Ai fixat prea multe scoruri.',
176177 ],
+1-1
resources/lang/ro/beatmap_discussions.php
···67676868 'reply' => [
6969 'open' => [
7070- 'guest' => 'Conectează-te pentru a răspunde',
7070+ 'guest' => 'Autentifică-te pentru a răspunde',
7171 'user' => 'Răspunde',
7272 ],
7373 ],
+1-1
resources/lang/ro/beatmappacks.php
···44444545 'require_login' => [
4646 '_' => 'Trebuie să fii :link pentru a descărca',
4747- 'link_text' => 'conectat',
4747+ 'link_text' => 'autentificat',
4848 ],
4949];
+10-10
resources/lang/ro/beatmaps.php
···2626 'message_type_select' => 'Selectează tipul comentariului',
2727 'reply_notice' => 'Apasă enter pentru a răspunde.',
2828 'reply_placeholder' => 'Scrie-ți răspunsul aici',
2929- 'require-login' => 'Te rugăm să te conectezi pentru a posta sau a răspunde',
2929+ 'require-login' => 'Te rugăm să te autentifici pentru a posta sau a răspunde',
3030 'resolved' => 'Rezolvat',
3131 'restore' => 'restabilește',
3232 'show_deleted' => 'Arată șterse',
···158158 ],
159159160160 'hype' => [
161161- 'button' => '\'Hype\' acest beatmap!',
162162- 'button_done' => 'Deja Hyped!',
163163- 'confirm' => "Ești sigur? Acest lucru îți va folosi unul din restul tău de :n 'hype' rămași și nu poate fi anulat.",
164164- 'explanation' => '\'Hype\' acest beatmap pentru a-l face mai vizibil pentru nominalizare și clasament!',
165165- 'explanation_guest' => 'Conectează-te și \'hype\' acest beatmap pentru a-l face mai vizibil pentru nominalizare și clasament!',
166166- 'new_time' => "O să primești alt 'hype' pe :new_time.",
167167- 'remaining' => 'Mai ai :remaining \'hype\' rămași.',
161161+ 'button' => 'Acordă hype acestui beatmap!',
162162+ 'button_done' => 'Hype deja acordat!',
163163+ 'confirm' => "Ești sigur? Acest lucru îți va folosi unul din cele :n hype rămase și nu poate fi anulat.",
164164+ 'explanation' => 'Acordă un hype acestui beatmap pentru a-l face mai vizibil pentru nominalizare și clasament!',
165165+ 'explanation_guest' => 'Autentifică-te și acordă un hype acestui beatmap pentru a-l face mai vizibil pentru nominalizare și clasament!',
166166+ 'new_time' => "Vei primi un alt hype pe :new_time.",
167167+ 'remaining' => 'Mai ai :remaining hype rămași.',
168168 'required_text' => 'Hype: :current/:required',
169169- 'section_title' => 'Trenul Hype',
169169+ 'section_title' => 'Trenul de Hype',
170170 'title' => 'Hype',
171171 ],
172172···191191 'nominate' => 'Nominalizează',
192192 'nominate_confirm' => 'Nominalizezi acest beatmap?',
193193 'nominated_by' => 'nominalizat de :users',
194194- 'not_enough_hype' => "Nu este suficient hype.",
194194+ 'not_enough_hype' => "Nu există suficient hype.",
195195 'remove_from_loved' => 'Șterge din Iubit',
196196 'remove_from_loved_prompt' => 'Motivul pentru ștergere din Iubit:',
197197 'required_text' => 'Nominalizări: :current/:required',
+3-3
resources/lang/ro/beatmapsets.php
···102102 ],
103103104104 'hype' => [
105105- 'action' => 'Hype această mapă dacă ți-a plăcut să o joci, astfel încât să progreseze la stadiul de <strong>Clasat</strong>.',
105105+ 'action' => 'Acordă un hype acestui beatmap dacă ți-a plăcut să îl joci pentru a îl ajuta să progreseze la stadiul de <strong>Clasat</strong>.',
106106107107 'current' => [
108108- '_' => 'Această mapă este în prezent :status.',
108108+ '_' => 'Acest beatmap este în prezent :status.',
109109110110 'status' => [
111111 'pending' => 'în așteptare',
···178178179179 'no_scores' => [
180180 'country' => 'Nimeni din țara ta nu a stabilit un scor pe acest beatmap încă!',
181181- 'friend' => 'Nimeni din prietenii tăi nu a stabilit un scor pe acest beatmap încă!',
181181+ 'friend' => 'Niciunul dintre prietenii tăi nu a stabilit un scor pe acest beatmap încă!',
182182 'global' => 'Niciun scor încă. Poate ar trebui să încerci să obții câteva?',
183183 'loading' => 'Se încarcă scorurile...',
184184 'unranked' => 'Beatmap neclasificat.',
···13131414 'beatmapset_discussion' => [
1515 'beatmap_missing' => 'Marcajul de timp este specificat dar beatmap-ul lipsește.',
1616- 'beatmapset_no_hype' => "Acest beatmap nu poate fi hyped.",
1717- 'hype_requires_null_beatmap' => 'Hype trebuie să fie făcut în secțiunea General (toate dificultățile).',
1616+ 'beatmapset_no_hype' => "Nu poți acorda hype acestui beatmap.",
1717+ 'hype_requires_null_beatmap' => 'Hype-ul trebuie să fie acordat în secțiunea General (toate dificultățile).',
1818 'invalid_beatmap_id' => 'Dificultatea specificată nu este validă.',
1919 'invalid_beatmapset_id' => 'Beatmap-ul specificat nu este valid.',
2020 'locked' => 'Discuția este închisă.',
···2525 ],
26262727 'hype' => [
2828- 'discussion_locked' => "Acest beatmap este momentan blocat pentru discuții și nu poate fi hyped",
2828+ 'discussion_locked' => "Acest beatmap este momentan blocat pentru discuții și hype-ul nu poate fi acordat",
2929 'guest' => 'Trebuie să fii autentificat pentru a acorda un hype.',
3030 'hyped' => 'Deja ai acordat un hype acestui beatmap.',
3131 'limit_exceeded' => 'Ți-ai folosit deja tot hype-ul.',
3232- 'not_hypeable' => 'Acest beatmap nu poate fi hyped',
3232+ 'not_hypeable' => 'Nu poți acorda hype acestui beatmap',
3333 'owner' => 'Nu ii poți acorda un hype propriului tău beatmap.',
3434 ],
3535
···3131description | string? | |
3232icon | string? | display icon for the channel
3333type | [ChannelType](#channeltype) | type of channel
3434+message_length_limit | number | |
3435moderated | boolean | user can't send message when the value is `true`
3536uuid | string? | value from requests that is relayed back to the sender.
3637
+1-1
resources/views/docs/_structures/comment.md
···2020}
2121```
22222323-Represents an single comment.
2323+Represents a single comment.
24242525Field | Type | Description
2626---------------- | ---------- | ------------------
+8
resources/views/docs/_structures/nomination.md
···11+## Nomination
22+33+Field | Type
44+-----------------|-----
55+beatmapset_id | number
66+rulesets | [GameMode](#gamemode)[]
77+reset | boolean
88+user_id | number
+3
resources/views/docs/_structures/user_compact.md
···4848country | |
4949cover | |
5050favourite_beatmapset_count | number
5151+follow_user_mapping | number[]
5152follower_count | number
5253friends | |
5354graveyard_beatmapset_count | number
5455groups | [UserGroup](#usergroup)[]
5656+guest_beatmapset_count | number
5557is_restricted | boolean?
5658loved_beatmapset_count | number
5959+mapping_follower_count | number
5760monthly_playcounts | [UserMonthlyPlaycount](#usermonthlyplaycount)[]
5861page | |
5962pending_beatmapset_count | |
···6161count_300 | number | |
6262count_50 | number | |
6363count_miss | number | |
6464+country_rank | number? | Current country rank according to pp. |
6465grade_counts.a | number | Number of A ranked scores.
6566grade_counts.s | number | Number of S ranked scores.
6667grade_counts.sh | number | Number of Silver S ranked scores.
···7475play_count | number | Number of maps played.
7576play_time | number | Cumulative time played.
7677pp | number | Performance points
7878+pp_exp | number | Experimental (lazer) performance points
7779global_rank | number? | Current rank according to pp.
8080+global_rank_exp | number? | Current rank according to experimental (lazer) pp.
7881ranked_score | number | Current ranked score.
7982replays_watched_by_others | number | Number of replays watched by other users.
8083total_hits | number | Total number of hits.
···22 Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
33 See the LICENCE file in the repository root for full licence text.
44--}}
55+@php
66+ use App\Models\NewsPost;
77+88+ $newsPostLargePreviews = NewsPost::LANDING_LIMIT;
99+@endphp
510@extends('master')
611712@section('content')
···1318 <h2 class="user-home__news-title">{{ osu_trans('home.user.news.title') }}</h2>
14191520 @foreach ($news as $post)
1616- @if ($loop->iteration > 3)
2121+ @if ($loop->iteration > $newsPostLargePreviews)
1722 @break
1823 @endif
19242025 @include('home._user_news_post_preview', ['post' => $post, 'collapsed' => false])
2126 @endforeach
22272323- @if (count($news) > 3)
2828+ @if (count($news) > $newsPostLargePreviews)
2429 <div class="user-home__news-posts-group">
2530 @foreach ($news as $post)
2626- @if ($loop->iteration <= 3)
3131+ @if ($loop->iteration <= $newsPostLargePreviews)
2732 @continue
2833 @endif
2934···3237 </div>
3338 @endif
34393535- @if (count($news) > App\Models\NewsPost::DASHBOARD_LIMIT)
4040+ @if (count($news) > NewsPost::DASHBOARD_LIMIT)
3641 <a
3742 href="{{ route('news.index') }}"
3843 class="user-home__news-posts-group user-home__news-posts-group--more"
+1-1
resources/views/layout/popup-container.blade.php
···33 See the LICENCE file in the repository root for full licence text.
44--}}
55@php
66- $popup = Session::get('popup');
66+ $popup = Session('popup');
77@endphp
88<div id="popup-container">
99 <div class="alert alert-dismissable popup-clone col-md-6 col-md-offset-3 text-center" style="display: none">
+2-2
resources/views/objects/_flag_country.blade.php
···22 Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
33 See the LICENCE file in the repository root for full licence text.
44--}}
55-<div class="{{ class_with_modifiers('flag-country', $modifiers ?? []) }}"
55+<span class="{{ class_with_modifiers('flag-country', $modifiers ?? []) }}"
66 @if (isset($countryName))
77 title="{{ $countryName }}"
88 @endif
99 style="background-image: url('{{ flag_url($countryCode) }}');"
1010-></div>
1010+></span>
+6-3
resources/views/rankings/kudosu.blade.php
···22 Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the GNU Affero General Public License v3.0.
33 See the LICENCE file in the repository root for full licence text.
44--}}
55+@php
66+ $firstScoreRank = $scores->firstItem();
77+@endphp
58@extends('rankings.index', [
69 'hasFilter' => false,
710 'hasMode' => false,
88- 'hasPager' => false,
1111+ 'hasPager' => true,
912 'type' => 'kudosu',
1013])
1114···2730 </tr>
2831 </thead>
2932 <tbody>
3030- @foreach ($users as $index => $user)
3333+ @foreach ($scores as $index => $user)
3134 <tr class="{{ class_with_modifiers('ranking-page-table__row', ['inactive' => !$user->isActive()]) }}">
3235 <td class="ranking-page-table__column ranking-page-table__column--rank">
3333- #{{ i18n_number_format($index + 1) }}
3636+ #{{ i18n_number_format($index + $firstScoreRank) }}
3437 </td>
3538 <td class="ranking-page-table__column">
3639 <div class="ranking-page-table__user-link">
+1
routes/web.php
···204204 // Reference: https://bugs.php.net/bug.php?id=55815
205205 // Note that hhvm behaves differently (the same as POST).
206206 Route::post('avatar', 'AccountController@avatar')->name('avatar');
207207+ Route::put('country', 'AccountController@updateCountry')->name('country');
207208 Route::post('cover', 'AccountController@cover')->name('cover');
208209 Route::put('email', 'AccountController@updateEmail')->name('email');
209210 Route::put('notification-options', 'AccountController@updateNotificationOptions')->name('notification-options');
-2
tests/Browser/SanityTest.php
···360360361361 // TODO: add additional logic for certain routes to re-run tests per game mode, per user score type, etc
362362 $this->browse(function (Browser $browser) use ($route, $type, $url) {
363363- static::resetSession($browser);
364364-365363 try {
366364 if ($type === 'user') {
367365 $browser->loginAs(self::$scaffolding['user']);