diff --git a/CHANGELOG.md b/CHANGELOG.md index b3655536b5..ddf4c4ecc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Feat: マウスでもタイムラインを引っ張って更新できるように - アクセシビリティ設定からオフにすることもできます - Enhance: タイムラインのパフォーマンスを向上 +- Enhance: バックアップされた設定のプロファイルを削除できるように - Fix: 一部のブラウザでアコーディオンメニューのアニメーションが動作しない問題を修正 - Fix: ダイアログのお知らせが画面からはみ出ることがある問題を修正 - Fix: ユーザーポップアップでエラーが生じてもインジケーターが表示され続けてしまう問題を修正 diff --git a/assets/ui-icons.afdesign b/assets/ui-icons.afdesign new file mode 100644 index 0000000000..e36bfcd3ca Binary files /dev/null and b/assets/ui-icons.afdesign differ diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index b331f64330..12779fafa4 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1348,6 +1348,7 @@ readonly: "Només lectura" goToDeck: "Tornar al tauler" federationJobs: "Treballs sindicats " driveAboutTip: "Al Disc veure's una llista de tots els arxius que has anat pujant.
\nPots tornar-los a fer servir adjuntant-los a notes noves o pots adelantar-te i pujar arxius per publicar-los més tard!
\nTingués en compte que si esborres un arxiu també desapareixerà de tots els llocs on l'has fet servir (notes, pàgines, avatars, imatges de capçalera, etc.)
\nTambé pots crear carpetes per organitzar les." +scrollToClose: "Desplaçar per tancar" _chat: noMessagesYet: "Encara no tens missatges " newMessage: "Missatge nou" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 2c7a1d6f95..4ca8f27790 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1348,6 +1348,7 @@ readonly: "Nur Lesezugriff" goToDeck: "Zurück zum Deck" federationJobs: "Föderation Jobs" driveAboutTip: "In Drive sehen Sie eine Liste der Dateien, die Sie in der Vergangenheit hochgeladen haben.
\nSie können diese Dateien wiederverwenden um sie zu beispiel an Notizen anzuhängen, oder sie können Dateien vorab hochzuladen, um sie später zu versenden!
\nWenn Sie eine Datei löschen, verschwindet sie auch von allen Stellen, an denen Sie sie verwendet haben (Notizen, Seiten, Avatare, Banner usw.).
\nSie können auch Ordner erstellen, um sie zu organisieren." +scrollToClose: "Zum Schließen scrollen" _chat: noMessagesYet: "Noch keine Nachrichten" newMessage: "Neue Nachricht" @@ -1425,6 +1426,7 @@ _settings: ifOff: "Wenn ausgeschaltet" enableSyncThemesBetweenDevices: "Synchronisierung von installierten Themen auf verschiedenen Endgeräten" enablePullToRefresh: "Ziehen zum Aktualisieren" + enablePullToRefresh_description: "Bei Benutzung einer Maus, mit gedrücktem Mausrad ziehen" _chat: showSenderName: "Name des Absenders anzeigen" sendOnEnter: "Eingabetaste sendet Nachricht" diff --git a/locales/en-US.yml b/locales/en-US.yml index e2312b0422..4efd1a36f6 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -1426,7 +1426,7 @@ _settings: ifOff: "When turned off" enableSyncThemesBetweenDevices: "Synchronize installed themes across devices" enablePullToRefresh: "Pull to Refresh" - enablePullToRefresh_description: "When using a mouse, drag while pressing in the scrolling wheel." + enablePullToRefresh_description: "When using a mouse, drag while pressing in the scroll wheel." _chat: showSenderName: "Show sender's name" sendOnEnter: "Press Enter to send" diff --git a/locales/index.d.ts b/locales/index.d.ts index 22d9254c20..81da082c51 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -5745,6 +5745,10 @@ export interface Locale extends ILocale { * 例: 「メインPC」、「スマホ」など */ "profileNameDescription2": string; + /** + * プロファイルの管理 + */ + "manageProfiles": string; }; "_preferencesBackup": { /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 27391a69aa..8dd18e2fee 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1439,6 +1439,7 @@ _preferencesProfile: profileName: "プロファイル名" profileNameDescription: "このデバイスを識別する名前を設定してください。" profileNameDescription2: "例: 「メインPC」、「スマホ」など" + manageProfiles: "プロファイルの管理" _preferencesBackup: autoBackup: "自動バックアップ" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index e81af534e7..c801126435 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -5,6 +5,7 @@ introMisskey: "Добро пожаловать! Misskey — это децент poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом Misskey, называемый экземпляром Misskey." monthAndDay: "{day}.{month}" search: "Поиск" +reset: "Сброс" notifications: "Уведомления" username: "Имя пользователя" password: "Пароль" @@ -48,6 +49,7 @@ pin: "Закрепить в профиле" unpin: "Открепить от профиля" copyContent: "Скопировать содержимое" copyLink: "Скопировать ссылку" +copyRemoteLink: "Скопировать ссылку на репост" copyLinkRenote: "Скопировать ссылку на репост" delete: "Удалить" deleteAndEdit: "Удалить и отредактировать" @@ -215,8 +217,10 @@ perDay: "По дням" stopActivityDelivery: "Остановить отправку обновлений активности" blockThisInstance: "Блокировать этот инстанс" silenceThisInstance: "Заглушить этот инстанс" +mediaSilenceThisInstance: "Заглушить сервер" operations: "Операции" software: "Программы" +softwareName: "Software Name" version: "Версия" metadata: "Метаданные" withNFiles: "Файлы, {n} шт." @@ -235,7 +239,11 @@ clearCachedFilesConfirm: "Удалить все закэшированные ф blockedInstances: "Заблокированные инстансы" blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать. Они больше не смогут обмениваться с вашим инстансом." silencedInstances: "Заглушённые инстансы" +silencedInstancesDescription: "Перечислите имена серверов, которые вы хотите отключить, разделив их новой строкой. Все учетные записи, принадлежащие к указанным в списке серверам, будут заблокированы и смогут отправлять запросы только на повторное использование и не смогут указывать локальные учетные записи, если они не будут отслеживаться. Это не повлияет на заблокированные серверы." +mediaSilencedInstances: "Заглушённые сервера" +mediaSilencedInstancesDescription: "Укажите названия серверов, для которых вы хотите отключить доступ к файлам, по одному серверу в строке. Все учетные записи, принадлежащие к перечисленным серверам, будут считаться конфиденциальными и не смогут использовать пользовательские эмодзи. Это никак не повлияет на заблокированные серверы." federationAllowedHosts: "Серверы, поддерживающие федерацию" +federationAllowedHostsDescription: "Укажите имена серверов, для которых вы хотите разрешить объединение, разделив их разделителями строк." muteAndBlock: "Скрытие и блокировка" mutedUsers: "Скрытые пользователи" blockedUsers: "Заблокированные пользователи" @@ -294,6 +302,7 @@ uploadFromUrlMayTakeTime: "Загрузка может занять некото explore: "Обзор" messageRead: "Прочитали" noMoreHistory: "История закончилась" +startChat: "Начать чат" nUsersRead: "Прочитали {n}" agreeTo: "Я соглашаюсь с {0}" agree: "Согласен" @@ -416,6 +425,7 @@ antennaExcludeBots: "Исключать ботов" antennaKeywordsDescription: "Пишите слова через пробел в одной строке, чтобы ловить их появление вместе; на отдельных строках располагайте слова, или группы слов, чтобы ловить любые из них." notifyAntenna: "Уведомлять о новых заметках" withFileAntenna: "Только заметки с вложениями" +excludeNotesInSensitiveChannel: "Исключить заметки из конфиденциальных каналов" enableServiceworker: "Включить ServiceWorker" antennaUsersDescription: "Пишите каждое название аккаута на отдельной строке" caseSensitive: "С учётом регистра" @@ -446,6 +456,8 @@ totpDescription: "Описание приложения-аутентификат moderator: "Модератор" moderation: "Модерация" moderationNote: "Примечания модератора" +moderationNoteDescription: "Вы можете заполнять заметки, которые будут доступны только модераторам." +addModerationNote: "" moderationLogs: "Журнал модерации" nUsersMentioned: "Упомянуло пользователей: {n}" securityKeyAndPasskey: "Ключ безопасности и парольная фраза" @@ -506,6 +518,8 @@ emojiStyle: "Стиль эмодзи" native: "Системные" menuStyle: "Стиль меню" style: "Стиль" +drawer: "Панель" +popup: "Всплывающие окна" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" showReactionsCount: "Видеть количество реакций на заметках" noHistory: "История пока пуста" @@ -560,6 +574,7 @@ serverLogs: "Журнал сервера" deleteAll: "Удалить всё" showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты" showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)" +withRepliesByDefaultForNewlyFollowed: "По умолчанию включайте ответы новых пользователей, на которых вы подписались, во временную шкалу" newNoteRecived: "Появилась новая заметка" sounds: "Звуки" sound: "Звуки" @@ -572,6 +587,7 @@ masterVolume: "Основная регулировка громкости" notUseSound: "Выключить звук" useSoundOnlyWhenActive: "Воспроизводить звук только когда Misskey активен." details: "Подробнее" +renoteDetails: "Узнать больше" chooseEmoji: "Выберите эмодзи" unableToProcess: "Не удаётся завершить операцию" recentUsed: "Последние использованные" @@ -587,6 +603,8 @@ ascendingOrder: "по возрастанию" descendingOrder: "По убыванию" scratchpad: "Когтеточка" scratchpadDescription: "«Когтеточка» — это место для опытов с AiScript. Здесь можно писать программы, взаимодействующие с Misskey, запускать и смотреть что из этого получается." +uiInspector: "Средство проверки пользовательского интерфейса" +uiInspectorDescription: "Вы можете просмотреть список экземпляров компонентов пользовательского интерфейса, существующих в памяти. Элементы пользовательского интерфейса генерируются с помощью серии функций Ui:C:." output: "Выходы" script: "Скрипт" disablePagesScript: "Отключить скрипты на «Страницах»" @@ -667,14 +685,19 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений" smtpSecureInfo: "Выключите при использовании STARTTLS." testEmail: "Проверка доставки электронной почты" wordMute: "Скрытие слов" +wordMuteDescription: "Сведите к минимуму записи, содержащие указанное утверждение. Нажмите на свернутую запись, чтобы отобразить ее." hardWordMute: "Строгое скрытие слов" +showMutedWord: "Отображать слово без уведомления (звука)" +hardWordMuteDescription: "Скрыть заметки, содержащие указанное слово или фразу. В отличие от word mute, заметка будет полностью скрыта от просмотра." regexpError: "Ошибка в регулярном выражении" regexpErrorDescription: "В списке {tab} скрытых слов, в строке {line} обнаружена синтаксическая ошибка:" instanceMute: "Глушение инстансов" userSaysSomething: "{name} что-то сообщает" +userSaysSomethingAbout: "{name} что-то говорил о「{word}」" makeActive: "Активировать" display: "Отображение" copy: "Копировать" +copiedToClipboard: "Скопированы в буфер обмена" metrics: "Метрики" overview: "Обзор" logs: "Журналы" @@ -840,6 +863,7 @@ administration: "Управление" accounts: "Учётные записи" switch: "Переключение" noMaintainerInformationWarning: "Не заполнены сведения об администраторах" +noInquiryUrlWarning: "URL-адрес контактной формы еще не задан." noBotProtectionWarning: "Ботозащита не настроена" configure: "Настроить" postToGallery: "Опубликовать в галерею" @@ -904,6 +928,7 @@ followersVisibility: "Видимость подписчиков" continueThread: "Показать следующие ответы" deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?" incorrectPassword: "Пароль неверен." +incorrectTotp: "Введен неверный одноразовый пароль или срок его действия истек." voteConfirm: "Отдать голос за «{choice}»?" hide: "Спрятать" useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве" @@ -928,6 +953,9 @@ oneHour: "1 час" oneDay: "1 день" oneWeek: "1 неделя" oneMonth: "1 месяц" +threeMonths: "3 месяца" +oneYear: "1 год" +threeDays: "3 дня" reflectMayTakeTime: "Изменения могут занять время для отображения" failedToFetchAccountInformation: "Не удалось получить информацию об аккаунте" rateLimitExceeded: "Ограничение скорости превышено" @@ -952,6 +980,7 @@ document: "Документ" numberOfPageCache: "Количество сохранённых страниц в кэше" numberOfPageCacheDescription: "Описание количества страниц в кэше" logoutConfirm: "Вы хотите выйти из аккаунта?" +logoutWillClearClientData: "Когда вы выйдете из системы, информация о конфигурации клиента будет удалена из браузера.Чтобы иметь возможность восстановить информацию о вашей конфигурации при повторном входе в систему, пожалуйста, включите опцию автоматического резервного копирования в настройках." lastActiveDate: "Последняя дата использования" statusbar: "Статусбар" pleaseSelect: "Пожалуйста, выберите" @@ -1001,6 +1030,7 @@ neverShow: "Больше не показывать" remindMeLater: "Напомнить позже" didYouLikeMisskey: "Вам нравится Misskey?" pleaseDonate: "Сайт {host} работает на Misskey. Это бесплатное программное обеспечение, и ваши пожертвования очень бы помогли продолжать его разработку!" +correspondingSourceIsAvailable: "Соответствующий исходный код можно найти по адресу {anchor} " roles: "Роли" role: "Роль" noRole: "Нет роли" @@ -1056,6 +1086,7 @@ prohibitedWords: "Запрещённые слова" prohibitedWordsDescription: "Включает вывод ошибки при попытке опубликовать пост, содержащий указанное слово/набор слов.\nМножество слов может быть указано, разделяемые новой строкой." prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение." hiddenTags: "Скрытые хештеги" +hiddenTagsDescription: "Установленные теги не будут отображаться в тренде, можно установить несколько тегов." notesSearchNotAvailable: "Поиск заметок недоступен" license: "Лицензия" unfavoriteConfirm: "Удалить избранное?" @@ -1066,6 +1097,7 @@ retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?" retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться" enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей" enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов" +enableStatsForFederatedInstances: "Получить информацию об удаленном сервере" showClipButtonInNoteFooter: "Показать кнопку добавления в подборку в меню действий с заметкой" reactionsDisplaySize: "Размер реакций" limitWidthOfReaction: "Ограничить максимальную ширину реакций и отображать их в уменьшенном размере." @@ -1101,6 +1133,7 @@ preservedUsernames: "Зарезервированные имена пользо preservedUsernamesDescription: "Перечислите зарезервированные имена пользователей, отделяя их строками. Они станут недоступны при создании учётной записи. Это ограничение не применяется при создании учётной записи администраторами. Также, уже существующие учётные записи останутся без изменений." createNoteFromTheFile: "Создать заметку из этого файла" archive: "Архив" +archived: "Архивировано" unarchive: "Разархивировать" channelArchiveConfirmTitle: "Переместить {name} в архив?" channelArchiveConfirmDescription: "Архивированные каналы перестанут отображаться в списке каналов или результатах поиска. В них также нельзя будет добавлять новые записи." @@ -1121,6 +1154,7 @@ rolesThatCanBeUsedThisEmojiAsReaction: "Роли тех, кому можно и rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Если здесь ничего не указать, в качестве реакции эту эмодзи сможет использовать каждый." rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Эти роли должны быть общедоступными." cancelReactionConfirm: "Вы действительно хотите удалить свою реакцию?" +changeReactionConfirm: "Вы действительно хотите удалить свою реакцию?" later: "Позже" goToMisskey: "К Misskey" additionalEmojiDictionary: "Дополнительные словари эмодзи" @@ -1130,9 +1164,16 @@ enableServerMachineStats: "Опубликовать характеристики enableIdenticonGeneration: "Включить генерацию иконки пользователя" turnOffToImprovePerformance: "Отключение этого параметра может повысить производительность." createInviteCode: "Создать код приглашения" +createWithOptions: "Используйте параметры для создания" createCount: "Количество приглашений" +inviteCodeCreated: "Создан пригласительный код" +inviteLimitExceeded: "Достигнут предел количества пригласительных кодов, которые могут быть созданы." +createLimitRemaining: "Пригласительные коды, которые могут быть созданы: {limit} " +inviteLimitResetCycle: "За определенное {time} Вы можете создать неограниченное количество пригласительных кодов {limit} " expirationDate: "Дата истечения" noExpirationDate: "Бессрочно" +inviteCodeUsedAt: "Дата и время, когда был использован пригласительный код" +registeredUserUsingInviteCode: "Пользователи, которые использовали пригласительный код" unused: "Неиспользованное" used: "Использован" expired: "Срок действия приглашения истёк" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 64b05e58f5..28053bb7b0 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1424,6 +1424,7 @@ _settings: ifOn: "启用时" ifOff: "关闭时" enablePullToRefresh: "开启下拉刷新" + enablePullToRefresh_description: "使用鼠标时按下滚轮来拖动" _chat: showSenderName: "显示发送者的名字" sendOnEnter: "回车键发送" diff --git a/package.json b/package.json index 6d46d1c641..2f3d77b319 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2025.5.0-beta.0", + "version": "2025.5.0-rc.0", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/backend/migration/1745378064470-composite-note-index.js b/packages/backend/migration/1745378064470-composite-note-index.js index 1487aa9630..12108a6b3c 100644 --- a/packages/backend/migration/1745378064470-composite-note-index.js +++ b/packages/backend/migration/1745378064470-composite-note-index.js @@ -14,7 +14,7 @@ export class CompositeNoteIndex1745378064470 { if (concurrently) { const hasValidIndex = await queryRunner.query(`SELECT indisvalid FROM pg_index INNER JOIN pg_class ON pg_index.indexrelid = pg_class.oid WHERE pg_class.relname = 'IDX_724b311e6f883751f261ebe378'`); - if (!hasValidIndex || hasValidIndex[0].indisvalid !== true) { + if (hasValidIndex.length === 0 || hasValidIndex[0].indisvalid !== true) { await queryRunner.query(`DROP INDEX IF EXISTS "IDX_724b311e6f883751f261ebe378"`); await queryRunner.query(`CREATE INDEX CONCURRENTLY "IDX_724b311e6f883751f261ebe378" ON "note" ("userId", "id" DESC)`); } diff --git a/packages/backend/package.json b/packages/backend/package.json index a26a77c416..36f7781908 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -78,7 +78,7 @@ "@fastify/multipart": "9.0.3", "@fastify/static": "8.1.1", "@fastify/view": "10.0.2", - "@misskey-dev/sharp-read-bmp": "1.3.0", + "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.2.1", "@napi-rs/canvas": "0.1.69", "@nestjs/common": "11.1.0", diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts index 4fc1193f32..8d2de89efd 100644 --- a/packages/backend/src/core/AchievementService.ts +++ b/packages/backend/src/core/AchievementService.ts @@ -9,87 +9,7 @@ import type { MiUser } from '@/models/User.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { NotificationService } from '@/core/NotificationService.js'; - -export const ACHIEVEMENT_TYPES = [ - 'notes1', - 'notes10', - 'notes100', - 'notes500', - 'notes1000', - 'notes5000', - 'notes10000', - 'notes20000', - 'notes30000', - 'notes40000', - 'notes50000', - 'notes60000', - 'notes70000', - 'notes80000', - 'notes90000', - 'notes100000', - 'login3', - 'login7', - 'login15', - 'login30', - 'login60', - 'login100', - 'login200', - 'login300', - 'login400', - 'login500', - 'login600', - 'login700', - 'login800', - 'login900', - 'login1000', - 'passedSinceAccountCreated1', - 'passedSinceAccountCreated2', - 'passedSinceAccountCreated3', - 'loggedInOnBirthday', - 'loggedInOnNewYearsDay', - 'noteClipped1', - 'noteFavorited1', - 'myNoteFavorited1', - 'profileFilled', - 'markedAsCat', - 'following1', - 'following10', - 'following50', - 'following100', - 'following300', - 'followers1', - 'followers10', - 'followers50', - 'followers100', - 'followers300', - 'followers500', - 'followers1000', - 'collectAchievements30', - 'viewAchievements3min', - 'iLoveMisskey', - 'foundTreasure', - 'client30min', - 'client60min', - 'noteDeletedWithin1min', - 'postedAtLateNight', - 'postedAt0min0sec', - 'selfQuote', - 'htl20npm', - 'viewInstanceChart', - 'outputHelloWorldOnScratchpad', - 'open3windows', - 'driveFolderCircularReference', - 'reactWithoutRead', - 'clickedClickHere', - 'justPlainLucky', - 'setNameToSyuilo', - 'cookieClicked', - 'brainDiver', - 'smashTestNotificationButton', - 'tutorialCompleted', - 'bubbleGameExplodingHead', - 'bubbleGameDoubleExplodingHead', -] as const; +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; @Injectable() export class AchievementService { diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 27aa3d89de..e4eb10efca 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -67,6 +67,7 @@ import { packedChatMessageSchema, packedChatMessageLiteSchema, packedChatMessage import { packedChatRoomSchema } from '@/models/json-schema/chat-room.js'; import { packedChatRoomInvitationSchema } from '@/models/json-schema/chat-room-invitation.js'; import { packedChatRoomMembershipSchema } from '@/models/json-schema/chat-room-membership.js'; +import { packedAchievementNameSchema, packedAchievementSchema } from '@/models/json-schema/achievement.js'; export const refs = { UserLite: packedUserLiteSchema, @@ -78,6 +79,8 @@ export const refs = { User: packedUserSchema, UserList: packedUserListSchema, + Achievement: packedAchievementSchema, + AchievementName: packedAchievementNameSchema, Ad: packedAdSchema, Announcement: packedAnnouncementSchema, App: packedAppSchema, diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts index 5544555296..c4c1fa5ec9 100644 --- a/packages/backend/src/models/UserProfile.ts +++ b/packages/backend/src/models/UserProfile.ts @@ -274,7 +274,7 @@ export class MiUserProfile { default: [], }) public achievements: { - name: string; + name: typeof ACHIEVEMENT_TYPES[number]; unlockedAt: number; }[]; @@ -295,3 +295,84 @@ export class MiUserProfile { } } } + +export const ACHIEVEMENT_TYPES = [ + 'notes1', + 'notes10', + 'notes100', + 'notes500', + 'notes1000', + 'notes5000', + 'notes10000', + 'notes20000', + 'notes30000', + 'notes40000', + 'notes50000', + 'notes60000', + 'notes70000', + 'notes80000', + 'notes90000', + 'notes100000', + 'login3', + 'login7', + 'login15', + 'login30', + 'login60', + 'login100', + 'login200', + 'login300', + 'login400', + 'login500', + 'login600', + 'login700', + 'login800', + 'login900', + 'login1000', + 'passedSinceAccountCreated1', + 'passedSinceAccountCreated2', + 'passedSinceAccountCreated3', + 'loggedInOnBirthday', + 'loggedInOnNewYearsDay', + 'noteClipped1', + 'noteFavorited1', + 'myNoteFavorited1', + 'profileFilled', + 'markedAsCat', + 'following1', + 'following10', + 'following50', + 'following100', + 'following300', + 'followers1', + 'followers10', + 'followers50', + 'followers100', + 'followers300', + 'followers500', + 'followers1000', + 'collectAchievements30', + 'viewAchievements3min', + 'iLoveMisskey', + 'foundTreasure', + 'client30min', + 'client60min', + 'noteDeletedWithin1min', + 'postedAtLateNight', + 'postedAt0min0sec', + 'selfQuote', + 'htl20npm', + 'viewInstanceChart', + 'outputHelloWorldOnScratchpad', + 'open3windows', + 'driveFolderCircularReference', + 'reactWithoutRead', + 'clickedClickHere', + 'justPlainLucky', + 'setNameToSyuilo', + 'cookieClicked', + 'brainDiver', + 'smashTestNotificationButton', + 'tutorialCompleted', + 'bubbleGameExplodingHead', + 'bubbleGameDoubleExplodingHead', +] as const; diff --git a/packages/backend/src/models/json-schema/achievement.ts b/packages/backend/src/models/json-schema/achievement.ts new file mode 100644 index 0000000000..39a621a570 --- /dev/null +++ b/packages/backend/src/models/json-schema/achievement.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; + +export const packedAchievementNameSchema = { + type: 'string', + enum: ACHIEVEMENT_TYPES, + optional: false, +} as const; + +export const packedAchievementSchema = { + type: 'object', + properties: { + name: { + ref: 'AchievementName', + }, + unlockedAt: { + type: 'number', + optional: false, + }, + }, +} as const; diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index 7f23d2d6a1..6de120c8d7 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; import { notificationTypes, userExportableEntities } from '@/types.js'; const baseSchema = { @@ -312,9 +311,7 @@ export const packedNotificationSchema = { enum: ['achievementEarned'], }, achievement: { - type: 'string', - optional: false, nullable: false, - enum: ACHIEVEMENT_TYPES, + ref: 'AchievementName', }, }, }, { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index e475296702..2b5f706ff9 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -630,18 +630,7 @@ export const packedMeDetailedOnlySchema = { type: 'array', nullable: false, optional: false, items: { - type: 'object', - nullable: false, optional: false, - properties: { - name: { - type: 'string', - nullable: false, optional: false, - }, - unlockedAt: { - type: 'number', - nullable: false, optional: false, - }, - }, + ref: 'Achievement', }, }, loggedInDays: { diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts index e70905ef1b..0e42647ef7 100644 --- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts +++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts @@ -5,7 +5,8 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService.js'; +import { AchievementService } from '@/core/AchievementService.js'; +import { ACHIEVEMENT_TYPES } from '@/models/UserProfile.js'; export const meta = { requireCredential: true, diff --git a/packages/backend/src/server/api/endpoints/users/achievements.ts b/packages/backend/src/server/api/endpoints/users/achievements.ts index f7139b3684..bae216e347 100644 --- a/packages/backend/src/server/api/endpoints/users/achievements.ts +++ b/packages/backend/src/server/api/endpoints/users/achievements.ts @@ -14,15 +14,7 @@ export const meta = { res: { type: 'array', items: { - type: 'object', - properties: { - name: { - type: 'string', - }, - unlockedAt: { - type: 'number', - }, - }, + ref: 'Achievement', }, }, } as const; diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts index ce3f931bb0..ca6a639be8 100644 --- a/packages/backend/test/unit/entities/UserEntityService.ts +++ b/packages/backend/test/unit/entities/UserEntityService.ts @@ -232,7 +232,7 @@ describe('UserEntityService', () => { }); test('MeDetailed', async() => { - const achievements = [{ name: 'achievement', unlockedAt: new Date().getTime() }]; + const achievements = [{ name: 'iLoveMisskey' as const, unlockedAt: new Date().getTime() }]; const me = await createUser({}, { birthday: '2000-01-01', achievements: achievements, diff --git a/packages/frontend-embed/src/pages/not-found.vue b/packages/frontend-embed/src/pages/not-found.vue index 061254a39a..68897ca7e1 100644 --- a/packages/frontend-embed/src/pages/not-found.vue +++ b/packages/frontend-embed/src/pages/not-found.vue @@ -6,7 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -83,7 +80,6 @@ import XFile from './MkFormDialog.file.vue'; import type { Form } from '@/utility/form.js'; import MkModalWindow from '@/components/MkModalWindow.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; const props = defineProps<{ title: string; diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index 9d862a4eac..509099e0b9 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -5,12 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -38,7 +35,6 @@ import * as Misskey from 'misskey-js'; import XMessage from './XMessage.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; -import { infoImageUrl } from '@/instance.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import MkInput from '@/components/MkInput.vue'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue index 5390a48be5..21be0b18a9 100644 --- a/packages/frontend/src/pages/drive.file.info.vue +++ b/packages/frontend/src/pages/drive.file.info.vue @@ -68,10 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only -
- -
{{ i18n.ts.nothing }}
-
+ @@ -82,7 +79,6 @@ import MkInfo from '@/components/MkInfo.vue'; import MkMediaList from '@/components/MkMediaList.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import bytes from '@/filters/bytes.js'; -import { infoImageUrl } from '@/instance.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/utility/misskey-api.js'; diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index 4f57c1209e..b0a18987b4 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -7,12 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
- +