Compare commits
39 Commits
bfd3de6647
...
3e2be115b0
Author | SHA1 | Date |
---|---|---|
|
3e2be115b0 | |
|
eb4062cf63 | |
|
d9012740a1 | |
|
1b776a7e7e | |
|
e0b7c56458 | |
|
2787158a04 | |
|
2bbc0878e7 | |
|
fb1542429f | |
|
05b23eda59 | |
|
ddd6d72dd7 | |
|
25db8c2fa9 | |
|
2ad7b010e4 | |
|
7c06ffc422 | |
|
df3ed93f84 | |
|
b030e33856 | |
|
7fd3adedee | |
|
ae59578115 | |
|
609a37742c | |
|
d9d796b204 | |
|
6c2c3f08be | |
|
e5e4390494 | |
|
5a09e7a8b4 | |
|
88e6bd1533 | |
|
7d8c98767a | |
|
490222fb78 | |
|
1af4081090 | |
|
33e76f9dfc | |
|
8dd8f636dc | |
|
3451c9a0de | |
|
fc88410c0d | |
|
3682c0069c | |
|
2b42e8f171 | |
|
e990831a09 | |
|
18355a0838 | |
|
811077ca66 | |
|
1c26dae39f | |
|
c37f9d38a3 | |
|
dec3e86e5e | |
|
0c14250678 |
|
@ -38,6 +38,7 @@
|
|||
- Enhance: プラグインの管理が強化されました
|
||||
- インストール/アンインストール/設定の変更時にリロード不要になりました
|
||||
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
|
||||
- Enhance: アイコンのスクロール追従を無効化してパフォーマンス向上できるように
|
||||
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
|
||||
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
|
||||
- Enhance: テーマ設定画面のデザインを改善
|
||||
|
|
|
@ -1335,6 +1335,7 @@ information: "Informació"
|
|||
chat: "Xat"
|
||||
migrateOldSettings: "Migració de la configuració antiga "
|
||||
migrateOldSettings_description: "Normalment això es fa automàticament, però si la transició no es fa, el procés es pot iniciar manualment. S'esborrarà la configuració actual."
|
||||
compress: "Comprimir "
|
||||
_chat:
|
||||
noMessagesYet: "Encara no tens missatges "
|
||||
newMessage: "Missatge nou"
|
||||
|
@ -1363,6 +1364,8 @@ _chat:
|
|||
newline: "Línia nova "
|
||||
muteThisRoom: "Silenciar aquesta sala"
|
||||
deleteRoom: "Esborrar la sala"
|
||||
chatNotAvailableForThisAccountOrServer: "El xat no està disponible per aquest servidor o aquest compte."
|
||||
chatNotAvailableInOtherAccount: "La funció de xat es troba desactivada al compte de l'altre usuari."
|
||||
cannotChatWithTheUser: "No pots xatejar amb aquest usuari"
|
||||
cannotChatWithTheUser_description: "El xat està desactivat o l'altra part encara no l'ha obert."
|
||||
chatWithThisUser: "Xateja amb aquest usuari"
|
||||
|
|
|
@ -301,6 +301,7 @@ 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"
|
||||
|
@ -1318,25 +1319,55 @@ 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"
|
||||
|
@ -1353,9 +1384,11 @@ _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"
|
||||
|
@ -1364,6 +1397,7 @@ _abuseUserReport:
|
|||
accept: "Akzeptieren"
|
||||
reject: "Ablehnen"
|
||||
_delivery:
|
||||
status: "Auslieferungsstatus"
|
||||
stop: "Gesperrt"
|
||||
_type:
|
||||
none: "Wird veröffentlicht"
|
||||
|
@ -1465,7 +1499,9 @@ _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."
|
||||
|
@ -1483,6 +1519,7 @@ _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:
|
||||
|
@ -1800,6 +1837,7 @@ _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"
|
||||
|
@ -1815,12 +1853,19 @@ _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"
|
||||
|
@ -2172,7 +2217,9 @@ _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?"
|
||||
|
@ -2295,6 +2342,7 @@ _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"
|
||||
|
@ -2352,6 +2400,7 @@ _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"
|
||||
|
@ -2383,6 +2432,7 @@ _pages:
|
|||
eyeCatchingImageSet: "Vorschaubild festlegen"
|
||||
eyeCatchingImageRemove: "Vorschaubild entfernen"
|
||||
chooseBlock: "Block hinzufügen"
|
||||
enterSectionTitle: "Titel des Abschnitts eingeben"
|
||||
selectType: "Typ auswählen"
|
||||
contentBlocks: "Inhalt"
|
||||
inputBlocks: "Eingabe"
|
||||
|
@ -2393,6 +2443,8 @@ _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"
|
||||
|
@ -2415,6 +2467,7 @@ _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"
|
||||
|
|
|
@ -301,6 +301,7 @@ uploadFromUrlMayTakeTime: "It may take some time until the upload is complete."
|
|||
explore: "Explore"
|
||||
messageRead: "Read"
|
||||
noMoreHistory: "There is no further history"
|
||||
startChat: "Start chat"
|
||||
nUsersRead: "read by {n}"
|
||||
agreeTo: "I agree to {0}"
|
||||
agree: "Agree"
|
||||
|
@ -1331,12 +1332,55 @@ emojiPalette: "Emoji palette"
|
|||
postForm: "Posting form"
|
||||
textCount: "Character count"
|
||||
information: "About"
|
||||
chat: "Chat"
|
||||
migrateOldSettings: "Migrate old client settings"
|
||||
migrateOldSettings_description: "This should be done automatically but if for some reason the migration was not successful, you can trigger the migration process yourself manually. The current configuration information will be overwritten."
|
||||
compress: "Compress"
|
||||
_chat:
|
||||
noMessagesYet: "No messages yet"
|
||||
newMessage: "New message"
|
||||
individualChat: "Private Chat"
|
||||
individualChat_description: "Have a private chat with another person."
|
||||
roomChat: "Room Chat"
|
||||
roomChat_description: "A chat room which can have multiple people.\nYou can also invite people who don't allow private chats if they accept the invite."
|
||||
createRoom: "Create Room"
|
||||
inviteUserToChat: "Invite users to start chatting"
|
||||
yourRooms: "Created rooms"
|
||||
joiningRooms: "Joined rooms"
|
||||
invitations: "Invite"
|
||||
noInvitations: "No invitations"
|
||||
history: "History"
|
||||
noHistory: "No history available"
|
||||
noRooms: "No rooms found"
|
||||
inviteUser: "Invite Users"
|
||||
sentInvitations: "Sent Invites"
|
||||
join: "Join"
|
||||
ignore: "Ignore"
|
||||
leave: "Leave room"
|
||||
members: "Members"
|
||||
searchMessages: "Search messages"
|
||||
home: "Home"
|
||||
send: "Send"
|
||||
newline: "New line"
|
||||
muteThisRoom: "Mute room"
|
||||
deleteRoom: "Delete room"
|
||||
chatNotAvailableForThisAccountOrServer: "Chat is not enabled on this server or for this account."
|
||||
chatNotAvailableInOtherAccount: "The chat function is disabled for the other user."
|
||||
cannotChatWithTheUser: "Cannot start a chat with this user"
|
||||
cannotChatWithTheUser_description: "Chat is either unavailable or the other party has not enabled chat."
|
||||
chatWithThisUser: "Chat with user"
|
||||
thisUserAllowsChatOnlyFromFollowers: "This user accepts chats from followers only."
|
||||
thisUserAllowsChatOnlyFromFollowing: "This user accepts chats only from users they follow."
|
||||
thisUserAllowsChatOnlyFromMutualFollowing: "This user only accepts chats from users who are mutual followers."
|
||||
thisUserNotAllowedChatAnyone: "This user is not accepting chats from anyone."
|
||||
chatAllowedUsers: "Who to allow chatting with"
|
||||
chatAllowedUsers_note: "You can chat with anyone to whom you have sent a chat message regardless of this setting."
|
||||
_chatAllowedUsers:
|
||||
everyone: "Everyone"
|
||||
followers: "Only your followers"
|
||||
following: "Only users you are following"
|
||||
mutual: "Mutual followers only"
|
||||
none: "Nobody"
|
||||
_emojiPalette:
|
||||
palettes: "Palette"
|
||||
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
|
||||
|
@ -1362,6 +1406,12 @@ _settings:
|
|||
timelineAndNote: "Timeline and note"
|
||||
makeEveryTextElementsSelectable: "Make all text elements selectable"
|
||||
makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
|
||||
showNavbarSubButtons: "Show sub-buttons on the navigation bar"
|
||||
ifOn: "When turned on"
|
||||
ifOff: "When turned off"
|
||||
_chat:
|
||||
showSenderName: "Show sender's name"
|
||||
sendOnEnter: "Press Enter to send"
|
||||
_preferencesProfile:
|
||||
profileName: "Profile name"
|
||||
profileNameDescription: "Set a name that identifies this device."
|
||||
|
@ -1871,6 +1921,7 @@ _role:
|
|||
canImportFollowing: "Allow importing following"
|
||||
canImportMuting: "Allow importing muting"
|
||||
canImportUserLists: "Allow importing lists"
|
||||
canChat: "Allow Chat"
|
||||
_condition:
|
||||
roleAssignedTo: "Assigned to manual roles"
|
||||
isLocal: "Local user"
|
||||
|
@ -2101,6 +2152,7 @@ _sfx:
|
|||
noteMy: "Own note"
|
||||
notification: "Notifications"
|
||||
reaction: "On choosing a reaction"
|
||||
chatMessage: "Chat Messages"
|
||||
_soundSettings:
|
||||
driveFile: "Use an audio file in Drive."
|
||||
driveFileWarn: "Select an audio file from Drive."
|
||||
|
@ -2248,6 +2300,7 @@ _permissions:
|
|||
"read:federation": "Get federation data"
|
||||
"write:report-abuse": "Report violation"
|
||||
"write:chat": "Compose or delete chat messages"
|
||||
"read:chat": "Browse Chat"
|
||||
_auth:
|
||||
shareAccessTitle: "Granting application permissions"
|
||||
shareAccess: "Would you like to authorize \"{name}\" to access this account?"
|
||||
|
@ -2496,6 +2549,7 @@ _notification:
|
|||
newNote: "New note"
|
||||
unreadAntennaNote: "Antenna {name}"
|
||||
roleAssigned: "Role given"
|
||||
chatRoomInvitationReceived: "You have been invited to a chat room"
|
||||
emptyPushNotificationMessage: "Push notifications have been updated"
|
||||
achievementEarned: "Achievement unlocked"
|
||||
testNotification: "Test notification"
|
||||
|
@ -2524,6 +2578,7 @@ _notification:
|
|||
receiveFollowRequest: "Received follow requests"
|
||||
followRequestAccepted: "Accepted follow requests"
|
||||
roleAssigned: "Role given"
|
||||
chatRoomInvitationReceived: "Invited to chat room"
|
||||
achievementEarned: "Achievement unlocked"
|
||||
exportCompleted: "The export has been completed"
|
||||
login: "Sign In"
|
||||
|
@ -2663,6 +2718,7 @@ _moderationLogTypes:
|
|||
deletePage: "Page deleted"
|
||||
deleteFlash: "Play deleted"
|
||||
deleteGalleryPost: "Gallery post deleted"
|
||||
deleteChatRoom: "Deleted Chat Room"
|
||||
updateProxyAccountDescription: "Update the description of the proxy account"
|
||||
_fileViewer:
|
||||
title: "File details"
|
||||
|
|
|
@ -5472,6 +5472,14 @@ export interface Locale extends ILocale {
|
|||
* ルームを削除
|
||||
*/
|
||||
"deleteRoom": string;
|
||||
/**
|
||||
* このサーバー、またはこのアカウントでチャットは有効化されていません。
|
||||
*/
|
||||
"chatNotAvailableForThisAccountOrServer": string;
|
||||
/**
|
||||
* 相手のアカウントでチャット機能が使えない状態になっています。
|
||||
*/
|
||||
"chatNotAvailableInOtherAccount": string;
|
||||
/**
|
||||
* このユーザーとのチャットを開始できません
|
||||
*/
|
||||
|
@ -5626,6 +5634,10 @@ export interface Locale extends ILocale {
|
|||
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
|
||||
*/
|
||||
"makeEveryTextElementsSelectable_description": string;
|
||||
/**
|
||||
* アイコンをスクロールに追従させる
|
||||
*/
|
||||
"useStickyIcons": string;
|
||||
/**
|
||||
* ナビゲーションバーに副ボタンを表示
|
||||
*/
|
||||
|
|
|
@ -1335,6 +1335,7 @@ information: "Informazioni"
|
|||
chat: "Chat"
|
||||
migrateOldSettings: "Migrare le vecchie impostazioni"
|
||||
migrateOldSettings_description: "Di solito, viene fatto automaticamente. Se per qualche motivo non fossero migrate con successo, è possibile avviare il processo di migrazione manualmente, sovrascrivendo le configurazioni attuali."
|
||||
compress: "Comprimi"
|
||||
_chat:
|
||||
noMessagesYet: "Ancora nessun messaggio"
|
||||
newMessage: "Nuovo messaggio"
|
||||
|
@ -1363,6 +1364,8 @@ _chat:
|
|||
newline: "Nuova riga"
|
||||
muteThisRoom: "Silenzia stanza"
|
||||
deleteRoom: "Elimina stanza"
|
||||
chatNotAvailableForThisAccountOrServer: "Questo server, o questo profilo ha disabilitato la chat."
|
||||
chatNotAvailableInOtherAccount: "La chat non è disponibile nel profilo dell'altra persona."
|
||||
cannotChatWithTheUser: "Impossibile chattare con questa persona"
|
||||
cannotChatWithTheUser_description: "La chat potrebbe non essere disponibile, oppure l'altra persona potrebbe non esserlo."
|
||||
chatWithThisUser: "Chatta con questa persona"
|
||||
|
|
|
@ -1365,6 +1365,8 @@ _chat:
|
|||
newline: "改行"
|
||||
muteThisRoom: "このルームをミュート"
|
||||
deleteRoom: "ルームを削除"
|
||||
chatNotAvailableForThisAccountOrServer: "このサーバー、またはこのアカウントでチャットは有効化されていません。"
|
||||
chatNotAvailableInOtherAccount: "相手のアカウントでチャット機能が使えない状態になっています。"
|
||||
cannotChatWithTheUser: "このユーザーとのチャットを開始できません"
|
||||
cannotChatWithTheUser_description: "チャットが使えない状態になっているか、相手がチャットを開放していません。"
|
||||
chatWithThisUser: "チャットする"
|
||||
|
@ -1407,6 +1409,7 @@ _settings:
|
|||
timelineAndNote: "タイムラインとノート"
|
||||
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
|
||||
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
|
||||
useStickyIcons: "アイコンをスクロールに追従させる"
|
||||
showNavbarSubButtons: "ナビゲーションバーに副ボタンを表示"
|
||||
ifOn: "オンのとき"
|
||||
ifOff: "オフのとき"
|
||||
|
|
|
@ -1335,6 +1335,7 @@ information: "关于"
|
|||
chat: "聊天"
|
||||
migrateOldSettings: "迁移旧设置信息"
|
||||
migrateOldSettings_description: "通常设置信息将自动迁移。但如果由于某种原因迁移不成功,则可以手动触发迁移过程。当前的配置信息将被覆盖。"
|
||||
compress: "压缩"
|
||||
_chat:
|
||||
noMessagesYet: "还没有消息"
|
||||
newMessage: "新消息"
|
||||
|
@ -1363,6 +1364,8 @@ _chat:
|
|||
newline: "换行"
|
||||
muteThisRoom: "静音此房间"
|
||||
deleteRoom: "删除房间"
|
||||
chatNotAvailableForThisAccountOrServer: "此服务器或者账户还未开启聊天功能。"
|
||||
chatNotAvailableInOtherAccount: "对方账户目前处于无法使用聊天的状态。"
|
||||
cannotChatWithTheUser: "无法与此用户聊天"
|
||||
cannotChatWithTheUser_description: "可能现在无法使用聊天,或者对方未开启聊天。"
|
||||
chatWithThisUser: "聊天"
|
||||
|
|
|
@ -1335,6 +1335,7 @@ information: "關於"
|
|||
chat: "聊天"
|
||||
migrateOldSettings: "遷移舊設定資訊"
|
||||
migrateOldSettings_description: "通常情況下,這會自動進行,但若因某些原因未能順利遷移,您可以手動觸發遷移處理。請注意,當前的設定資訊將會被覆寫。"
|
||||
compress: "壓縮"
|
||||
_chat:
|
||||
noMessagesYet: "尚無訊息"
|
||||
newMessage: "新訊息"
|
||||
|
@ -1363,6 +1364,8 @@ _chat:
|
|||
newline: "換行"
|
||||
muteThisRoom: "此聊天室已靜音"
|
||||
deleteRoom: "刪除聊天室"
|
||||
chatNotAvailableForThisAccountOrServer: "這個伺服器或這個帳號的聊天功能尚未啟用。"
|
||||
chatNotAvailableInOtherAccount: "對方的帳號無法使用聊天功能。"
|
||||
cannotChatWithTheUser: "無法與此使用者聊天"
|
||||
cannotChatWithTheUser_description: "聊天功能目前無法使用,或對方尚未開放聊天功能。"
|
||||
chatWithThisUser: "聊天"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.3.2-beta.14",
|
||||
"version": "2025.3.2-beta.16",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
@ -463,6 +463,7 @@ export class WebhookTestService {
|
|||
followersVisibility: 'public',
|
||||
followingVisibility: 'public',
|
||||
chatScope: 'mutual',
|
||||
canChat: true,
|
||||
twoFactorEnabled: false,
|
||||
usePasswordLessLogin: false,
|
||||
securityKeys: false,
|
||||
|
|
|
@ -557,6 +557,7 @@ 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,
|
||||
|
|
|
@ -363,6 +363,10 @@ 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,
|
||||
|
|
|
@ -84,6 +84,7 @@ describe('ユーザー', () => {
|
|||
followingVisibility: user.followingVisibility,
|
||||
followersVisibility: user.followersVisibility,
|
||||
chatScope: user.chatScope,
|
||||
canChat: user.canChat,
|
||||
roles: user.roles,
|
||||
memo: user.memo,
|
||||
});
|
||||
|
@ -346,6 +347,7 @@ 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);
|
||||
|
||||
|
|
|
@ -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, scroll, isTailVisible, isHeadVisible } from '@@/js/scroll.js';
|
||||
import { onScrollTop, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, 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) {
|
||||
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||
scrollInContainer(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||
} else {
|
||||
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ export function onScrollBottom(el: HTMLElement, cb: () => unknown, tolerance = 1
|
|||
return removeListener;
|
||||
}
|
||||
|
||||
export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) {
|
||||
export function scrollInContainer(el: HTMLElement, options: ScrollToOptions | undefined) {
|
||||
const container = getScrollContainer(el);
|
||||
if (container == null) {
|
||||
window.scroll(options);
|
||||
|
@ -108,7 +108,7 @@ export function scroll(el: HTMLElement, options: ScrollToOptions | undefined) {
|
|||
* @param options Scroll options
|
||||
*/
|
||||
export function scrollToTop(el: HTMLElement, options: { behavior?: ScrollBehavior; } = {}) {
|
||||
scroll(el, { top: 0, ...options });
|
||||
scrollInContainer(el, { top: 0, ...options });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,7 +58,12 @@ export default [
|
|||
// location ... window.locationと衝突 or 紛らわしい
|
||||
// document ... window.documentと衝突 or 紛らわしい
|
||||
// history ... window.historyと衝突 or 紛らわしい
|
||||
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
|
||||
// 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'],
|
||||
'no-restricted-globals': [
|
||||
'error',
|
||||
{
|
||||
|
@ -85,6 +90,26 @@ 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 という変数名を定義し忘れている',
|
||||
|
|
|
@ -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: () => App<Element>) {
|
||||
export async function common(createVue: () => Promise<App<Element>>) {
|
||||
console.info(`Misskey v${version}`);
|
||||
|
||||
if (_DEV_) {
|
||||
|
@ -263,7 +263,7 @@ export async function common(createVue: () => App<Element>) {
|
|||
});
|
||||
});
|
||||
|
||||
const app = createVue();
|
||||
const app = await createVue();
|
||||
|
||||
if (_DEV_) {
|
||||
app.config.performance = true;
|
||||
|
|
|
@ -32,7 +32,7 @@ import { signout } from '@/signout.js';
|
|||
import { migrateOldSettings } from '@/pref-migrate.js';
|
||||
|
||||
export async function mainBoot() {
|
||||
const { isClientUpdated, lastVersion } = await common(() => {
|
||||
const { isClientUpdated, lastVersion } = await common(async () => {
|
||||
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 = defineAsyncComponent(() => import('@/ui/zen.vue'));
|
||||
rootComponent = await import('@/ui/zen.vue').then(x => x.default);
|
||||
break;
|
||||
case 'deck':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/deck.vue'));
|
||||
rootComponent = await import('@/ui/deck.vue').then(x => x.default);
|
||||
break;
|
||||
case 'visitor':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/visitor.vue'));
|
||||
rootComponent = await import('@/ui/visitor.vue').then(x => x.default);
|
||||
break;
|
||||
case 'classic':
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/classic.vue'));
|
||||
rootComponent = await import('@/ui/classic.vue').then(x => x.default);
|
||||
break;
|
||||
default:
|
||||
rootComponent = defineAsyncComponent(() => import('@/ui/universal.vue'));
|
||||
rootComponent = await import('@/ui/universal.vue').then(x => x.default);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
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(() => createApp(
|
||||
defineAsyncComponent(() => import('@/ui/minimum.vue')),
|
||||
));
|
||||
const { isClientUpdated } = await common(async () => createApp(UiMinimum));
|
||||
|
||||
emojiPicker.init();
|
||||
}
|
||||
|
|
|
@ -2,20 +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 { 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 => setTimeout(resolve, ms));
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
* 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 => setTimeout(resolve, ms));
|
||||
return new Promise(resolve => window.setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
|
|
|
@ -79,7 +79,7 @@ function opening() {
|
|||
picker.value?.focus();
|
||||
|
||||
// 何故かちょっと待たないとフォーカスされない
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
picker.value?.focus();
|
||||
}, 10);
|
||||
}
|
||||
|
|
|
@ -100,7 +100,7 @@ function touchMove(event: TouchEvent) {
|
|||
|
||||
pullDistance.value = 0;
|
||||
isSwiping.value = false;
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
isSwipingForClass.value = false;
|
||||
}, 400);
|
||||
|
||||
|
|
|
@ -339,7 +339,7 @@ const bufferedDataRatio = computed(() => {
|
|||
// MediaControl Events
|
||||
function onMouseOver() {
|
||||
if (controlStateTimer) {
|
||||
clearTimeout(controlStateTimer);
|
||||
window.clearTimeout(controlStateTimer);
|
||||
}
|
||||
isHoverring.value = true;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ 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;
|
||||
|
@ -94,7 +95,7 @@ const emit = defineEmits<{
|
|||
(ev: 'closed'): void;
|
||||
}>();
|
||||
|
||||
provide('modal', true);
|
||||
provide(DI.inModal, true);
|
||||
|
||||
const maxHeight = ref<number>();
|
||||
const fixed = ref(false);
|
||||
|
|
|
@ -14,8 +14,6 @@ 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/>
|
||||
|
@ -47,7 +45,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" :user="appearNote.user" :link="!mock" :preview="!mock"/>
|
||||
<MkAvatar :class="[$style.avatar, prefer.s.useStickyIcons ? $style.useSticky : null]" :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"/>
|
||||
|
@ -852,9 +850,12 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
margin: 0 14px 0 0;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
position: sticky !important;
|
||||
top: calc(22px + var(--MI-stickyTop, 0px));
|
||||
left: 0;
|
||||
|
||||
&.useSticky {
|
||||
position: sticky !important;
|
||||
top: calc(22px + var(--MI-stickyTop, 0px));
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.main {
|
||||
|
@ -1041,7 +1042,10 @@ function emitUpdReaction(emoji: string, delta: number) {
|
|||
margin: 0 10px 0 0;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
top: calc(14px + var(--MI-stickyTop, 0px));
|
||||
|
||||
&.useSticky {
|
||||
top: calc(14px + var(--MI-stickyTop, 0px));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,32 +13,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notes }">
|
||||
<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 :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>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useTemplateRef } from 'vue';
|
||||
import { useTemplateRef, TransitionGroup } 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;
|
||||
|
@ -55,20 +49,29 @@ defineExpose({
|
|||
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
container-type: inline-size;
|
||||
|
||||
&.noGap {
|
||||
> .notes {
|
||||
background: var(--MI_THEME-panel);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.noGap) {
|
||||
> .notes {
|
||||
background: var(--MI_THEME-bg);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
|
||||
<template #default="{ items: notifications }">
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</MkPullToRefresh>
|
||||
|
@ -29,7 +31,6 @@ 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';
|
||||
|
@ -84,28 +85,22 @@ 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>
|
||||
.list {
|
||||
.notifications {
|
||||
container-type: inline-size;
|
||||
background: var(--MI_THEME-panel);
|
||||
}
|
||||
|
||||
.item {
|
||||
border-bottom: solid 0.5px var(--MI_THEME-divider);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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_" class="empty">
|
||||
<div v-else-if="empty" key="_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 class="loading"/>
|
||||
<MkLoading v-else/>
|
||||
</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 class="loading"/>
|
||||
<MkLoading v-else/>
|
||||
</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, scroll, isTailVisible } from '@@/js/scroll.js';
|
||||
import { onScrollTop, isHeadVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scrollInContainer, 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) {
|
||||
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
|
||||
scrollInContainer(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) {
|
||||
clearTimeout(timerForSetPause);
|
||||
window.clearTimeout(timerForSetPause);
|
||||
timerForSetPause = null;
|
||||
} else {
|
||||
isPausingUpdate = false;
|
||||
|
@ -453,11 +453,11 @@ onBeforeMount(() => {
|
|||
init().then(() => {
|
||||
if (props.pagination.reversed) {
|
||||
nextTick(() => {
|
||||
setTimeout(toBottom, 800);
|
||||
window.setTimeout(toBottom, 800);
|
||||
|
||||
// scrollToBottomでmoreFetchingボタンが画面外まで出るまで
|
||||
// more = trueを遅らせる
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
moreFetching.value = false;
|
||||
}, 2000);
|
||||
});
|
||||
|
@ -467,11 +467,11 @@ onBeforeMount(() => {
|
|||
|
||||
onBeforeUnmount(() => {
|
||||
if (timerForSetPause) {
|
||||
clearTimeout(timerForSetPause);
|
||||
window.clearTimeout(timerForSetPause);
|
||||
timerForSetPause = null;
|
||||
}
|
||||
if (preventAppearFetchMoreTimer.value) {
|
||||
clearTimeout(preventAppearFetchMoreTimer.value);
|
||||
window.clearTimeout(preventAppearFetchMoreTimer.value);
|
||||
preventAppearFetchMoreTimer.value = null;
|
||||
}
|
||||
scrollObserver.value?.disconnect();
|
||||
|
|
|
@ -140,7 +140,7 @@ import { DI } from '@/di.js';
|
|||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const modal = inject('modal');
|
||||
const modal = inject(DI.inModal, false);
|
||||
|
||||
const props = withDefaults(defineProps<PostFormProps & {
|
||||
fixed?: boolean;
|
||||
|
|
|
@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="{ [$style.slotClip]: isPullStart }">
|
||||
<slot/>
|
||||
</div>
|
||||
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -82,11 +81,11 @@ function moveBySystem(to: number): Promise<void> {
|
|||
return;
|
||||
}
|
||||
const startTime = Date.now();
|
||||
let intervalId = setInterval(() => {
|
||||
let intervalId = window.setInterval(() => {
|
||||
const time = Date.now() - startTime;
|
||||
if (time > RELEASE_TRANSITION_DURATION) {
|
||||
pullDistance.value = to;
|
||||
clearInterval(intervalId);
|
||||
window.clearInterval(intervalId);
|
||||
r();
|
||||
return;
|
||||
}
|
||||
|
@ -261,8 +260,4 @@ defineExpose({
|
|||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.slotClip {
|
||||
overflow-y: clip;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -25,7 +25,7 @@ const props = defineProps<{
|
|||
menuReaction?: boolean;
|
||||
}>();
|
||||
|
||||
const react = inject(DI.mfmEmojiReactCallback);
|
||||
const react = inject(DI.mfmEmojiReactCallback, null);
|
||||
|
||||
const char2path = prefer.s.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
* 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 type { StoryObj } from '@storybook/vue3';
|
||||
import MkPageHeader from './MkPageHeader.vue';
|
||||
import type { StoryObj } from '@storybook/vue3';
|
||||
export const Empty = {
|
||||
render(args) {
|
||||
return {
|
||||
|
@ -29,7 +28,7 @@ export const Empty = {
|
|||
};
|
||||
},
|
||||
async play() {
|
||||
const wait = new Promise((resolve) => setTimeout(resolve, 800));
|
||||
const wait = new Promise((resolve) => window.setTimeout(resolve, 800));
|
||||
await waitFor(async () => await wait);
|
||||
},
|
||||
args: {
|
||||
|
|
|
@ -133,7 +133,7 @@ async function enter(el: Element) {
|
|||
entering = false;
|
||||
});
|
||||
|
||||
setTimeout(renderTab, 170);
|
||||
window.setTimeout(renderTab, 170);
|
||||
}
|
||||
|
||||
function afterEnter(el: Element) {
|
||||
|
|
|
@ -69,7 +69,6 @@ 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);
|
||||
|
||||
|
@ -130,7 +129,6 @@ 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,
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<div :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
||||
<div ref="rootEl" :class="[$style.root, reversed ? '_pageScrollableReversed' : '_pageScrollable']">
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="actions" :tabs="tabs"/></template>
|
||||
<div :class="$style.body">
|
||||
|
@ -16,6 +16,8 @@ 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';
|
||||
|
||||
|
@ -31,6 +33,13 @@ 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>
|
||||
|
|
|
@ -44,7 +44,9 @@ provide(DI.routerCurrentDepth, currentDepth + 1);
|
|||
|
||||
const rootEl = useTemplateRef('rootEl');
|
||||
onMounted(() => {
|
||||
rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
|
||||
if (prefer.s.animation) {
|
||||
rootEl.value.style.viewTransitionName = viewId; // view-transition-nameにcss varが使えないっぽいため直接代入
|
||||
}
|
||||
});
|
||||
|
||||
// view-transition-newなどの<pt-name-selector>にはcss varが使えず、v-bindできないため直接スタイルを生成
|
||||
|
|
|
@ -50,6 +50,12 @@ 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';
|
||||
|
@ -68,13 +74,6 @@ 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[],
|
||||
|
@ -130,7 +129,7 @@ const bus = new GridEventEmitter();
|
|||
*
|
||||
* @see {@link onResize}
|
||||
*/
|
||||
const resizeObserver = new ResizeObserver((entries) => setTimeout(() => onResize(entries)));
|
||||
const resizeObserver = new ResizeObserver((entries) => window.setTimeout(() => onResize(entries)));
|
||||
|
||||
const rootEl = ref<InstanceType<typeof HTMLTableElement>>();
|
||||
/**
|
||||
|
|
|
@ -15,4 +15,5 @@ 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>,
|
||||
};
|
||||
|
|
|
@ -13,6 +13,8 @@ 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';
|
||||
|
@ -23,8 +25,6 @@ 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';
|
||||
|
|
|
@ -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 { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } 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() {
|
||||
scroll(rootEl.value, { top: 0 });
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
async function timetravel() {
|
||||
|
|
|
@ -242,6 +242,10 @@ function showMenu(ev: MouseEvent, contextmenu = false) {
|
|||
font-size: 80%;
|
||||
}
|
||||
|
||||
.fukidashi {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.content {
|
||||
overflow: clip;
|
||||
overflow-wrap: break-word;
|
||||
|
|
|
@ -5,7 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template>
|
||||
<div class="_gaps">
|
||||
<MkButton primary gradate rounded :class="$style.start" @click="start"><i class="ti ti-plus"></i> {{ i18n.ts.startChat }}</MkButton>
|
||||
<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>
|
||||
|
||||
<MkAd :prefer="['horizontal', 'horizontal-big']"/>
|
||||
|
||||
|
@ -78,6 +80,7 @@ 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();
|
||||
|
||||
|
|
|
@ -41,6 +41,12 @@ 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">
|
||||
|
@ -93,6 +99,7 @@ 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();
|
||||
|
|
|
@ -6,17 +6,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template>
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<MkSpacer :contentMax="800">
|
||||
<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>
|
||||
<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>
|
||||
</MkSpacer>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
|
|
@ -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;
|
||||
setTimeout(() => {
|
||||
window.setTimeout(() => {
|
||||
i++;
|
||||
logPos.value++;
|
||||
previousLog = log;
|
||||
|
|
|
@ -42,22 +42,6 @@ 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">
|
||||
|
@ -395,40 +379,6 @@ 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>
|
||||
|
@ -468,6 +418,76 @@ 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>
|
||||
|
@ -650,6 +670,7 @@ 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);
|
||||
|
@ -678,6 +699,7 @@ watch([
|
|||
highlightSensitiveMedia,
|
||||
enableSeasonalScreenEffect,
|
||||
chatShowSenderName,
|
||||
useStickyIcons,
|
||||
], async () => {
|
||||
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
|
||||
});
|
||||
|
@ -786,7 +808,7 @@ function testNotification(): void {
|
|||
smashCount = 0;
|
||||
}
|
||||
if (smashTimer) {
|
||||
clearTimeout(smashTimer);
|
||||
window.clearTimeout(smashTimer);
|
||||
}
|
||||
smashTimer = window.setTimeout(() => {
|
||||
smashCount = 0;
|
||||
|
|
|
@ -4,47 +4,43 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<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>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
|
||||
import { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } 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';
|
||||
|
@ -133,7 +129,7 @@ function queueUpdated(q: number): void {
|
|||
}
|
||||
|
||||
function top(): void {
|
||||
if (rootEl.value) scroll(rootEl.value, { top: 0, behavior: 'smooth' });
|
||||
if (rootEl.value) scrollInContainer(rootEl.value, { top: 0, behavior: 'instant' });
|
||||
}
|
||||
|
||||
async function chooseList(ev: MouseEvent): Promise<void> {
|
||||
|
|
|
@ -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 { scroll } from '@@/js/scroll.js';
|
||||
import { scrollInContainer } 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() {
|
||||
scroll(rootEl.value, { top: 0 });
|
||||
scrollInContainer(rootEl.value, { top: 0 });
|
||||
}
|
||||
|
||||
function settings() {
|
||||
|
|
|
@ -198,6 +198,9 @@ export const PREF_DEF = {
|
|||
useBlurEffect: {
|
||||
default: DEFAULT_DEVICE_KIND === 'desktop',
|
||||
},
|
||||
useStickyIcons: {
|
||||
default: true,
|
||||
},
|
||||
showFixedPostForm: {
|
||||
default: false,
|
||||
},
|
||||
|
|
|
@ -178,6 +178,14 @@ rt {
|
|||
overflow: clip;
|
||||
overflow-y: scroll;
|
||||
overscroll-behavior: contain;
|
||||
|
||||
/*
|
||||
理屈は知らないけど、ここでbackgroundを設定しておかないと
|
||||
スクロールコンテナーが少なくともChromeにおいて
|
||||
main thread scrolling になってしまい、パフォーマンスが(多分)落ちる。
|
||||
backgroundが透明だと裏側を描画しないといけなくなるとかそういう理由かもしれない
|
||||
*/
|
||||
background: var(--MI_THEME-bg);
|
||||
}
|
||||
|
||||
._pageScrollableReversed {
|
||||
|
|
|
@ -226,13 +226,17 @@ 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%;
|
||||
|
|
|
@ -377,7 +377,7 @@ function onDrop(ev) {
|
|||
font-size: 0.9em;
|
||||
color: var(--MI_THEME-panelHeaderFg);
|
||||
background: var(--MI_THEME-panelHeaderBg);
|
||||
box-shadow: 0 1px 0 0 var(--MI_THEME-panelHeaderDivider);
|
||||
box-shadow: 0 0.5px 0 0 var(--MI_THEME-panelHeaderDivider);
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
|
|
@ -5,119 +5,30 @@ 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">
|
||||
<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>
|
||||
<StackingRouterView v-if="prefer.s['experimental.stackingRouterView']" :class="$style.content"/>
|
||||
<RouterView v-else :class="$style.content"/>
|
||||
</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 { defineAsyncComponent, provide, onMounted, computed, ref, watch, useTemplateRef } from 'vue';
|
||||
import { provide, onMounted, computed, ref } 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;
|
||||
|
@ -131,8 +42,6 @@ window.addEventListener('resize', () => {
|
|||
});
|
||||
|
||||
const pageMetadata = ref<null | PageMetadata>(null);
|
||||
const widgetsShowing = ref(false);
|
||||
const navFooter = useTemplateRef('navFooter');
|
||||
|
||||
provide(DI.router, mainRouter);
|
||||
provideMetadataReceiver((metadataGetter) => {
|
||||
|
@ -148,14 +57,6 @@ 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', () => {
|
||||
|
@ -195,20 +96,6 @@ 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>
|
||||
|
@ -216,13 +103,14 @@ 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%;
|
||||
|
|
|
@ -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 => setTimeout(resolve, (claimingQueue.size - 1) * 500));
|
||||
await new Promise(resolve => window.setTimeout(resolve, (claimingQueue.size - 1) * 500));
|
||||
window.setTimeout(() => {
|
||||
claimingQueue.delete(type);
|
||||
}, 500);
|
||||
|
|
|
@ -290,51 +290,41 @@ 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: 'zydOfGYip',
|
||||
id: '31Y4IcGEf',
|
||||
label: i18n.ts.alwaysConfirmFollow,
|
||||
keywords: ['follow', 'confirm', 'always'],
|
||||
},
|
||||
{
|
||||
id: 'wqpOC22Zm',
|
||||
id: '78q2asrLS',
|
||||
label: i18n.ts.highlightSensitiveMedia,
|
||||
keywords: ['highlight', 'sensitive', 'nsfw', 'image', 'photo', 'picture', 'media', 'thumbnail'],
|
||||
},
|
||||
{
|
||||
id: 'c98gbF9c6',
|
||||
id: 'zydOfGYip',
|
||||
label: i18n.ts.confirmWhenRevealingSensitiveMedia,
|
||||
keywords: ['sensitive', 'nsfw', 'media', 'image', 'photo', 'picture', 'attachment', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: '4LxdiOMNh',
|
||||
id: 'wqpOC22Zm',
|
||||
label: i18n.ts.enableAdvancedMfm,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced'],
|
||||
},
|
||||
{
|
||||
id: '9gTCaLkIf',
|
||||
id: 'c98gbF9c6',
|
||||
label: i18n.ts.enableInfiniteScroll,
|
||||
keywords: ['auto', 'load', 'auto', 'more', 'scroll'],
|
||||
},
|
||||
{
|
||||
id: 'jmJT0twuJ',
|
||||
id: '6ANRSOaNg',
|
||||
label: i18n.ts.emojiStyle,
|
||||
keywords: ['emoji', 'style', 'native', 'system', 'fluent', 'twemoji'],
|
||||
},
|
||||
{
|
||||
id: 'igFN7RIUa',
|
||||
id: 'wo0s0CaI1',
|
||||
label: i18n.ts.pinnedList,
|
||||
keywords: ['pinned', 'list'],
|
||||
},
|
||||
|
@ -343,85 +333,85 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['general'],
|
||||
},
|
||||
{
|
||||
id: 'ufc2X9voy',
|
||||
id: 'l78F2W9Ok',
|
||||
children: [
|
||||
{
|
||||
id: 'd2H4E5ys6',
|
||||
id: 'iOJ3Crlky',
|
||||
label: i18n.ts.showFixedPostForm,
|
||||
keywords: ['post', 'form', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '1LHOhDKGW',
|
||||
id: 'CQldliCSi',
|
||||
label: i18n.ts.showFixedPostFormInChannel,
|
||||
keywords: ['post', 'form', 'timeline', 'channel'],
|
||||
},
|
||||
{
|
||||
id: 'DSzwvTp7i',
|
||||
id: 'd2H4E5ys6',
|
||||
label: i18n.ts.collapseRenotes,
|
||||
keywords: ['renote', i18n.ts.collapseRenotesDescription],
|
||||
},
|
||||
{
|
||||
id: 'jb3HUeyrx',
|
||||
id: 'yb11lSY1G',
|
||||
label: i18n.ts.showGapBetweenNotesInTimeline,
|
||||
keywords: ['note', 'timeline', 'gap'],
|
||||
},
|
||||
{
|
||||
id: '2LNjwv1cr',
|
||||
id: 'fL49Zxe9i',
|
||||
label: i18n.ts.disableStreamingTimeline,
|
||||
keywords: ['disable', 'streaming', 'timeline'],
|
||||
},
|
||||
{
|
||||
id: '7W6g8Dcqz',
|
||||
id: 'ykifk3NHS',
|
||||
label: i18n.ts.showNoteActionsOnlyHover,
|
||||
keywords: ['hover', 'show', 'footer', 'action'],
|
||||
},
|
||||
{
|
||||
id: 'uAOoH3LFF',
|
||||
id: 'tLGyaQagB',
|
||||
label: i18n.ts.showClipButtonInNoteFooter,
|
||||
keywords: ['footer', 'action', 'clip', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'eCiyZLC8n',
|
||||
id: '7W6g8Dcqz',
|
||||
label: i18n.ts.showReactionsCount,
|
||||
keywords: ['reaction', 'count', 'show'],
|
||||
},
|
||||
{
|
||||
id: '68u9uRmFP',
|
||||
id: 'uAOoH3LFF',
|
||||
label: i18n.ts.confirmOnReact,
|
||||
keywords: ['reaction', 'confirm'],
|
||||
},
|
||||
{
|
||||
id: 'rHWm4sXIe',
|
||||
id: 'eCiyZLC8n',
|
||||
label: i18n.ts.loadRawImages,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'quality', 'raw', 'attachment'],
|
||||
},
|
||||
{
|
||||
id: '9L2XGJw7e',
|
||||
id: '68u9uRmFP',
|
||||
label: i18n.ts.useReactionPickerForContextMenu,
|
||||
keywords: ['reaction', 'picker', 'contextmenu', 'open'],
|
||||
},
|
||||
{
|
||||
id: 'uIMCIK7kG',
|
||||
id: 'yxehrHZ6x',
|
||||
label: i18n.ts.reactionsDisplaySize,
|
||||
keywords: ['reaction', 'size', 'scale', 'display'],
|
||||
},
|
||||
{
|
||||
id: 'uMckjO9bz',
|
||||
id: 'gi8ILaE2Z',
|
||||
label: i18n.ts.limitWidthOfReaction,
|
||||
keywords: ['reaction', 'size', 'scale', 'display', 'width', 'limit'],
|
||||
},
|
||||
{
|
||||
id: 'yeghU4qiH',
|
||||
id: 'cEQJZ7DQG',
|
||||
label: i18n.ts.mediaListWithOneImageAppearance,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'list', 'size', 'height'],
|
||||
},
|
||||
{
|
||||
id: 'yYSOPoAKE',
|
||||
id: 'haX4QVulD',
|
||||
label: i18n.ts.instanceTicker,
|
||||
keywords: ['ticker', 'information', 'label', 'instance', 'server', 'host', 'federation'],
|
||||
},
|
||||
{
|
||||
id: 'iOHiIu32L',
|
||||
id: 'pneYnQekL',
|
||||
label: i18n.ts.displayOfSensitiveMedia,
|
||||
keywords: ['attachment', 'image', 'photo', 'picture', 'media', 'thumbnail', 'nsfw', 'sensitive', 'display', 'show', 'hide', 'visibility'],
|
||||
},
|
||||
|
@ -430,25 +420,25 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['timeline', 'note'],
|
||||
},
|
||||
{
|
||||
id: 'eROFRMtXv',
|
||||
id: 'eJ2jme16W',
|
||||
children: [
|
||||
{
|
||||
id: 'BaQfrVO82',
|
||||
id: 'ErMQr6LQk',
|
||||
label: i18n.ts.keepCw,
|
||||
keywords: ['remember', 'keep', 'note', 'cw'],
|
||||
},
|
||||
{
|
||||
id: 'vFerPo2he',
|
||||
id: 'zrJicawH9',
|
||||
label: i18n.ts.rememberNoteVisibility,
|
||||
keywords: ['remember', 'keep', 'note', 'visibility'],
|
||||
},
|
||||
{
|
||||
id: 'dcAC0yJcH',
|
||||
id: 'BaQfrVO82',
|
||||
label: i18n.ts.enableQuickAddMfmFunction,
|
||||
keywords: ['mfm', 'enable', 'show', 'advanced', 'picker', 'form', 'function', 'fn'],
|
||||
},
|
||||
{
|
||||
id: 'bECeWZVMb',
|
||||
id: 'C2WYcVM1d',
|
||||
label: i18n.ts.defaultNoteVisibility,
|
||||
keywords: ['default', 'note', 'visibility'],
|
||||
},
|
||||
|
@ -457,20 +447,20 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['post', 'form'],
|
||||
},
|
||||
{
|
||||
id: 'tsSP93Cc6',
|
||||
id: 'sQXSA6gik',
|
||||
children: [
|
||||
{
|
||||
id: 'dtw8FepYL',
|
||||
id: 'rICn8stqk',
|
||||
label: i18n.ts.useGroupedNotifications,
|
||||
keywords: ['group'],
|
||||
},
|
||||
{
|
||||
id: 'eb0yCYJTn',
|
||||
id: 'xFmAg2tDe',
|
||||
label: i18n.ts.position,
|
||||
keywords: ['position'],
|
||||
},
|
||||
{
|
||||
id: '1Spt4Gpr5',
|
||||
id: 'Ek4Cw3VPq',
|
||||
label: i18n.ts.stackAxis,
|
||||
keywords: ['stack', 'axis', 'direction'],
|
||||
},
|
||||
|
@ -479,20 +469,15 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['notification'],
|
||||
},
|
||||
{
|
||||
id: 'SYmWxGOF',
|
||||
label: i18n.ts.dataSaver,
|
||||
keywords: ['datasaver'],
|
||||
},
|
||||
{
|
||||
id: 'vPQPvmntL',
|
||||
id: 'gDVCqZfxm',
|
||||
children: [
|
||||
{
|
||||
id: 'zZxyXHk3A',
|
||||
id: 'ei8Ix3s4S',
|
||||
label: i18n.ts._settings._chat.showSenderName,
|
||||
keywords: ['show', 'sender', 'name'],
|
||||
},
|
||||
{
|
||||
id: 'omEy5Q3Ev',
|
||||
id: '2E7vdIUQd',
|
||||
label: i18n.ts._settings._chat.sendOnEnter,
|
||||
keywords: ['send', 'enter', 'newline'],
|
||||
},
|
||||
|
@ -501,50 +486,77 @@ export const searchIndexes: SearchIndexItem[] = [
|
|||
keywords: ['chat', 'messaging'],
|
||||
},
|
||||
{
|
||||
id: '5fy7VEy6i',
|
||||
id: '96LnS1sxB',
|
||||
children: [
|
||||
{
|
||||
id: 'EosiWZvak',
|
||||
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',
|
||||
label: i18n.ts.squareAvatars,
|
||||
keywords: ['avatar', 'icon', 'square'],
|
||||
},
|
||||
{
|
||||
id: 'qY5xTzl35',
|
||||
id: 'xNsLokqeA',
|
||||
label: i18n.ts.seasonalScreenEffect,
|
||||
keywords: ['effect', 'show'],
|
||||
},
|
||||
{
|
||||
id: '2VSnj81vC',
|
||||
id: 'sZcalFBE8',
|
||||
label: i18n.ts.openImageInNewTab,
|
||||
keywords: ['image', 'photo', 'picture', 'media', 'thumbnail', 'new', 'tab'],
|
||||
},
|
||||
{
|
||||
id: 'hdQa7W2H1',
|
||||
id: 'Eh7vTluDO',
|
||||
label: i18n.ts.withRepliesByDefaultForNewlyFollowed,
|
||||
keywords: ['follow', 'replies'],
|
||||
},
|
||||
{
|
||||
id: 'nnj4DkjhP',
|
||||
id: 'vTRSKf1JA',
|
||||
label: i18n.ts.whenServerDisconnected,
|
||||
keywords: ['server', 'disconnect', 'reconnect', 'reload', 'streaming'],
|
||||
},
|
||||
{
|
||||
id: 'Eh7vTluDO',
|
||||
id: 'zlO5cBZFH',
|
||||
label: i18n.ts.numberOfPageCache,
|
||||
keywords: ['cache', 'page'],
|
||||
},
|
||||
{
|
||||
id: 'vTRSKf1JA',
|
||||
id: 'huQ8nc4iD',
|
||||
label: i18n.ts.forceShowAds,
|
||||
keywords: ['ad', 'show'],
|
||||
},
|
||||
{
|
||||
id: 'dwhQfcLGt',
|
||||
id: 'nJWfqwQ4R',
|
||||
label: i18n.ts.hemisphere,
|
||||
keywords: [],
|
||||
},
|
||||
{
|
||||
id: 'Ar1lj7f7U',
|
||||
id: 'kwEEgTlwR',
|
||||
label: i18n.ts.additionalEmojiDictionary,
|
||||
keywords: ['emoji', 'dictionary', 'additional', 'extra'],
|
||||
},
|
||||
|
|
|
@ -15,11 +15,11 @@ export function confetti(options: { duration?: number; } = {}) {
|
|||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const interval = window.setInterval(() => {
|
||||
const timeLeft = animationEnd - Date.now();
|
||||
|
||||
if (timeLeft <= 0) {
|
||||
return clearInterval(interval);
|
||||
return window.clearInterval(interval);
|
||||
}
|
||||
|
||||
const particleCount = 50 * (timeLeft / duration);
|
||||
|
|
|
@ -362,12 +362,18 @@ 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} ` });
|
||||
},
|
||||
}, {
|
||||
type: 'link',
|
||||
icon: 'ti ti-messages',
|
||||
text: i18n.ts._chat.chatWithThisUser,
|
||||
to: `/chat/user/${user.id}`,
|
||||
}, { type: 'divider' }, {
|
||||
});
|
||||
|
||||
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' }, {
|
||||
icon: user.isMuted ? 'ti ti-eye' : 'ti ti-eye-off',
|
||||
text: user.isMuted ? i18n.ts.unmute : i18n.ts.mute,
|
||||
action: toggleMute,
|
||||
|
|
|
@ -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) {
|
||||
clearTimeout(lastHotKeyStoreTimer);
|
||||
window.clearTimeout(lastHotKeyStoreTimer);
|
||||
}
|
||||
|
||||
latestHotkey = {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
const requestIdleCallback: typeof globalThis.requestIdleCallback = globalThis.requestIdleCallback ?? ((callback) => {
|
||||
const start = performance.now();
|
||||
const timeoutId = setTimeout(() => {
|
||||
const timeoutId = window.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) => {
|
||||
clearTimeout(timeoutId);
|
||||
window.clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
class IdlingRenderScheduler {
|
||||
|
|
|
@ -158,7 +158,7 @@ export async function playMisskeySfxFile(soundStore: SoundStore): Promise<boolea
|
|||
canPlay = false;
|
||||
return await playMisskeySfxFileInternal(soundStore).finally(() => {
|
||||
// ごく短時間に音が重複しないように
|
||||
setTimeout(() => {
|
||||
window.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 = setInterval(() => {
|
||||
const si = window.setInterval(() => {
|
||||
if (audioEl.readyState > 0) {
|
||||
resolve(audioEl.duration * 1000);
|
||||
clearInterval(si);
|
||||
window.clearInterval(si);
|
||||
audioEl.remove();
|
||||
}
|
||||
}, 100);
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
|
||||
export async function tick(): Promise<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
await new Promise((globalThis.requestIdleCallback ?? setTimeout) as never);
|
||||
await new Promise((globalThis.requestIdleCallback ?? window.setTimeout) as never);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.3.2-beta.14",
|
||||
"version": "2025.3.2-beta.16",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
|
|
|
@ -4057,6 +4057,7 @@ 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;
|
||||
|
|
Loading…
Reference in New Issue