Compare commits

..

8 Commits

Author SHA1 Message Date
syuilo bfd3de6647 New translations ja-jp.yml (Italian) 2025-03-29 07:32:18 +09:00
syuilo 5f1ace5632 New translations ja-jp.yml (Chinese Simplified) 2025-03-29 01:36:26 +09:00
syuilo e3b93ddd14 New translations ja-jp.yml (Chinese Traditional) 2025-03-28 18:13:52 +09:00
syuilo f66f669071 New translations ja-jp.yml (English) 2025-03-28 17:11:49 +09:00
syuilo 408111ceab New translations ja-jp.yml (English) 2025-03-28 15:27:07 +09:00
syuilo f9ff1362b3 New translations ja-jp.yml (Catalan) 2025-03-28 15:27:05 +09:00
syuilo 6afc9c59a3 New translations ja-jp.yml (Chinese Simplified) 2025-03-28 11:31:16 +09:00
syuilo 7fae794d42 New translations ja-jp.yml (Chinese Traditional) 2025-03-28 11:30:54 +09:00
60 changed files with 421 additions and 476 deletions

View File

@ -38,7 +38,6 @@
- Enhance: プラグインの管理が強化されました
- インストール/アンインストール/設定の変更時にリロード不要になりました
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
- Enhance: アイコンのスクロール追従を無効化してパフォーマンス向上できるように
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
- Enhance: テーマ設定画面のデザインを改善

View File

@ -301,7 +301,6 @@ uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis das Hochladen abgeschl
explore: "Erkunden"
messageRead: "Gelesen"
noMoreHistory: "Kein weiterer Verlauf vorhanden"
startChat: "Chat starten"
nUsersRead: "Von {n} Benutzern gelesen"
agreeTo: "Ich stimme {0} zu"
agree: "Zustimmen"
@ -1319,55 +1318,25 @@ noName: "Kein Name"
skip: "Überspringen"
restore: "Wiederherstellen"
syncBetweenDevices: "Zwischen Geräten synchronisieren"
preferenceSyncConflictTitle: "Der konfigurierte Wert ist auf dem Server bereits vorhanden."
paste: "Einfügen"
postForm: "Notizfenster"
textCount: "Zeichenanzahl"
information: "Über"
chat: "Chat"
compress: "Komprimieren"
_chat:
noMessagesYet: "Noch keine Nachrichten"
createRoom: "Raum erstellen"
inviteUserToChat: "Lade Benutzer ein, um mit dem Chatten zu beginnen"
yourRooms: "Erstellte Räume"
invitations: "Einladen"
noInvitations: "Keine Einladungen"
history: "Verlauf"
noHistory: "Kein Verlauf gefunden"
noRooms: "Keine Räume gefunden"
inviteUser: "Benutzer einladen"
sentInvitations: "Verschickte Einladungen"
join: "Beitreten"
ignore: "Ignorieren"
leave: "Raum verlassen"
members: "Mitglieder"
searchMessages: "Nachrichten suchen"
home: "Startseite"
send: "Senden"
muteThisRoom: "Raum stummschalten"
_emojiPalette:
palettes: "Palette"
enableSyncBetweenDevicesForPalettes: "Synchronisierung der Paletten zwischen Geräten aktivieren"
paletteForMain: "Hauptpalette"
_settings:
driveBanner: "Du kannst den Drive verwalten und konfigurieren, die Auslastung überprüfen und Einstellungen für das Hochladen von Dateien vornehmen."
pluginBanner: "Du kannst die Funktionen des Clients mit Plugins erweitern. Plugins können installiert, individuell konfiguriert und verwaltet werden."
api: "API"
webhook: "Webhook"
serviceConnectionBanner: "Du kannst Zugriffstoken und Webhooks für die Integration mit externen Anwendungen und Diensten verwalten und konfigurieren."
accountData: "Kontodaten"
accountDataBanner: "Export/Import und Verwaltung von Kontodatenarchiven."
muteAndBlockBanner: "Du kannst Einstellungen konfigurieren und verwalten, um Inhalte auszublenden und Aktionen für bestimmte Benutzer zu beschränken."
accessibilityBanner: "Die Clients können personalisiert und für eine optimale Nutzung im Hinblick auf ihre Darstellung und ihr Verhalten eingerichtet werden."
privacyBanner: "Du kannst Einstellungen für die Privatsphäre deines Kontos vornehmen, z. B. inwieweit Inhalte veröffentlicht werden, wie leicht sie zu finden sind und ob Follower genehmigt werden müssen."
securityBanner: "Du kannst Einstellungen für die Kontosicherheit konfigurieren, z. B. Passwörter, Anmeldemethoden, Authentifizierungs-Apps und Passkeys."
_chat:
showSenderName: "Name des Absenders anzeigen"
sendOnEnter: "Eingabetaste sendet Nachricht"
_preferencesProfile:
profileName: "Profilname"
profileNameDescription: "Lege einen Namen fest, der dieses Gerät identifiziert."
profileNameDescription2: "Beispiel: \"Haupt-PC\", \"Smartphone\""
_preferencesBackup:
autoBackup: "Automatische Sicherung"
@ -1384,11 +1353,9 @@ _accountSettings:
requireSigninToViewContentsDescription2: "Der Inhalt wird nicht in URL-Vorschauen (OGP), eingebettet in Webseiten oder auf Servern, die keine Zitate unterstützen, angezeigt."
requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern."
makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar"
makeNotesFollowersOnlyBeforeDescription: "Solange diese Funktion aktiviert ist, sind Notizen, die nach dem eingestellten Datum und der eingestellten Zeit liegen oder die eingestellte Zeit abgelaufen ist, nur für Follower sichtbar. Bei Deaktivierung wird auch der öffentliche Status der Notiz wiederhergestellt."
makeNotesHiddenBefore: "Frühere Notizen privat machen"
makeNotesHiddenBeforeDescription: ""
mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden."
mayNotEffectSomeSituations: "Diese Einschränkungen sind vereinfacht. Sie gelten möglicherweise nicht in allen Situationen, z. B. bei der Anzeige auf einem fremden Server oder während der Moderation."
notesOlderThanSpecifiedDateAndTime: "Notizen vor einem bestimmtem Datum und Uhrzeit"
_abuseUserReport:
forward: "Weiterleiten"
@ -1397,7 +1364,6 @@ _abuseUserReport:
accept: "Akzeptieren"
reject: "Ablehnen"
_delivery:
status: "Auslieferungsstatus"
stop: "Gesperrt"
_type:
none: "Wird veröffentlicht"
@ -1499,9 +1465,7 @@ _initialTutorial:
title: "Du hast das Tutorial abgeschlossen! 🎉"
description: "Die hier beschriebenen Funktionen sind nur ein kleiner Teil dessen, was Misskey zu bieten hat; um mehr darüber zu erfahren, wie du Misskey benutzen kannst, besuche bitte {link}."
_timelineDescription:
home: "In der Startseiten-Chronik kannst du Notizen von Konten sehen, denen du folgst."
local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server."
social: "Die soziale Chronik zeigt Notizen von der Startseite und der lokalen Chronik."
global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern."
_serverRules:
description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
@ -1519,7 +1483,6 @@ _serverSettings:
fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. "
reactionsBufferingDescription: "Wenn diese Option aktiviert ist, kann sie die Leistung beim Erstellen von Reaktionen erheblich verbessern und die Belastung der Datenbank verringern. Allerdings steigt die Speichernutzung von Redis."
inquiryUrl: "Kontakt-URL"
inquiryUrlDescription: "Gib eine URL für das Kontaktformular der Serverbetreiber oder eine Webseite an, die Kontaktinformationen enthält."
openRegistrationWarning: "Das Aktivieren von Registrierungen ist riskant. Es wird empfohlen, sie nur dann zu aktivieren, wenn der Server ständig überwacht wird und im Falle eines Problems sofort reagiert werden kann."
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern."
_accountMigration:
@ -1837,7 +1800,6 @@ _role:
canManageAvatarDecorations: "Profilbilddekorationen verwalten"
driveCapacity: "Drive-Kapazität"
alwaysMarkNsfw: "Dateien immer als NSFW markieren"
canUpdateBioMedia: "Kann ein Profil- oder ein Bannerbild bearbeiten"
pinMax: "Maximale Anzahl an angehefteten Notizen"
antennaMax: "Maximale Anzahl an Antennen"
wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen"
@ -1853,19 +1815,12 @@ _role:
canUseTranslator: "Verwendung des Übersetzers"
avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können"
canImportAntennas: "Importieren von Antennen erlauben"
canImportBlocking: "Importieren von Blockierungen zulassen"
canImportFollowing: "Importieren von Gefolgten zulassen"
canImportMuting: "Importieren von Stummgeschalteten zulassen"
canImportUserLists: "Importieren von Listen erlauben"
canChat: "Chatten erlauben"
_condition:
roleAssignedTo: "Manuellen Rollen zugewiesen"
isLocal: "Lokaler Benutzer"
isRemote: "Benutzer fremder Instanz"
isCat: "Katzen-Benutzer"
isBot: "Bot-Benutzer"
isSuspended: "Gesperrter Benutzer"
isExplorable: "Benutzer, die ihr Konto im \"Erkunden\"-Bereich sichtbar machen"
createdLessThan: "Kontoerstellung liegt weniger als X zurück"
createdMoreThan: "Kontoerstellung liegt mehr als X zurück"
followersLessThanOrEq: "Hat X oder weniger Follower"
@ -2217,9 +2172,7 @@ _permissions:
"read:admin:ad": "Werbung ansehen"
"write:invite-codes": "Einladungscodes erstellen"
"read:invite-codes": "Einladungscodes anzeigen"
"write:report-abuse": "Verstöße melden"
"write:chat": "Chats bedienen"
"read:chat": "Chats durchsuchen"
_auth:
shareAccessTitle: "Verteilung von App-Berechtigungen"
shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?"
@ -2342,7 +2295,6 @@ _profile:
avatarDecorationMax: "Du kannst bis zu {max} Dekorationen hinzufügen."
followedMessage: "Nachricht, wenn dir jemand folgt"
followedMessageDescription: "Du kannst eine kurze Nachricht festlegen, die dem Empfänger angezeigt wird, wenn er dir folgt."
followedMessageDescriptionForLockedAccount: "Wenn Folgeanfragen deine Genehmigung brauchen, wird dies beim Genehmigen einer Anfrage angezeigt."
_exportOrImport:
allNotes: "Alle Notizen"
favoritedNotes: "Als Favorit markierte Notizen"
@ -2400,7 +2352,6 @@ _play:
title: "Titel"
script: "Skript"
summary: "Beschreibung"
visibilityDescription: "Wenn du die Sichtbarkeit auf Privat stellst, wird der Play nicht auf deinem Profil sichtbar sein, aber jeder, der die URL hat, kann ihn trotzdem aufrufen."
_pages:
newPage: "Seite erstellen"
editPage: "Seite bearbeiten"
@ -2432,7 +2383,6 @@ _pages:
eyeCatchingImageSet: "Vorschaubild festlegen"
eyeCatchingImageRemove: "Vorschaubild entfernen"
chooseBlock: "Block hinzufügen"
enterSectionTitle: "Titel des Abschnitts eingeben"
selectType: "Typ auswählen"
contentBlocks: "Inhalt"
inputBlocks: "Eingabe"
@ -2443,8 +2393,6 @@ _pages:
section: "Abschnitt"
image: "Bild"
button: "Knopf"
dynamic: "Dynamische Bausteine"
dynamicDescription: "Dieser Baustein wurde abgeschafft. Bitte verwende von nun an {play}."
note: "Eingebettete Notiz"
_note:
id: "Notiz-ID"
@ -2467,7 +2415,6 @@ _notification:
newNote: "Neue Notiz"
unreadAntennaNote: "Antenne {name}"
roleAssigned: "Rolle zugewiesen"
chatRoomInvitationReceived: "Du wurdest in einen Chatraum eingeladen"
emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert"
achievementEarned: "Errungenschaft freigeschaltet"
testNotification: "Testbenachrichtigung"

