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: テーマ設定画面のデザインを改善
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
/**
|
||||
* ナビゲーションバーに副ボタンを表示
|
||||
*/
|
||||
|
|
|
@ -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: "オフのとき"
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -3,19 +3,17 @@
|
|||
* 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 = {
|
||||
|
|
|
@ -3,17 +3,15 @@
|
|||
* 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;
|
||||
|
||||
|
|
|
@ -3,10 +3,9 @@
|
|||
* 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