12
locales/index.d.ts vendored
View File

@ -5472,14 +5472,6 @@ export interface Locale extends ILocale {
*
*/
"deleteRoom": string;
/**
*
*/
"chatNotAvailableForThisAccountOrServer": string;
/**
* 使
*/
"chatNotAvailableInOtherAccount": string;
/**
*
*/
@ -5634,10 +5626,6 @@ export interface Locale extends ILocale {
*
*/
"makeEveryTextElementsSelectable_description": string;
/**
*
*/
"useStickyIcons": string;
/**
*
*/

View File

@ -1365,8 +1365,6 @@ _chat:
newline: "改行"
muteThisRoom: "このルームをミュート"
deleteRoom: "ルームを削除"
chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。"
chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。"
cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
chatWithThisUser: "チャットする"
@ -1409,7 +1407,6 @@ _settings:
timelineAndNote: "タイムラインとノート"
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
useStickyIcons: "アイコンをスクロールに追従させる"
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
ifOn: "オンのとき"
ifOff: "オフのとき"

View File

@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2025.3.2-beta.16",
"version": "2025.3.2-beta.14",
"codename": "nasubi",
"repository": {
"type": "git",

View File

@ -463,7 +463,6 @@ export class WebhookTestService {
followersVisibility: 'public',
followingVisibility: 'public',
chatScope: 'mutual',
canChat: true,
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,

View File

@ -557,7 +557,6 @@ export class UserEntityService implements OnModuleInit {
followersVisibility: profile!.followersVisibility,
followingVisibility: profile!.followingVisibility,
chatScope: user.chatScope,
canChat: this.roleService.getUserPolicies(user.id).then(r => r.canChat),
roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({
id: role.id,
name: role.name,

View File

@ -363,10 +363,6 @@ export const packedUserDetailedNotMeOnlySchema = {
nullable: false, optional: false,
enum: ['everyone', 'following', 'followers', 'mutual', 'none'],
},
canChat: {
type: 'boolean',
nullable: false, optional: false,
},
roles: {
type: 'array',
nullable: false, optional: false,

View File

@ -84,7 +84,6 @@ describe('ユーザー', () => {
followingVisibility: user.followingVisibility,
followersVisibility: user.followersVisibility,
chatScope: user.chatScope,
canChat: user.canChat,
roles: user.roles,
memo: user.memo,
});
@ -347,7 +346,6 @@ describe('ユーザー', () => {
assert.strictEqual(response.followingVisibility, 'public');
assert.strictEqual(response.followersVisibility, 'public');
assert.strictEqual(response.chatScope, 'mutual');
assert.strictEqual(response.canChat, true);
assert.deepStrictEqual(response.roles, []);
assert.strictEqual(response.memo, null);

View File

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
import type { ComputedRef } from 'vue';
import { misskeyApi } from '@/misskey-api.js';
import { i18n } from '@/i18n.js';
@ -252,7 +252,7 @@ const fetchMore = async (): Promise<void> => {
return nextTick(() => {
if (scrollableElement.value) {
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
} else {
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
}

View File

@ -93,7 +93,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
return removeListener;
}
export function scrollInContainer(el: HTMLElement, options: ScrollToOptions | undefined) {
export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) {
const container = getScrollContainer(el);
if (container == null) {
window.scroll(options);
@ -108,7 +108,7 @@ export function scrollInContainer(el: HTMLElement, options: ScrollToOptions | un
* @param options Scroll options
*/
export function scrollToTop(el: HTMLElement, options: { behavior?: ScrollBehavior; } = {}) {
scrollInContainer(el, { top: 0, ...options });
scroll(el, { top: 0, ...options });
}
/**

View File

@ -58,12 +58,7 @@ export default [
// location ... window.locationと衝突 or 紛らわしい
// document ... window.documentと衝突 or 紛らわしい
// history ... window.historyと衝突 or 紛らわしい
// scroll ... window.scrollと衝突 or 紛らわしい
// setTimeout ... window.setTimeoutと衝突 or 紛らわしい
// setInterval ... window.setIntervalと衝突 or 紛らわしい
// clearTimeout ... window.clearTimeoutと衝突 or 紛らわしい
// clearInterval ... window.clearIntervalと衝突 or 紛らわしい
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history', 'scroll', 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval'],
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
'no-restricted-globals': [
'error',
{
@ -90,26 +85,6 @@ export default [
'name': 'history',
'message': 'Use `window.history`.',
},
{
'name': 'scroll',
'message': 'Use `window.scroll`.',
},
{
'name': 'setTimeout',
'message': 'Use `window.setTimeout`.',
},
{
'name': 'setInterval',
'message': 'Use `window.setInterval`.',
},
{
'name': 'clearTimeout',
'message': 'Use `window.clearTimeout`.',
},
{
'name': 'clearInterval',
'message': 'Use `window.clearInterval`.',
},
{
'name': 'name',
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',

View File

@ -29,7 +29,7 @@ import { fetchCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js';
import { $i } from '@/i.js';
export async function common(createVue: () => Promise<App<Element>>) {
export async function common(createVue: () => App<Element>) {
console.info(`Misskey v${version}`);
if (_DEV_) {
@ -263,7 +263,7 @@ export async function common(createVue: () => Promise<App<Element>>) {
});
});
const app = await createVue();
const app = createVue();
if (_DEV_) {
app.config.performance = true;

View File

@ -32,7 +32,7 @@ import { signout } from '@/signout.js';
import { migrateOldSettings } from '@/pref-migrate.js';
export async function mainBoot() {
const { isClientUpdated, lastVersion } = await common(async () => {
const { isClientUpdated, lastVersion } = await common(() => {
let uiStyle = ui;
const searchParams = new URLSearchParams(window.location.search);
@ -46,19 +46,19 @@ export async function mainBoot() {
let rootComponent: Component;
switch (uiStyle) {
case 'zen':
rootComponent = await import('@/ui/zen.vue').then(x => x.default);
rootComponent = defineAsyncComponent(() => import('@/ui/zen.vue'));
break;
case 'deck':
rootComponent = await import('@/ui/deck.vue').then(x => x.default);
rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue'));
break;
case 'visitor':
rootComponent = await import('@/ui/visitor.vue').then(x => x.default);
rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue'));
break;
case 'classic':
rootComponent = await import('@/ui/classic.vue').then(x => x.default);
rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue'));
break;
default:
rootComponent = await import('@/ui/universal.vue').then(x => x.default);
rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue'));
break;
}

View File

@ -6,10 +6,11 @@
import { createApp, defineAsyncComponent } from 'vue';
import { common } from './common.js';
import { emojiPicker } from '@/utility/emoji-picker.js';
import UiMinimum from '@/ui/minimum.vue';
export async function subBoot() {
const { isClientUpdated } = await common(async () => createApp(UiMinimum));
const { isClientUpdated } = await common(() => createApp(
defineAsyncComponent(() => import('@/ui/minimum.vue')),
));
emojiPicker.init();
}

View File

@ -2,18 +2,20 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
import { channel } from '../../.storybook/fakes.js';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkChannelFollowButton from './MkChannelFollowButton.vue';
import type { StoryObj } from '@storybook/vue3';
import { i18n } from '@/i18n.js';
function sleep(ms: number) {
return new Promise(resolve => window.setTimeout(resolve, ms));
return new Promise(resolve => setTimeout(resolve, ms));
}
export const Default = {

View File

@ -2,16 +2,18 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
/* eslint-disable import/no-default-export */
import type { StoryObj } from '@storybook/vue3';
import { HttpResponse, http } from 'msw';
import { action } from '@storybook/addon-actions';
import { expect, userEvent, within } from '@storybook/test';
import { commonHandlers } from '../../.storybook/mocks.js';
import MkClickerGame from './MkClickerGame.vue';
import type { StoryObj } from '@storybook/vue3';
function sleep(ms: number) {
return new Promise(resolve => window.setTimeout(resolve, ms));
return new Promise(resolve => setTimeout(resolve, ms));
}
export const Default = {

View File

@ -79,7 +79,7 @@ function opening() {
picker.value?.focus();
//
window.setTimeout(() => {
setTimeout(() => {
picker.value?.focus();
}, 10);
}

View File

@ -100,7 +100,7 @@ function touchMove(event: TouchEvent) {
pullDistance.value = 0;
isSwiping.value = false;
window.setTimeout(() => {
setTimeout(() => {
isSwipingForClass.value = false;
}, 400);

View File

@ -339,7 +339,7 @@ const bufferedDataRatio = computed(() => {
// MediaControl Events
function onMouseOver() {
if (controlStateTimer) {
window.clearTimeout(controlStateTimer);
clearTimeout(controlStateTimer);
}
isHoverring.value = true;
}

View File

@ -50,7 +50,6 @@ import { deviceKind } from '@/utility/device-kind.js';
import { focusTrap } from '@/utility/focus-trap.js';
import { focusParent } from '@/utility/focus.js';
import { prefer } from '@/preferences.js';
import { DI } from '@/di.js';
function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === 'BODY') return null;
@ -95,7 +94,7 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
provide(DI.inModal, true);
provide('modal', true);
const maxHeight = ref<number>();
const fixed = ref(false);

View File

@ -14,6 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
>
<MkNoteSub v-if="appearNote.reply && !renoteCollapsed" :note="appearNote.reply" :class="$style.replyTo"/>
<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
<div v-if="isRenote" :class="$style.renote">
<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
@ -45,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
<div :class="$style.main">
<MkNoteHeader :note="appearNote" :mini="true"/>
<MkInstanceTicker v-if="showTicker" :host="appearNote.user.host" :instance="appearNote.user.instance"/>
@ -850,12 +852,9 @@ function emitUpdReaction(emoji: string, delta: number) {
margin: 0 14px 0 0;
width: 58px;
height: 58px;
&.useSticky {
position: sticky !important;
top: calc(22px + var(--MI-stickyTop, 0px));
left: 0;
}
position: sticky !important;
top: calc(22px + var(--MI-stickyTop, 0px));
left: 0;
}
.main {
@ -1042,10 +1041,7 @@ function emitUpdReaction(emoji: string, delta: number) {
margin: 0 10px 0 0;
width: 46px;
height: 46px;
&.useSticky {
top: calc(14px + var(--MI-stickyTop, 0px));
}
top: calc(14px + var(--MI-stickyTop, 0px));
}
}

View File

@ -13,26 +13,32 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<template #default="{ items: notes }">
<div :class="[$style.root, { [$style.noGap]: noGap, '_gaps': !noGap }]">
<template v-for="(note, i) in notes" :key="note.id">
<MkNote :class="$style.note" :note="note" :withHardMute="true"/>
<div v-if="note._shouldInsertAd_" :class="$style.ad">
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
</div>
</template>
<div :class="[$style.root, { [$style.noGap]: noGap }]">
<MkDateSeparatedList
ref="notes"
v-slot="{ item: note }"
:items="notes"
:direction="pagination.reversed ? 'up' : 'down'"
:reversed="pagination.reversed"
:noGap="noGap"
:ad="true"
:class="$style.notes"
>
<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
</MkDateSeparatedList>
</div>
</template>
</MkPagination>
</template>
<script lang="ts" setup>
import { useTemplateRef, TransitionGroup } from 'vue';
import { useTemplateRef } from 'vue';
import type { Paging } from '@/components/MkPagination.vue';
import MkNote from '@/components/MkNote.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkPagination from '@/components/MkPagination.vue';
import { i18n } from '@/i18n.js';
import { infoImageUrl } from '@/instance.js';
import { prefer } from '@/preferences.js';
const props = defineProps<{
pagination: Paging;
@ -49,29 +55,20 @@ defineExpose({
<style lang="scss" module>
.root {
container-type: inline-size;
&.noGap {
background: var(--MI_THEME-panel);
.note {
border-bottom: solid 0.5px var(--MI_THEME-divider);
}
.ad {
padding: 8px;
background-size: auto auto;
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px);
border-bottom: solid 0.5px var(--MI_THEME-divider);
> .notes {
background: var(--MI_THEME-panel);
}
}
&:not(.noGap) {
background: var(--MI_THEME-bg);
> .notes {
background: var(--MI_THEME-bg);
.note {
background: var(--MI_THEME-panel);
border-radius: var(--MI-radius);
.note {
background: var(--MI_THEME-panel);
border-radius: var(--MI-radius);
}
}
}
}

View File

@ -14,12 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<template #default="{ items: notifications }">
<div :class="$style.notifications">
<template v-for="(notification, i) in notifications" :key="notification.id">
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :class="$style.item" :note="notification.note" :withHardMute="true"/>
<XNotification v-else :class="$style.item" :notification="notification" :withTime="true" :full="true"/>
</template>
</div>
<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
</MkDateSeparatedList>
</template>
</MkPagination>
</MkPullToRefresh>
@ -31,6 +29,7 @@ import * as Misskey from 'misskey-js';
import type { notificationTypes } from '@@/js/const.js';
import MkPagination from '@/components/MkPagination.vue';
import XNotification from '@/components/MkNotification.vue';
import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
import MkNote from '@/components/MkNote.vue';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
@ -85,22 +84,28 @@ onMounted(() => {
connection.on('notificationFlushed', reload);
});
onActivated(() => {
pagingComponent.value?.reload();
connection = useStream().useChannel('main');
connection.on('notification', onNotification);
connection.on('notificationFlushed', reload);
});
onUnmounted(() => {
if (connection) connection.dispose();
});
onDeactivated(() => {
if (connection) connection.dispose();
});
defineExpose({
reload,
});
</script>
<style lang="scss" module>
.notifications {
container-type: inline-size;
.list {
background: var(--MI_THEME-panel);
}
.item {
border-bottom: solid 0.5px var(--MI_THEME-divider);
}
</style>

View File

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkError v-else-if="error" @retry="init()"/>
<div v-else-if="empty" key="_empty_">
<div v-else-if="empty" key="_empty_" class="empty">
<slot name="empty">
<div class="_fullinfo">
<img :src="infoImageUrl" draggable="false"/>
@ -29,14 +29,14 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMoreAhead : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMoreAhead">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else/>
<MkLoading v-else class="loading"/>
</div>
<slot :items="Array.from(items.values())" :fetching="fetching || moreFetching"></slot>
<div v-show="!pagination.reversed && more" key="_more_">
<MkButton v-if="!moreFetching" v-appear="(enableInfiniteScroll && !props.disableAutoLoad) ? appearFetchMore : null" :class="$style.more" :wait="moreFetching" primary rounded @click="fetchMore">
{{ i18n.ts.loadMore }}
</MkButton>
<MkLoading v-else/>
<MkLoading v-else class="loading"/>
</div>
</div>
</Transition>
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, isTailVisible } from '@@/js/scroll.js';
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isTailVisible } from '@@/js/scroll.js';
import type { ComputedRef } from 'vue';
import type { MisskeyEntity } from '@/types/date-separated-list.js';
import { misskeyApi } from '@/utility/misskey-api.js';
@ -258,7 +258,7 @@ const fetchMore = async (): Promise<void> => {
return nextTick(() => {
if (scrollableElement.value) {
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
} else {
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
}
@ -357,7 +357,7 @@ watch(visibility, () => {
BACKGROUND_PAUSE_WAIT_SEC * 1000);
} else { // 'visible'
if (timerForSetPause) {
window.clearTimeout(timerForSetPause);
clearTimeout(timerForSetPause);
timerForSetPause = null;
} else {
isPausingUpdate = false;
@ -453,11 +453,11 @@ onBeforeMount(() => {
init().then(() => {
if (props.pagination.reversed) {
nextTick(() => {
window.setTimeout(toBottom, 800);
setTimeout(toBottom, 800);
// scrollToBottommoreFetching
// more = true
window.setTimeout(() => {
setTimeout(() => {
moreFetching.value = false;
}, 2000);
});
@ -467,11 +467,11 @@ onBeforeMount(() => {
onBeforeUnmount(() => {
if (timerForSetPause) {
window.clearTimeout(timerForSetPause);
clearTimeout(timerForSetPause);
timerForSetPause = null;
}
if (preventAppearFetchMoreTimer.value) {
window.clearTimeout(preventAppearFetchMoreTimer.value);
clearTimeout(preventAppearFetchMoreTimer.value);
preventAppearFetchMoreTimer.value = null;
}
scrollObserver.value?.disconnect();

View File

@ -140,7 +140,7 @@ import { DI } from '@/di.js';
const $i = ensureSignin();
const modal = inject(DI.inModal, false);
const modal = inject('modal');
const props = withDefaults(defineProps<PostFormProps & {
fixed?: boolean;

View File

@ -16,8 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</div>
</div>
<slot/>
<div :class="{ [$style.slotClip]: isPullStart }">
<slot/>
</div>
</div>
</template>
@ -81,11 +82,11 @@ function moveBySystem(to: number): Promise<void> {
return;
}
const startTime = Date.now();
let intervalId = window.setInterval(() => {
let intervalId = setInterval(() => {
const time = Date.now() - startTime;
if (time > RELEASE_TRANSITION_DURATION) {
pullDistance.value = to;
window.clearInterval(intervalId);
clearInterval(intervalId);
r();
return;
}
@ -260,4 +261,8 @@ defineExpose({
margin: 5px 0;
}
}
.slotClip {
overflow-y: clip;
}
</style>

View File

@ -25,7 +25,7 @@ const props = defineProps<{
menuReaction?: boolean;
}>();
const react = inject(DI.mfmEmojiReactCallback, null);
const react = inject(DI.mfmEmojiReactCallback);
const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;

View File

@ -2,10 +2,11 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { waitFor } from '@storybook/test';
import MkPageHeader from './MkPageHeader.vue';
import type { StoryObj } from '@storybook/vue3';
import MkPageHeader from './MkPageHeader.vue';
export const Empty = {
render(args) {
return {
@ -28,7 +29,7 @@ export const Empty = {
};
},
async play() {
const wait = new Promise((resolve) => window.setTimeout(resolve, 800));
const wait = new Promise((resolve) => setTimeout(resolve, 800));
await waitFor(async () => await wait);
},
args: {

View File

@ -133,7 +133,7 @@ async function enter(el: Element) {
entering = false;
});
window.setTimeout(renderTab, 170);
setTimeout(renderTab, 170);
}
function afterEnter(el: Element) {

View File

@ -69,6 +69,7 @@ const emit = defineEmits<{
}>();
const viewId = inject(DI.viewId);
const viewTransitionName = computed(() => `${viewId}---pageHeader`);
const injectedPageMetadata = inject(DI.pageMetadata);
const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMetadata.value);
@ -129,6 +130,7 @@ onUnmounted(() => {
backdrop-filter: var(--MI-blur, blur(15px));
border-bottom: solid 0.5px var(--MI_THEME-divider);
width: 100%;
view-transition-name: v-bind(viewTransitionName);
}
.upper,

View File

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
<div :class="$style.body">
@ -16,8 +16,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import { scrollInContainer } from '@@/js/scroll.js';
import type { PageHeaderItem } from '@/types/page-header.js';
import type { Tab } from './MkPageHeader.tabs.vue';
@ -33,13 +31,6 @@ const props = withDefaults(defineProps<{
});
const tab = defineModel<string>('tab');
const rootEl = useTemplateRef('rootEl');
defineExpose({
scrollToTop: () => {
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'smooth' });
},
});
</script>
<style lang="scss" module>

View File

@ -44,9 +44,7 @@ provide(DI.routerCurrentDepth, currentDepth + 1);
const rootEl = useTemplateRef('rootEl');
onMounted(() => {
if (prefer.s.animation) {
rootEl.value.style.viewTransitionName = viewId; // view-transition-namecss var使
}
rootEl.value.style.viewTransitionName = viewId; // view-transition-namecss var使
});
// view-transition-new<pt-name-selector>css var使v-bind

View File

@ -50,12 +50,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<script setup lang="ts">
import { computed, onMounted, ref, toRefs, watch } from 'vue';
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
import type { GridColumn } from '@/components/grid/column.js';
import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
import type { MenuItem } from '@/types/menu.js';
import { GridEventEmitter } from '@/components/grid/grid.js';
import MkDataRow from '@/components/grid/MkDataRow.vue';
import MkHeaderRow from '@/components/grid/MkHeaderRow.vue';
@ -74,6 +68,13 @@ import { createColumn } from '@/components/grid/column.js';
import { createRow, defaultGridRowSetting, resetRow } from '@/components/grid/row.js';
import { handleKeyEvent } from '@/utility/key-event.js';
import type { DataSource, GridSetting, GridState, Size } from '@/components/grid/grid.js';
import type { CellAddress, CellValue, GridCell } from '@/components/grid/cell.js';
import type { GridContext, GridEvent } from '@/components/grid/grid-event.js';
import type { GridColumn } from '@/components/grid/column.js';
import type { GridRow, GridRowSetting } from '@/components/grid/row.js';
import type { MenuItem } from '@/types/menu.js';
type RowHolder = {
row: GridRow,
cells: GridCell[],
@ -129,7 +130,7 @@ const bus = new GridEventEmitter();
*
* @see {@link onResize}
*/
const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries)));
const resizeObserver = new ResizeObserver((entries) => setTimeout(() => onResize(entries)));
const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
/**

View File

@ -15,5 +15,4 @@ export const DI = {
currentStickyTop: Symbol() as InjectionKey<Ref<number>>,
currentStickyBottom: Symbol() as InjectionKey<Ref<number>>,
mfmEmojiReactCallback: Symbol() as InjectionKey<(emoji: string) => void>,
inModal: Symbol() as InjectionKey<boolean>,
};

View File

@ -13,8 +13,6 @@ import type { ComponentProps as CP } from 'vue-component-type-helpers';
import type { Form, GetFormResultType } from '@/utility/form.js';
import type { MenuItem } from '@/types/menu.js';
import type { PostFormProps } from '@/types/post-form.js';
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { prefer } from '@/preferences.js';
import { i18n } from '@/i18n.js';
@ -25,6 +23,8 @@ import MkToast from '@/components/MkToast.vue';
import MkDialog from '@/components/MkDialog.vue';
import MkPopupMenu from '@/components/MkPopupMenu.vue';
import MkContextMenu from '@/components/MkContextMenu.vue';
import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { pleaseLogin } from '@/utility/please-login.js';
import { showMovedDialog } from '@/utility/show-moved-dialog.js';

View File

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import { scrollInContainer } from '@@/js/scroll.js';
import { scroll } from '@@/js/scroll.js';
import MkTimeline from '@/components/MkTimeline.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
@ -49,7 +49,7 @@ function queueUpdated(q) {
}
function top() {
scrollInContainer(rootEl.value, { top: 0 });
scroll(rootEl.value, { top: 0 });
}
async function timetravel() {

View File

@ -242,10 +242,6 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
font-size: 80%;
}
.fukidashi {
text-align: left;
}
.content {
overflow: clip;
overflow-wrap: break-word;

View File

@ -5,9 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div class="_gaps">
<MkButton v-if="$i.policies.canChat" primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
<MkInfo v-else>{{ i18n.ts._chat.chatNotAvailableForThisAccountOrServer }}</MkInfo>
<MkButton primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
@ -80,7 +78,6 @@ import * as os from '@/os.js';
import { updateCurrentAccountPartial } from '@/accounts.js';
import MkInput from '@/components/MkInput.vue';
import MkFoldableSection from '@/components/MkFoldableSection.vue';
import MkInfo from '@/components/MkInfo.vue';
const $i = ensureSignin();

View File

@ -41,12 +41,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<XMessage v-for="message in messages.toReversed()" :key="message.id" :message="message"/>
</TransitionGroup>
</div>
<div v-if="user && (!user.canChat || user.host !== null)">
<MkInfo warn>{{ i18n.ts._chat.chatNotAvailableInOtherAccount }}</MkInfo>
</div>
<MkInfo v-if="!$i.policies.canChat" warn>{{ i18n.ts._chat.chatNotAvailableForThisAccountOrServer }}</MkInfo>
</MkSpacer>
<MkSpacer v-else-if="tab === 'search'" :contentMax="700">
@ -99,7 +93,6 @@ import { prefer } from '@/preferences.js';
import MkButton from '@/components/MkButton.vue';
import { useRouter } from '@/router.js';
import { useMutationObserver } from '@/use/use-mutation-observer.js';
import MkInfo from '@/components/MkInfo.vue';
const $i = ensureSignin();
const router = useRouter();

View File

@ -6,15 +6,17 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
<MkSpacer :contentMax="800">
<div v-if="tab === 'all'">
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div>
<div v-else-if="tab === 'mentions'">
<MkNotes :pagination="mentionsPagination"/>
</div>
<div v-else-if="tab === 'directNotes'">
<MkNotes :pagination="directNotesPagination"/>
</div>
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<div v-if="tab === 'all'">
<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
</div>
<div v-else-if="tab === 'mentions'">
<MkNotes :pagination="mentionsPagination"/>
</div>
<div v-else-if="tab === 'directNotes'">
<MkNotes :pagination="directNotesPagination"/>
</div>
</MkHorizontalSwipe>
</MkSpacer>
</PageWithHeader>
</template>

View File

@ -145,13 +145,13 @@ SPDX-License-Identifier: AGPL-3.0-only
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
import * as Misskey from 'misskey-js';
import * as Reversi from 'misskey-reversi';
import { useInterval } from '@@/js/use-interval.js';
import { url } from '@@/js/config.js';
import MkButton from '@/components/MkButton.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import { deepClone } from '@/utility/clone.js';
import { useInterval } from '@@/js/use-interval.js';
import { ensureSignin } from '@/i.js';
import { url } from '@@/js/config.js';
import { i18n } from '@/i18n.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { userPage } from '@/filters/user.js';
@ -301,7 +301,7 @@ if (!props.game.isEnded) {
if (iAmPlayer.value) {
if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) {
props.connection!.send('claimTimeIsUp', {});
props.connection!.send('claimTimeIsUp', {});
}
}
}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
@ -424,7 +424,7 @@ function autoplay() {
const tick = () => {
const log = logs[i];
const time = log.time - previousLog.time;
window.setTimeout(() => {
setTimeout(() => {
i++;
logPos.value++;
previousLog = log;

View File

@ -42,6 +42,22 @@ SPDX-License-Identifier: AGPL-3.0-only
</SearchMarker>
<div class="_gaps_s">
<SearchMarker :keywords="['blur']">
<MkPreferenceContainer k="useBlurEffect">
<MkSwitch v-model="useBlurEffect">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['blur', 'modal']">
<MkPreferenceContainer k="useBlurEffectForModal">
<MkSwitch v-model="useBlurEffectForModal">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
<MkPreferenceContainer k="showAvatarDecorations">
<MkSwitch v-model="showAvatarDecorations">
@ -379,6 +395,40 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['datasaver']">
<MkFolder>
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
<template #icon><i class="ti ti-antenna-bars-3"></i></template>
<div class="_gaps_m">
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
<div class="_buttons">
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
</div>
<div class="_gaps_m">
<MkSwitch v-model="dataSaver.media">
{{ i18n.ts._dataSaver._media.title }}
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.avatar">
{{ i18n.ts._dataSaver._avatar.title }}
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.urlPreview">
{{ i18n.ts._dataSaver._urlPreview.title }}
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.code">
{{ i18n.ts._dataSaver._code.title }}
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
</MkSwitch>
</div>
</div>
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['chat', 'messaging']">
<MkFolder>
<template #label><SearchLabel>{{ i18n.ts.chat }}</SearchLabel></template>
@ -418,76 +468,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['performance']">
<MkFolder>
<template #label><SearchLabel>{{ i18n.ts.performance }}</SearchLabel></template>
<template #icon><i class="ti ti-battery-vertical-eco"></i></template>
<div class="_gaps_s">
<SearchMarker :keywords="['blur']">
<MkPreferenceContainer k="useBlurEffect">
<MkSwitch v-model="useBlurEffect">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffect }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['blur', 'modal']">
<MkPreferenceContainer k="useBlurEffectForModal">
<MkSwitch v-model="useBlurEffectForModal">
<template #label><SearchLabel>{{ i18n.ts.useBlurEffectForModal }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['sticky']">
<MkPreferenceContainer k="useStickyIcons">
<MkSwitch v-model="useStickyIcons">
<template #label><SearchLabel>{{ i18n.ts._settings.useStickyIcons }}</SearchLabel></template>
<template #caption><SearchLabel>{{ i18n.ts.turnOffToImprovePerformance }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
</div>
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['datasaver']">
<MkFolder>
<template #label><SearchLabel>{{ i18n.ts.dataSaver }}</SearchLabel></template>
<template #icon><i class="ti ti-antenna-bars-3"></i></template>
<div class="_gaps_m">
<MkInfo>{{ i18n.ts.reloadRequiredToApplySettings }}</MkInfo>
<div class="_buttons">
<MkButton inline @click="enableAllDataSaver">{{ i18n.ts.enableAll }}</MkButton>
<MkButton inline @click="disableAllDataSaver">{{ i18n.ts.disableAll }}</MkButton>
</div>
<div class="_gaps_m">
<MkSwitch v-model="dataSaver.media">
{{ i18n.ts._dataSaver._media.title }}
<template #caption>{{ i18n.ts._dataSaver._media.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.avatar">
{{ i18n.ts._dataSaver._avatar.title }}
<template #caption>{{ i18n.ts._dataSaver._avatar.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.urlPreview">
{{ i18n.ts._dataSaver._urlPreview.title }}
<template #caption>{{ i18n.ts._dataSaver._urlPreview.description }}</template>
</MkSwitch>
<MkSwitch v-model="dataSaver.code">
{{ i18n.ts._dataSaver._code.title }}
<template #caption>{{ i18n.ts._dataSaver._code.description }}</template>
</MkSwitch>
</div>
</div>
</MkFolder>
</SearchMarker>
<SearchMarker :keywords="['other']">
<MkFolder>
<template #label><SearchLabel>{{ i18n.ts.other }}</SearchLabel></template>
@ -670,7 +650,6 @@ const useBlurEffect = prefer.model('useBlurEffect');
const defaultFollowWithReplies = prefer.model('defaultFollowWithReplies');
const chatShowSenderName = prefer.model('chat.showSenderName');
const chatSendOnEnter = prefer.model('chat.sendOnEnter');
const useStickyIcons = prefer.model('useStickyIcons');
watch(lang, () => {
miLocalStorage.setItem('lang', lang.value as string);
@ -699,7 +678,6 @@ watch([
highlightSensitiveMedia,
enableSeasonalScreenEffect,
chatShowSenderName,
useStickyIcons,
], async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
});
@ -808,7 +786,7 @@ function testNotification(): void {
smashCount = 0;
}
if (smashTimer) {
window.clearTimeout(smashTimer);
clearTimeout(smashTimer);
}
smashTimer = window.setTimeout(() => {
smashCount = 0;

View File

@ -4,43 +4,47 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div ref="rootEl" class="_pageScrollable">
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"/></template>
<MkSpacer :contentMax="800">
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }}
</MkInfo>
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="_panel" fixed style="margin-bottom: var(--MI-margin);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<MkTimeline
ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
:class="$style.tl"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"
:withReplies="withReplies"
:withSensitive="withSensitive"
:onlyFiles="onlyFiles"
:sound="true"
@queue="queueUpdated"
/>
</MkSpacer>
</MkStickyContainer>
</div>
<PageWithHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true">
<MkSpacer :contentMax="800">
<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
<div ref="rootEl">
<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
{{ i18n.ts._timelineDescription[src] }}
</MkInfo>
<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
<div :class="$style.tl">
<MkTimeline
ref="tlComponent"
:key="src + withRenotes + withReplies + onlyFiles + withSensitive"
:src="src.split(':')[0]"
:list="src.split(':')[1]"
:withRenotes="withRenotes"
:withReplies="withReplies"
:withSensitive="withSensitive"
:onlyFiles="onlyFiles"
:sound="true"
@queue="queueUpdated"
/>
</div>
</div>
</MkHorizontalSwipe>
</MkSpacer>
</PageWithHeader>
</template>
<script lang="ts" setup>
import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
import { scrollInContainer } from '@@/js/scroll.js';
import { scroll } from '@@/js/scroll.js';
import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
import type { MenuItem } from '@/types/menu.js';
import type { BasicTimelineType } from '@/timelines.js';
import MkTimeline from '@/components/MkTimeline.vue';
import MkInfo from '@/components/MkInfo.vue';
import MkPostForm from '@/components/MkPostForm.vue';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
import { store } from '@/store.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
@ -129,7 +133,7 @@ function queueUpdated(q: number): void {
}
function top(): void {
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'instant' });
if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' });
}
async function chooseList(ev: MouseEvent): Promise<void> {

View File

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { computed, watch, ref, useTemplateRef } from 'vue';
import * as Misskey from 'misskey-js';
import { scrollInContainer } from '@@/js/scroll.js';
import { scroll } from '@@/js/scroll.js';
import MkTimeline from '@/components/MkTimeline.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import { definePage } from '@/page.js';
@ -54,7 +54,7 @@ function queueUpdated(q) {
}
function top() {
scrollInContainer(rootEl.value, { top: 0 });
scroll(rootEl.value, { top: 0 });
}
function settings() {

View File

@ -198,9 +198,6 @@ export const PREF_DEF = {
useBlurEffect: {
default: DEFAULT_DEVICE_KIND === 'desktop',
},
useStickyIcons: {
default: true,
},
showFixedPostForm: {
default: false,
},

View File

@ -178,14 +178,6 @@ rt {
overflow: clip;
overflow-y: scroll;
overscroll-behavior: contain;
/*
理屈は知らないけどここでbackgroundを設定しておかないと
スクロールコンテナーが少なくともChromeにおいて
main thread scrolling になってしまいパフォーマンスが(多分)落ちる
backgroundが透明だと裏側を描画しないといけなくなるとかそういう理由かもしれない
*/
background: var(--MI_THEME-bg);
}
._pageScrollableReversed {

View File

@ -226,17 +226,13 @@ html,
body {
width: 100%;
height: 100%;
overflow: clip;
position: fixed;
top: 0;
left: 0;
overscroll-behavior: none;
}
body {
/* NOTE: htmlにも overflow: clip を設定したいところだが、設定すると何故か少なくともChromeで html が main thread scrolling になりパフォーマンスが(多分)落ちる */
overflow: clip;
}
#misskey_app {
width: 100%;
height: 100%;

View File

@ -377,7 +377,7 @@ function onDrop(ev) {
font-size: 0.9em;
color: var(--MI_THEME-panelHeaderFg);
background: var(--MI_THEME-panelHeaderBg);
box-shadow: 0 0.5px 0 0 var(--MI_THEME-panelHeaderDivider);
box-shadow: 0 1px 0 0 var(--MI_THEME-panelHeaderDivider);
cursor: pointer;
user-select: none;
}

View File

@ -5,30 +5,119 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<div :class="$style.root">
<XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/>
<div :class="$style.contents" @contextmenu.stop="onContextmenu">
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :class="$style.content"/>
<RouterView v-else :class="$style.content"/>
<div>
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
<XAnnouncements v-if="$i"/>
<XStatusBars :class="$style.statusbars"/>
</div>
<div :class="$style.content">
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']"/>
<RouterView v-else/>
</div>
<div v-if="isMobile" ref="navFooter" :class="$style.nav">
<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button>
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button>
<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')">
<i :class="$style.navButtonIcon" class="ti ti-bell"></i>
<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink">
<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
</span>
</button>
<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ti ti-apps"></i></button>
<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ti ti-pencil"></i></button>
</div>
</div>
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/>
</div>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
>
<div
v-if="drawerMenuShowing"
:class="$style.menuDrawerBg"
class="_modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_menuDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_menuDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_menuDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_menuDrawer_leaveTo : ''"
>
<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
<XDrawerMenu/>
</div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
>
<div
v-if="widgetsShowing"
:class="$style.widgetsDrawerBg"
class="_modalBg"
@click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false"
></div>
</Transition>
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
:enterFromClass="prefer.s.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
:leaveToClass="prefer.s.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
>
<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
<XWidgets/>
</div>
</Transition>
<XCommon/>
</div>
</template>
<script lang="ts" setup>
import { provide, onMounted, computed, ref } from 'vue';
import { defineAsyncComponent, provide, onMounted, computed, ref, watch, useTemplateRef } from 'vue';
import { instanceName } from '@@/js/config.js';
import { isLink } from '@@/js/is-link.js';
import XCommon from './_common_/common.vue';
import type { Ref } from 'vue';
import type { PageMetadata } from '@/page.js';
import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
import * as os from '@/os.js';
import { navbarItemDef } from '@/navbar.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/i.js';
import { provideMetadataReceiver, provideReactiveMetadata } from '@/page.js';
import { deviceKind } from '@/utility/device-kind.js';
import { miLocalStorage } from '@/local-storage.js';
import { mainRouter } from '@/router.js';
import { prefer } from '@/preferences.js';
import { shouldSuggestRestoreBackup } from '@/preferences/utility.js';
import { DI } from '@/di.js';
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
const XPreferenceRestore = defineAsyncComponent(() => import('@/ui/_common_/PreferenceRestore.vue'));
const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
const DESKTOP_THRESHOLD = 1100;
@ -42,6 +131,8 @@ window.addEventListener('resize', () => {
});
const pageMetadata = ref<null | PageMetadata>(null);
const widgetsShowing = ref(false);
const navFooter = useTemplateRef('navFooter');
provide(DI.router, mainRouter);
provideMetadataReceiver((metadataGetter) => {
@ -57,6 +148,14 @@ provideMetadataReceiver((metadataGetter) => {
});
provideReactiveMetadata(pageMetadata);
const menuIndicated = computed(() => {
for (const def in navbarItemDef) {
if (def === 'notifications') continue; //
if (navbarItemDef[def].indicated) return true;
}
return false;
});
const drawerMenuShowing = ref(false);
mainRouter.on('change', () => {
@ -96,6 +195,20 @@ const onContextmenu = (ev) => {
},
}], ev);
};
const navFooterHeight = ref(0);
watch(navFooter, () => {
if (navFooter.value) {
navFooterHeight.value = navFooter.value.offsetHeight;
window.document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)');
} else {
navFooterHeight.value = 0;
window.document.body.style.setProperty('--MI-minBottomSpacing', '0px');
}
}, {
immediate: true,
});
</script>
<style>
@ -103,12 +216,11 @@ html,
body {
width: 100%;
height: 100%;
overscroll-behavior: none;
}
body {
/* NOTE: htmlにも overflow: clip を設定したいところだが、設定すると何故か少なくともChromeで html が main thread scrolling になりパフォーマンスが(多分)落ちる */
overflow: clip;
position: fixed;
top: 0;
left: 0;
overscroll-behavior: none;
}
#misskey_app {

View File

@ -497,7 +497,7 @@ export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) {
if (claimedAchievements.includes(type)) return;
claimingQueue.add(type);
claimedAchievements.push(type);
await new Promise(resolve => window.setTimeout(resolve, (claimingQueue.size - 1) * 500));
await new Promise(resolve => setTimeout(resolve, (claimingQueue.size - 1) * 500));
window.setTimeout(() => {
claimingQueue.delete(type);
}, 500);

View File

@ -290,41 +290,51 @@ export const searchIndexes: SearchIndexItem[] = [
},
{
id: 'lfI3yMX9g',
label: i18n.ts.useBlurEffect,
keywords: ['blur'],
},
{
id: '31Y4IcGEf',
label: i18n.ts.useBlurEffectForModal,
keywords: ['blur', 'modal'],
},
{
id: '78q2asrLS',
label: i18n.ts.showAvatarDecorations,
keywords: ['avatar', 'icon', 'decoration', 'show'],
},
{
id: '31Y4IcGEf',
id: 'zydOfGYip',
label: i18n.ts.alwaysConfirmFollow,
keywords: ['follow', 'confirm', 'always'],
},
{
id: '78q2asrLS',
id: 'wqpOC22Zm',
label: i18n.ts.highlightSensitiveMedia,
keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
},
{
id: 'zydOfGYip',
id: 'c98gbF9c6',
label: i18n.ts.confirmWhenRevealingSensitiveMedia,
keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
},
{
id: 'wqpOC22Zm',
id: '4LxdiOMNh',
label: i18n.ts.enableAdvancedMfm,
keywords: ['mfm', 'enable', 'show', 'advanced'],
},
{
id: 'c98gbF9c6',
id: '9gTCaLkIf',
label: i18n.ts.enableInfiniteScroll,
keywords: ['auto', 'load', 'auto', 'more', 'scroll'],
},
{
id: '6ANRSOaNg',
id: 'jmJT0twuJ',
label: i18n.ts.emojiStyle,
keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
},
{
id: 'wo0s0CaI1',
id: 'igFN7RIUa',
label: i18n.ts.pinnedList,
keywords: ['pinned', 'list'],
},
@ -333,85 +343,85 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['general'],
},
{
id: 'l78F2W9Ok',
id: 'ufc2X9voy',
children: [
{
id: 'iOJ3Crlky',
id: 'd2H4E5ys6',
label: i18n.ts.showFixedPostForm,
keywords: ['post', 'form', 'timeline'],
},
{
id: 'CQldliCSi',
id: '1LHOhDKGW',
label: i18n.ts.showFixedPostFormInChannel,
keywords: ['post', 'form', 'timeline', 'channel'],
},
{
id: 'd2H4E5ys6',
id: 'DSzwvTp7i',
label: i18n.ts.collapseRenotes,
keywords: ['renote', i18n.ts.collapseRenotesDescription],
},
{
id: 'yb11lSY1G',
id: 'jb3HUeyrx',
label: i18n.ts.showGapBetweenNotesInTimeline,
keywords: ['note', 'timeline', 'gap'],
},
{
id: 'fL49Zxe9i',
id: '2LNjwv1cr',
label: i18n.ts.disableStreamingTimeline,
keywords: ['disable', 'streaming', 'timeline'],
},
{
id: 'ykifk3NHS',
id: '7W6g8Dcqz',
label: i18n.ts.showNoteActionsOnlyHover,
keywords: ['hover', 'show', 'footer', 'action'],
},
{
id: 'tLGyaQagB',
id: 'uAOoH3LFF',
label: i18n.ts.showClipButtonInNoteFooter,
keywords: ['footer', 'action', 'clip', 'show'],
},
{
id: '7W6g8Dcqz',
id: 'eCiyZLC8n',
label: i18n.ts.showReactionsCount,
keywords: ['reaction', 'count', 'show'],
},
{
id: 'uAOoH3LFF',
id: '68u9uRmFP',
label: i18n.ts.confirmOnReact,
keywords: ['reaction', 'confirm'],
},
{
id: 'eCiyZLC8n',
id: 'rHWm4sXIe',
label: i18n.ts.loadRawImages,
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
},
{
id: '68u9uRmFP',
id: '9L2XGJw7e',
label: i18n.ts.useReactionPickerForContextMenu,
keywords: ['reaction', 'picker', 'contextmenu', 'open'],
},
{
id: 'yxehrHZ6x',
id: 'uIMCIK7kG',
label: i18n.ts.reactionsDisplaySize,
keywords: ['reaction', 'size', 'scale', 'display'],
},
{
id: 'gi8ILaE2Z',
id: 'uMckjO9bz',
label: i18n.ts.limitWidthOfReaction,
keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
},
{
id: 'cEQJZ7DQG',
id: 'yeghU4qiH',
label: i18n.ts.mediaListWithOneImageAppearance,
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
},
{
id: 'haX4QVulD',
id: 'yYSOPoAKE',
label: i18n.ts.instanceTicker,
keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
},
{
id: 'pneYnQekL',
id: 'iOHiIu32L',
label: i18n.ts.displayOfSensitiveMedia,
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
},
@ -420,25 +430,25 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['timeline', 'note'],
},
{
id: 'eJ2jme16W',
id: 'eROFRMtXv',
children: [
{
id: 'ErMQr6LQk',
id: 'BaQfrVO82',
label: i18n.ts.keepCw,
keywords: ['remember', 'keep', 'note', 'cw'],
},
{
id: 'zrJicawH9',
id: 'vFerPo2he',
label: i18n.ts.rememberNoteVisibility,
keywords: ['remember', 'keep', 'note', 'visibility'],
},
{
id: 'BaQfrVO82',
id: 'dcAC0yJcH',
label: i18n.ts.enableQuickAddMfmFunction,
keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
},
{
id: 'C2WYcVM1d',
id: 'bECeWZVMb',
label: i18n.ts.defaultNoteVisibility,
keywords: ['default', 'note', 'visibility'],
},
@ -447,20 +457,20 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['post', 'form'],
},
{
id: 'sQXSA6gik',
id: 'tsSP93Cc6',
children: [
{
id: 'rICn8stqk',
id: 'dtw8FepYL',
label: i18n.ts.useGroupedNotifications,
keywords: ['group'],
},
{
id: 'xFmAg2tDe',
id: 'eb0yCYJTn',
label: i18n.ts.position,
keywords: ['position'],
},
{
id: 'Ek4Cw3VPq',
id: '1Spt4Gpr5',
label: i18n.ts.stackAxis,
keywords: ['stack', 'axis', 'direction'],
},
@ -469,15 +479,20 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['notification'],
},
{
id: 'gDVCqZfxm',
id: 'SYmWxGOF',
label: i18n.ts.dataSaver,
keywords: ['datasaver'],
},
{
id: 'vPQPvmntL',
children: [
{
id: 'ei8Ix3s4S',
id: 'zZxyXHk3A',
label: i18n.ts._settings._chat.showSenderName,
keywords: ['show', 'sender', 'name'],
},
{
id: '2E7vdIUQd',
id: 'omEy5Q3Ev',
label: i18n.ts._settings._chat.sendOnEnter,
keywords: ['send', 'enter', 'newline'],
},
@ -486,77 +501,50 @@ export const searchIndexes: SearchIndexItem[] = [
keywords: ['chat', 'messaging'],
},
{
id: '96LnS1sxB',
id: '5fy7VEy6i',
children: [
{
id: '5h8vhCX1S',
label: i18n.ts.turnOffToImprovePerformance,
keywords: ['blur'],
},
{
id: 'Cbjosj3TG',
label: i18n.ts.turnOffToImprovePerformance,
keywords: ['blur', 'modal'],
},
{
id: 'BKndoHcCj',
label: i18n.ts.turnOffToImprovePerformance,
keywords: ['sticky'],
},
],
label: i18n.ts.performance,
keywords: ['performance'],
},
{
id: '4yCgcFElF',
label: i18n.ts.dataSaver,
keywords: ['datasaver'],
},
{
id: 'DILm2LlCn',
children: [
{
id: 'Fd0rFTSry',
id: 'EosiWZvak',
label: i18n.ts.squareAvatars,
keywords: ['avatar', 'icon', 'square'],
},
{
id: 'xNsLokqeA',
id: 'qY5xTzl35',
label: i18n.ts.seasonalScreenEffect,
keywords: ['effect', 'show'],
},
{
id: 'sZcalFBE8',
id: '2VSnj81vC',
label: i18n.ts.openImageInNewTab,
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
},
{
id: 'Eh7vTluDO',
id: 'hdQa7W2H1',
label: i18n.ts.withRepliesByDefaultForNewlyFollowed,
keywords: ['follow', 'replies'],
},
{
id: 'vTRSKf1JA',
id: 'nnj4DkjhP',
label: i18n.ts.whenServerDisconnected,
keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
},
{
id: 'zlO5cBZFH',
id: 'Eh7vTluDO',
label: i18n.ts.numberOfPageCache,
keywords: ['cache', 'page'],
},
{
id: 'huQ8nc4iD',
id: 'vTRSKf1JA',
label: i18n.ts.forceShowAds,
keywords: ['ad', 'show'],
},
{
id: 'nJWfqwQ4R',
id: 'dwhQfcLGt',
label: i18n.ts.hemisphere,
keywords: [],
},
{
id: 'kwEEgTlwR',
id: 'Ar1lj7f7U',
label: i18n.ts.additionalEmojiDictionary,
keywords: ['emoji', 'dictionary', 'additional', 'extra'],
},

View File

@ -15,11 +15,11 @@ export function confetti(options: { duration?: number; } = {}) {
return Math.random() * (max - min) + min;
}
const interval = window.setInterval(() => {
const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
return window.clearInterval(interval);
return clearInterval(interval);
}
const particleCount = 50 * (timeLeft / duration);

View File

@ -362,18 +362,12 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${user.host}`;
os.post({ specified: user, initialText: `${canonical} ` });
},
});
if ($i.policies.canChat && user.canChat && user.host == null) {
menuItems.push({
type: 'link',
icon: 'ti ti-messages',
text: i18n.ts._chat.chatWithThisUser,
to: `/chat/user/${user.id}`,
});
}
menuItems.push({ type: 'divider' }, {
}, {
type: 'link',
icon: 'ti ti-messages',
text: i18n.ts._chat.chatWithThisUser,
to: `/chat/user/${user.id}`,
}, { type: 'divider' }, {
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
action: toggleMute,

View File

@ -2,7 +2,7 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { getHTMLElementOrNull } from '@/utility/get-dom-node-or-null.js';
import { getHTMLElementOrNull } from "@/utility/get-dom-node-or-null.js";
//#region types
export type Keymap = Record<string, CallbackFunction | CallbackObject>;
@ -136,7 +136,7 @@ let lastHotKeyStoreTimer: number | null = null;
const storePattern = (ev: KeyboardEvent, callback: CallbackFunction) => {
if (lastHotKeyStoreTimer != null) {
window.clearTimeout(lastHotKeyStoreTimer);
clearTimeout(lastHotKeyStoreTimer);
}
latestHotkey = {

View File

@ -5,7 +5,7 @@
const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.requestIdleCallback ?? ((callback) => {
const start = performance.now();
const timeoutId = window.setTimeout(() => {
const timeoutId = setTimeout(() => {
callback({
didTimeout: false, // polyfill でタイムアウト発火することはない
timeRemaining() {
@ -17,7 +17,7 @@ const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.re
return timeoutId;
});
const cancelIdleCallback: typeof globalThis.cancelIdleCallback = globalThis.cancelIdleCallback ?? ((timeoutId) => {
window.clearTimeout(timeoutId);
clearTimeout(timeoutId);
});
class IdlingRenderScheduler {

View File

@ -158,7 +158,7 @@ export async function playMisskeySfxFile(soundStore: SoundStore): Promise<boolea
canPlay = false;
return await playMisskeySfxFileInternal(soundStore).finally(() => {
// ごく短時間に音が重複しないように
window.setTimeout(() => {
setTimeout(() => {
canPlay = true;
}, 25);
});
@ -230,10 +230,10 @@ export async function getSoundDuration(file: string): Promise<number> {
const audioEl = window.document.createElement('audio');
audioEl.src = file;
return new Promise((resolve) => {
const si = window.setInterval(() => {
const si = setInterval(() => {
if (audioEl.readyState > 0) {
resolve(audioEl.duration * 1000);
window.clearInterval(si);
clearInterval(si);
audioEl.remove();
}
}, 100);

View File

@ -5,5 +5,5 @@
export async function tick(): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
await new Promise((globalThis.requestIdleCallback ?? window.setTimeout) as never);
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);
}

View File

@ -1,7 +1,7 @@
{
"type": "module",
"name": "misskey-js",
"version": "2025.3.2-beta.16",
"version": "2025.3.2-beta.14",
"description": "Misskey SDK for JavaScript",
"license": "MIT",
"main": "./built/index.js",

View File

@ -4057,7 +4057,6 @@ export type components = {
followersVisibility: 'public' | 'followers' | 'private';
/** @enum {string} */
chatScope: 'everyone' | 'following' | 'followers' | 'mutual' | 'none';
canChat: boolean;
roles: components['schemas']['RoleLite'][];
followedMessage?: string | null;
memo: string | null;