Merge branch 'develop' into fix-13913
This commit is contained in:
commit
b5392d4731
|
@ -16,7 +16,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout head
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4.4.0
|
||||
with:
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
@ -66,7 +66,7 @@ jobs:
|
|||
if: ${{ github.event.pull_request.mergeable == null || github.event.pull_request.mergeable == true }}
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Check version
|
||||
run: |
|
||||
if [ "$(jq -r '.version' package.json)" != "$(jq -r '.version' packages/misskey-js/package.json)" ]; then
|
||||
|
|
|
@ -12,7 +12,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Check
|
||||
run: |
|
||||
counter=0
|
||||
|
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
check_copyright_year:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
- run: |
|
||||
if [ "$(grep Copyright COPYING | sed -e 's/.*2014-\([0-9]*\) .*/\1/g')" -ne "$(date +%Y)" ]; then
|
||||
echo "Please change copyright year!"
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
wait_time: ${{ steps.get-wait-time.outputs.wait_time }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Check allowed users
|
||||
id: check-allowed-users
|
||||
|
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Log in to Docker Hub
|
||||
|
|
|
@ -32,7 +32,7 @@ jobs:
|
|||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Docker meta
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
DOCKER_CONTENT_TRUST: 1
|
||||
DOCKLE_VERSION: 0.4.14
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
- name: Download and install dockle v${{ env.DOCKLE_VERSION }}
|
||||
run: |
|
||||
curl -L -o dockle.deb "https://github.com/goodwithtech/dockle/releases/download/v${DOCKLE_VERSION}/dockle_${DOCKLE_VERSION}_Linux-64bit.deb"
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
ref: refs/pull/${{ github.event.number }}/merge
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
ref: ${{ matrix.ref }}
|
||||
submodules: true
|
||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
|||
pnpm_install:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
@ -69,7 +69,7 @@ jobs:
|
|||
eslint-cache-version: v1
|
||||
eslint-cache-path: ${{ github.workspace }}/node_modules/.cache/eslint-${{ matrix.workspace }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
@ -81,7 +81,7 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- name: Restore eslint cache
|
||||
uses: actions/cache@v4.2.3
|
||||
uses: actions/cache@v4.2.4
|
||||
with:
|
||||
path: ${{ env.eslint-cache-path }}
|
||||
key: eslint-${{ env.eslint-cache-version }}-${{ matrix.workspace }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||
|
@ -99,7 +99,7 @@ jobs:
|
|||
- sw
|
||||
- misskey-js
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
|
|
@ -22,12 +22,12 @@ jobs:
|
|||
NODE_OPTIONS: "--max_old_space_size=7168"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
if: github.event_name != 'pull_request_target'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
if: github.event_name == 'pull_request_target'
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
|
|
@ -50,7 +50,7 @@ jobs:
|
|||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
@ -129,7 +129,7 @@ jobs:
|
|||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
@ -173,7 +173,7 @@ jobs:
|
|||
POSTGRES_HOST_AUTH_METHOD: trust
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
@ -76,7 +76,7 @@ jobs:
|
|||
- 56312:6379
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
# https://github.com/cypress-io/cypress-docker-images/issues/150
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.2.2
|
||||
uses: actions/checkout@v4.3.0
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
|
|
@ -16,7 +16,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
- uses: actions/checkout@v4.3.0
|
||||
with:
|
||||
submodules: true
|
||||
- name: Setup pnpm
|
||||
|
|
|
@ -54,6 +54,8 @@
|
|||
- Fix: 複数のメンションを1行に記述した場合に、サジェストが正しく表示されない問題を修正
|
||||
- Fix: メンションとしての条件を満たしていても、特定の条件(`-`が含まれる場合など)で正しくサジェストされない問題を一部修正
|
||||
- Fix: ユーザーの前後ノートを閲覧する機能が動作しない問題を修正
|
||||
- Fix: 照会ダイアログでap/showでローカルユーザーを解決した際@username@nullに飛ばされる問題を修正
|
||||
- Fix: アイコンのデコレーションを付ける際にデコレーションが表示されなくなる問題を修正
|
||||
- Fix: Ctrlキー(Commandキー)を押下しながらリンクをクリックすると新しいタブで開くように
|
||||
|
||||
### Server
|
||||
|
@ -68,6 +70,7 @@
|
|||
- Fix: `notes/mentions` で場合によっては並び順が正しく返されない問題を修正
|
||||
- Fix: SystemWebhook設定でsecretを空に出来ない問題を修正
|
||||
- Fix: 削除されたユーザーがチャットメッセージにリアクションしている場合`chat/history`などでエラーになる問題を修正
|
||||
- Fix: Pageのアイキャッチ画像をドライブから消してもPageごと消えないように
|
||||
|
||||
|
||||
## 2025.7.0
|
||||
|
|
|
@ -1245,6 +1245,7 @@ releaseToRefresh: "Release to refresh"
|
|||
refreshing: "Refreshing..."
|
||||
pullDownToRefresh: "Pull down to refresh"
|
||||
useGroupedNotifications: "Display grouped notifications"
|
||||
emailVerificationFailedError: "A problem occurred while verifying your email address. The link may have expired."
|
||||
cwNotationRequired: "If \"Hide content\" is enabled, a description must be provided."
|
||||
doReaction: "Add reaction"
|
||||
code: "Code"
|
||||
|
@ -1375,6 +1376,7 @@ safeModeEnabled: "Safe mode is enabled"
|
|||
pluginsAreDisabledBecauseSafeMode: "All plugins are disabled because safe mode is enabled."
|
||||
customCssIsDisabledBecauseSafeMode: "Custom CSS is not applied because safe mode is enabled."
|
||||
themeIsDefaultBecauseSafeMode: "While safe mode is active, the default theme is used. Disabling safe mode will revert these changes."
|
||||
thankYouForTestingBeta: "Thank you for helping us test the beta version!"
|
||||
_order:
|
||||
newest: "Newest First"
|
||||
oldest: "Oldest First"
|
||||
|
@ -1664,6 +1666,9 @@ _serverSettings:
|
|||
userGeneratedContentsVisibilityForVisitor_description2: "Unconditionally publishing all content on the server to the Internet, including remote content received by the server is risky. This is especially important for guests who are unaware of the distributed nature of the content, as they may mistakenly believe that even remote content is content created by users on the server."
|
||||
restartServerSetupWizardConfirm_title: "Restart server setup wizard?"
|
||||
restartServerSetupWizardConfirm_text: "Some current settings will be reset."
|
||||
entrancePageStyle: "Entrance page style"
|
||||
showTimelineForVisitor: "Show timeline"
|
||||
showActivityiesForVisitor: "Show activities"
|
||||
_userGeneratedContentsVisibilityForVisitor:
|
||||
all: "Everything is public"
|
||||
localOnly: "Only local content is published, remote content is kept private"
|
||||
|
|
|
@ -1054,6 +1054,7 @@ permissionDeniedError: "Operación denegada"
|
|||
permissionDeniedErrorDescription: "Esta cuenta no tiene permisos para hacer esa acción."
|
||||
preset: "Predefinido"
|
||||
selectFromPresets: "Escoger desde predefinidos"
|
||||
custom: "Personalizado"
|
||||
achievements: "Logros"
|
||||
gotInvalidResponseError: "Respuesta del servidor inválida"
|
||||
gotInvalidResponseErrorDescription: "Puede que el servidor esté caído o en mantenimiento. Favor de intentar más tarde"
|
||||
|
@ -1244,6 +1245,7 @@ releaseToRefresh: "Soltar para recargar"
|
|||
refreshing: "Recargando..."
|
||||
pullDownToRefresh: "Tira hacia abajo para recargar"
|
||||
useGroupedNotifications: "Mostrar notificaciones agrupadas"
|
||||
emailVerificationFailedError: "Se ha producido un error al confirmar tu dirección de correo electrónico. Es posible que el enlace haya caducado."
|
||||
cwNotationRequired: "Si se ha activado \"ocultar contenido\", es necesario proporcionar una descripción."
|
||||
doReaction: "Añadir reacción"
|
||||
code: "Código"
|
||||
|
@ -1374,6 +1376,7 @@ safeModeEnabled: "El modo seguro está activado"
|
|||
pluginsAreDisabledBecauseSafeMode: "El modo seguro está activado, por lo que todos los plugins están desactivados."
|
||||
customCssIsDisabledBecauseSafeMode: "El modo seguro está activado, por lo que no se aplica el CSS personalizado."
|
||||
themeIsDefaultBecauseSafeMode: "Mientras el modo seguro esté activado, se utilizará el tema predeterminado. Cuando se desactive el modo seguro, se volverá al tema original."
|
||||
thankYouForTestingBeta: "¡Gracias por tu colaboración en la prueba de la versión beta!"
|
||||
_order:
|
||||
newest: "Los más recientes primero"
|
||||
oldest: "Los más antiguos primero"
|
||||
|
@ -1663,6 +1666,9 @@ _serverSettings:
|
|||
userGeneratedContentsVisibilityForVisitor_description2: "Publicar incondicionalmente todo el contenido del servidor en Internet, incluido el contenido remoto recibido por el servidor, es arriesgado. Esto es especialmente importante para los invitados que desconocen la naturaleza distribuida del contenido, ya que pueden creer erróneamente que incluso el contenido remoto es contenido creado por usuarios en el servidor."
|
||||
restartServerSetupWizardConfirm_title: "¿Reiniciar el asistente de configuración del servidor?"
|
||||
restartServerSetupWizardConfirm_text: "Algunas configuraciones actuales se restablecerán"
|
||||
entrancePageStyle: "Estilo de la página de inicio"
|
||||
showTimelineForVisitor: "Mostrar la línea de tiempo"
|
||||
showActivityiesForVisitor: "Mostrar actividades"
|
||||
_userGeneratedContentsVisibilityForVisitor:
|
||||
all: "Todo es público."
|
||||
localOnly: "Sólo se publica el contenido local, el remoto se mantiene privado"
|
||||
|
@ -2272,6 +2278,7 @@ _time:
|
|||
minute: "Minutos"
|
||||
hour: "Horas"
|
||||
day: "Días"
|
||||
month: "Mes(es)"
|
||||
_2fa:
|
||||
alreadyRegistered: "Ya has completado la configuración."
|
||||
registerTOTP: "Registrar aplicación autenticadora"
|
||||
|
|
|
@ -6633,7 +6633,7 @@ export interface Locale extends ILocale {
|
|||
/**
|
||||
* アクティビティを表示する
|
||||
*/
|
||||
"showActivityiesForVisitor": string;
|
||||
"showActivitiesForVisitor": string;
|
||||
"_userGeneratedContentsVisibilityForVisitor": {
|
||||
/**
|
||||
* 全て公開
|
||||
|
|
|
@ -1685,7 +1685,7 @@ _serverSettings:
|
|||
restartServerSetupWizardConfirm_text: "現在の一部の設定はリセットされます。"
|
||||
entrancePageStyle: "エントランスページのスタイル"
|
||||
showTimelineForVisitor: "タイムラインを表示する"
|
||||
showActivityiesForVisitor: "アクティビティを表示する"
|
||||
showActivitiesForVisitor: "アクティビティを表示する"
|
||||
|
||||
_userGeneratedContentsVisibilityForVisitor:
|
||||
all: "全て公開"
|
||||
|
|
|
@ -31,7 +31,7 @@ openInWindow: "Pencerede aç"
|
|||
profile: "Profil"
|
||||
timeline: "Pano"
|
||||
noAccountDescription: "Bu kullanıcı henüz biyografisini yazmamış."
|
||||
login: "Giriş Yap"
|
||||
login: "Oturum Aç"
|
||||
loggingIn: "Giriş Yapılıyor..."
|
||||
logout: "Çıkış Yap"
|
||||
signup: "Kaydol"
|
||||
|
@ -310,7 +310,7 @@ agreeBelow: "Aşağıdakileri kabul ediyorum"
|
|||
basicNotesBeforeCreateAccount: "Önemli notlar"
|
||||
termsOfService: "Hizmet Şartları"
|
||||
start: "Başla"
|
||||
home: "Ana sayfa"
|
||||
home: "Pano"
|
||||
remoteUserCaution: "Bu kullanıcı uzak bir sunucudan geldiği için, gösterilen bilgiler eksik olabilir."
|
||||
activity: "Etkinlik"
|
||||
images: "Görseller"
|
||||
|
@ -1054,6 +1054,7 @@ permissionDeniedError: "İşlem reddedildi"
|
|||
permissionDeniedErrorDescription: "Bu hesap bu işlemi gerçekleştirmek için gerekli izne sahip değildir."
|
||||
preset: "Ön ayar"
|
||||
selectFromPresets: "Ön ayarlardan seçim yapın"
|
||||
custom: "Özel"
|
||||
achievements: "Başarılar"
|
||||
gotInvalidResponseError: "Geçersiz sunucu yanıtı"
|
||||
gotInvalidResponseErrorDescription: "Sunucu erişilemez durumda olabilir veya bakım çalışması yapılmaktadır. Lütfen daha sonra tekrar dene."
|
||||
|
@ -1066,8 +1067,8 @@ collapseRenotesDescription: "Zaten yanıtladığın veya renote aldığın notla
|
|||
internalServerError: "İç Sunucu Hatası"
|
||||
internalServerErrorDescription: "Sunucu beklenmedik bir hatayla karşılaştı."
|
||||
copyErrorInfo: "Hata ayrıntılarını kopyala"
|
||||
joinThisServer: "Bu sunucuda kaydolun"
|
||||
exploreOtherServers: "Başka bir sunucu arayın"
|
||||
joinThisServer: "Kaydol"
|
||||
exploreOtherServers: "Diğer sunucuları keşfet"
|
||||
letsLookAtTimeline: "Pano'ya bir göz atın"
|
||||
disableFederationConfirm: "Federasyonu cidden devre dışı bırakmak istiyor musun?"
|
||||
disableFederationConfirmWarn: "Federasyondan ayrılsa bile, aksi belirtilmedikçe gönderiler herkese açık olmaya devam edecek. Genellikle bunu yapmanız gerekmez."
|
||||
|
@ -1244,6 +1245,7 @@ releaseToRefresh: "Yenilemek için serbest bırak"
|
|||
refreshing: "Yenileniyor..."
|
||||
pullDownToRefresh: "Yenilemek için aşağı çekin"
|
||||
useGroupedNotifications: "Gruplandırılmış bildirimleri göster"
|
||||
emailVerificationFailedError: "E-posta adresi doğrulanırken bir sorun oluştu. Bağlantının geçerlilik süresi dolmuş olabilir."
|
||||
cwNotationRequired: "“İçeriği gizle” seçeneği etkinleştirilirse, bir açıklama sağlanmalı."
|
||||
doReaction: "Tepki ekle"
|
||||
code: "Kod"
|
||||
|
@ -1374,6 +1376,7 @@ safeModeEnabled: "Güvenli mod etkinleştirildi"
|
|||
pluginsAreDisabledBecauseSafeMode: "Güvenli mod etkinleştirildiği için tüm eklentiler devre dışı bırakılmıştır."
|
||||
customCssIsDisabledBecauseSafeMode: "Güvenli mod etkin olduğu için özel CSS uygulanmıyor."
|
||||
themeIsDefaultBecauseSafeMode: "Güvenli mod etkinken, varsayılan tema kullanılır. Güvenli modu devre dışı bırakmak bu değişiklikleri geri alır."
|
||||
thankYouForTestingBeta: "Beta sürümünü test ettiğin için teşekkür ederiz!"
|
||||
_order:
|
||||
newest: "Önce yeni"
|
||||
oldest: "Önce eski"
|
||||
|
@ -1622,7 +1625,7 @@ _initialTutorial:
|
|||
_timelineDescription:
|
||||
home: "Ana Pano'da, takip ettiğin hesapların notlarını görebilirsin."
|
||||
local: "Yerel Pano'da, bu sunucudaki tüm kullanıcıların notlarını görebilirsin."
|
||||
social: "Sosyal Pano, Ana Sayfa ve Yerel Pano'dan gelen notları görüntüler."
|
||||
social: "Pano, Sosyal Pano ve Yerel Pano'dan gelen notları görüntüler."
|
||||
global: "Global Pano'da, bağlı tüm sunuculardan gelen notları görebilirsin."
|
||||
_serverRules:
|
||||
description: "Kayıt öncesinde gösterilecek bir dizi kural. Hizmet Şartlarının özetini belirlemen önerilir."
|
||||
|
@ -1663,6 +1666,9 @@ _serverSettings:
|
|||
userGeneratedContentsVisibilityForVisitor_description2: "Sunucu tarafından alınan uzak içerik dahil olmak üzere sunucudaki tüm içeriği koşulsuz olarak İnternet'e yayınlamak risklidir. Bu, içeriğin dağıtılmış yapısından haberdar olmayan misafirler için özellikle önemlidir, çünkü onlar yanlışlıkla uzak içeriğin bile sunucudaki kullanıcılar tarafından oluşturulan içerik olduğunu düşünebilirler."
|
||||
restartServerSetupWizardConfirm_title: "Sunucu kurulum sihirbazını yeniden başlatmak ister misin?"
|
||||
restartServerSetupWizardConfirm_text: "Bazı mevcut ayarlar sıfırlanacaktır."
|
||||
entrancePageStyle: "Giriş sayfası stili"
|
||||
showTimelineForVisitor: "Panoyu göster"
|
||||
showActivityiesForVisitor: "Aktiviteleri göster"
|
||||
_userGeneratedContentsVisibilityForVisitor:
|
||||
all: "Her şey halka açıktır."
|
||||
localOnly: "Yalnızca yerel içerik yayınlanır, uzak içerik gizli tutulur."
|
||||
|
@ -2272,6 +2278,7 @@ _time:
|
|||
minute: "Dakika(lar)"
|
||||
hour: "Saat(ler)"
|
||||
day: "Gün(ler)"
|
||||
month: "Ay"
|
||||
_2fa:
|
||||
alreadyRegistered: "2fa kimlik doğrulama cihazını zaten kaydettin."
|
||||
registerTOTP: "Kimlik doğrulama uygulamasını kaydet"
|
||||
|
@ -2477,7 +2484,7 @@ _poll:
|
|||
_visibility:
|
||||
public: "Halka açık"
|
||||
publicDescription: "Notunuz tüm kullanıcılar tarafından görülebilir olacaktır."
|
||||
home: "Ana sayfa"
|
||||
home: "Pano"
|
||||
homeDescription: "Yalnızca ana zaman çizelgesine gönder"
|
||||
followers: "Takipçiler"
|
||||
followersDescription: "Sadece takipçilerine görünür hale getir"
|
||||
|
@ -2553,7 +2560,7 @@ _instanceCharts:
|
|||
files: "Dosya sayısındaki fark"
|
||||
filesTotal: "Toplam dosya sayısı"
|
||||
_timelines:
|
||||
home: "Ana Sayfa"
|
||||
home: "Pano"
|
||||
local: "Yerel"
|
||||
social: "Sosyal"
|
||||
global: "Global"
|
||||
|
@ -2671,7 +2678,7 @@ _notification:
|
|||
chatRoomInvitationReceived: "Sohbet odasına davet edildi"
|
||||
achievementEarned: "Başarı kilidi açıldı"
|
||||
exportCompleted: "İhracat işlemi tamamlandı."
|
||||
login: "Giriş Yap"
|
||||
login: "Oturum Aç"
|
||||
createToken: "Erişim jetonu oluştur"
|
||||
test: "Bildirim testi"
|
||||
app: "Bağlı uygulamalardan gelen bildirimler"
|
||||
|
@ -2708,7 +2715,7 @@ _deck:
|
|||
main: "Ana"
|
||||
widgets: "Widget'lar"
|
||||
notifications: "Bildirimler"
|
||||
tl: "Ana Sayfa"
|
||||
tl: "Pano"
|
||||
antenna: "Antenler"
|
||||
list: "Liste"
|
||||
channel: "Kanal"
|
||||
|
|
18
package.json
18
package.json
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.8.0-beta.3",
|
||||
"version": "2025.8.0-beta.4",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/misskey-dev/misskey.git"
|
||||
},
|
||||
"packageManager": "pnpm@10.14.0",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"workspaces": [
|
||||
"packages/frontend-shared",
|
||||
"packages/frontend",
|
||||
|
@ -53,8 +53,8 @@
|
|||
"lodash": "4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"cssnano": "7.1.0",
|
||||
"esbuild": "0.25.8",
|
||||
"cssnano": "7.1.1",
|
||||
"esbuild": "0.25.9",
|
||||
"execa": "9.6.0",
|
||||
"fast-glob": "3.3.3",
|
||||
"glob": "11.0.3",
|
||||
|
@ -67,15 +67,15 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/node": "22.17.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.39.0",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"@types/node": "22.17.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "14.5.4",
|
||||
"eslint": "9.33.0",
|
||||
"eslint": "9.34.0",
|
||||
"globals": "16.3.0",
|
||||
"ncp": "2.0.0",
|
||||
"pnpm": "10.14.0",
|
||||
"pnpm": "10.15.0",
|
||||
"start-server-and-test": "2.0.13"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NonCascadingPageEyeCatching1756062689648 {
|
||||
name = 'NonCascadingPageEyeCatching1756062689648'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`);
|
||||
await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "page" DROP CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa"`);
|
||||
await queryRunner.query(`ALTER TABLE "page" ADD CONSTRAINT "FK_a9ca79ad939bf06066b81c9d3aa" FOREIGN KEY ("eyeCatchingImageId") REFERENCES "drive_file"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
|
@ -39,17 +39,17 @@
|
|||
},
|
||||
"optionalDependencies": {
|
||||
"@swc/core-android-arm64": "1.3.11",
|
||||
"@swc/core-darwin-arm64": "1.13.3",
|
||||
"@swc/core-darwin-x64": "1.13.3",
|
||||
"@swc/core-darwin-arm64": "1.13.4",
|
||||
"@swc/core-darwin-x64": "1.13.4",
|
||||
"@swc/core-freebsd-x64": "1.3.11",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.13.3",
|
||||
"@swc/core-linux-arm64-gnu": "1.13.3",
|
||||
"@swc/core-linux-arm64-musl": "1.13.3",
|
||||
"@swc/core-linux-x64-gnu": "1.13.3",
|
||||
"@swc/core-linux-x64-musl": "1.13.3",
|
||||
"@swc/core-win32-arm64-msvc": "1.13.3",
|
||||
"@swc/core-win32-ia32-msvc": "1.13.3",
|
||||
"@swc/core-win32-x64-msvc": "1.13.3",
|
||||
"@swc/core-linux-arm-gnueabihf": "1.13.4",
|
||||
"@swc/core-linux-arm64-gnu": "1.13.4",
|
||||
"@swc/core-linux-arm64-musl": "1.13.4",
|
||||
"@swc/core-linux-x64-gnu": "1.13.4",
|
||||
"@swc/core-linux-x64-musl": "1.13.4",
|
||||
"@swc/core-win32-arm64-msvc": "1.13.4",
|
||||
"@swc/core-win32-ia32-msvc": "1.13.4",
|
||||
"@swc/core-win32-x64-msvc": "1.13.4",
|
||||
"@tensorflow/tfjs": "4.22.0",
|
||||
"@tensorflow/tfjs-node": "4.22.0",
|
||||
"bufferutil": "4.0.9",
|
||||
|
@ -69,8 +69,8 @@
|
|||
"utf-8-validate": "6.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.864.0",
|
||||
"@aws-sdk/lib-storage": "3.864.0",
|
||||
"@aws-sdk/client-s3": "3.873.0",
|
||||
"@aws-sdk/lib-storage": "3.873.0",
|
||||
"@discordapp/twemoji": "16.0.1",
|
||||
"@fastify/accepts": "5.0.2",
|
||||
"@fastify/cookie": "11.0.2",
|
||||
|
@ -93,7 +93,7 @@
|
|||
"@sinonjs/fake-timers": "11.3.1",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.7.8",
|
||||
"@swc/core": "1.13.3",
|
||||
"@swc/core": "1.13.4",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@types/redis-info": "3.0.3",
|
||||
"accepts": "1.3.8",
|
||||
|
@ -103,10 +103,10 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.3",
|
||||
"bullmq": "5.56.9",
|
||||
"bullmq": "5.58.1",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.5.0",
|
||||
"chalk": "5.6.0",
|
||||
"chalk-template": "1.1.0",
|
||||
"chokidar": "4.0.3",
|
||||
"cli-highlight": "2.1.11",
|
||||
|
@ -114,7 +114,7 @@
|
|||
"content-disposition": "0.5.4",
|
||||
"date-fns": "2.30.0",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"fastify": "5.4.0",
|
||||
"fastify": "5.5.0",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.6.0",
|
||||
|
@ -135,7 +135,7 @@
|
|||
"jsonld": "8.3.3",
|
||||
"jsrsasign": "11.1.0",
|
||||
"juice": "11.0.1",
|
||||
"meilisearch": "0.51.0",
|
||||
"meilisearch": "0.52.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"microformats-parser": "2.0.4",
|
||||
"mime-types": "2.1.35",
|
||||
|
@ -151,7 +151,7 @@
|
|||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.4.0",
|
||||
"otpauth": "9.4.1",
|
||||
"parse5": "7.3.0",
|
||||
"pg": "8.16.3",
|
||||
"pkce-challenge": "4.1.0",
|
||||
|
@ -177,10 +177,10 @@
|
|||
"stringz": "2.1.0",
|
||||
"systeminformation": "5.27.7",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.4",
|
||||
"tmp": "0.2.5",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.25",
|
||||
"typeorm": "0.3.26",
|
||||
"typescript": "5.9.2",
|
||||
"ulid": "2.4.0",
|
||||
"vary": "1.1.2",
|
||||
|
@ -191,7 +191,7 @@
|
|||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@nestjs/platform-express": "10.4.20",
|
||||
"@sentry/vue": "9.45.0",
|
||||
"@sentry/vue": "9.46.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
|
@ -210,8 +210,8 @@
|
|||
"@types/jsrsasign": "10.5.15",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "22.17.1",
|
||||
"@types/nodemailer": "6.4.17",
|
||||
"@types/node": "22.17.2",
|
||||
"@types/nodemailer": "6.4.19",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
|
@ -231,8 +231,8 @@
|
|||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.39.0",
|
||||
"@typescript-eslint/parser": "8.39.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"aws-sdk-client-mock": "4.1.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
|
|
|
@ -69,7 +69,7 @@ export class MiPage {
|
|||
public eyeCatchingImageId: MiDriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => MiDriveFile, {
|
||||
onDelete: 'CASCADE',
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
public eyeCatchingImage: MiDriveFile | null;
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"@rollup/pluginutils": "5.2.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "3.5.18",
|
||||
"@vue/compiler-sfc": "3.5.19",
|
||||
"astring": "1.9.0",
|
||||
"buraha": "0.0.1",
|
||||
"estree-walker": "3.0.3",
|
||||
|
@ -26,16 +26,16 @@
|
|||
"mfm-js": "0.25.0",
|
||||
"misskey-js": "workspace:*",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.46.2",
|
||||
"sass": "1.89.2",
|
||||
"shiki": "3.9.1",
|
||||
"rollup": "4.48.0",
|
||||
"sass": "1.90.0",
|
||||
"shiki": "3.11.0",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tsc-alias": "1.8.16",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"uuid": "11.1.0",
|
||||
"vite": "7.0.6",
|
||||
"vue": "3.5.18"
|
||||
"vite": "7.1.3",
|
||||
"vue": "3.5.19"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@misskey-dev/summaly": "5.2.3",
|
||||
|
@ -43,14 +43,14 @@
|
|||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.17.0",
|
||||
"@types/node": "22.17.2",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/runtime-core": "3.5.18",
|
||||
"@vue/runtime-core": "3.5.19",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.0.0",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
|
@ -59,14 +59,14 @@
|
|||
"happy-dom": "18.0.1",
|
||||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"msw": "2.10.4",
|
||||
"msw": "2.10.5",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"start-server-and-test": "2.0.12",
|
||||
"tsx": "4.20.3",
|
||||
"start-server-and-test": "2.0.13",
|
||||
"tsx": "4.20.4",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vue-component-type-helpers": "3.0.5",
|
||||
"vue-component-type-helpers": "3.0.6",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.5"
|
||||
"vue-tsc": "3.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "22.17.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"esbuild": "0.25.8",
|
||||
"@types/node": "22.17.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"esbuild": "0.25.9",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"nodemon": "3.1.10",
|
||||
"typescript": "5.9.2",
|
||||
|
@ -35,6 +35,6 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"misskey-js": "workspace:*",
|
||||
"vue": "3.5.18"
|
||||
"vue": "3.5.19"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,14 +24,14 @@
|
|||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@rollup/pluginutils": "5.2.0",
|
||||
"@sentry/vue": "10.0.0",
|
||||
"@sentry/vue": "10.5.0",
|
||||
"@syuilo/aiscript": "1.1.0",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
"@vitejs/plugin-vue": "6.0.1",
|
||||
"@vue/compiler-sfc": "3.5.18",
|
||||
"@vue/compiler-sfc": "3.5.19",
|
||||
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
|
||||
"analytics": "0.8.16",
|
||||
"analytics": "0.8.19",
|
||||
"astring": "1.9.0",
|
||||
"broadcast-channel": "7.1.0",
|
||||
"buraha": "0.0.1",
|
||||
|
@ -55,7 +55,7 @@
|
|||
"ios-haptics": "0.1.0",
|
||||
"is-file-animated": "1.0.2",
|
||||
"json5": "2.2.3",
|
||||
"magic-string": "0.30.17",
|
||||
"magic-string": "0.30.18",
|
||||
"matter-js": "0.20.0",
|
||||
"mfm-js": "0.25.0",
|
||||
"misskey-bubble-game": "workspace:*",
|
||||
|
@ -63,10 +63,10 @@
|
|||
"misskey-reversi": "workspace:*",
|
||||
"photoswipe": "5.4.4",
|
||||
"punycode.js": "2.3.1",
|
||||
"rollup": "4.46.2",
|
||||
"rollup": "4.48.0",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.89.2",
|
||||
"shiki": "3.9.1",
|
||||
"sass": "1.90.0",
|
||||
"shiki": "3.11.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"textarea-caret": "3.1.0",
|
||||
"three": "0.179.1",
|
||||
|
@ -76,8 +76,8 @@
|
|||
"tsconfig-paths": "4.2.0",
|
||||
"typescript": "5.9.2",
|
||||
"v-code-diff": "1.13.1",
|
||||
"vite": "7.0.6",
|
||||
"vue": "3.5.18",
|
||||
"vite": "7.1.3",
|
||||
"vue": "3.5.19",
|
||||
"vuedraggable": "next",
|
||||
"wanakana": "5.3.1"
|
||||
},
|
||||
|
@ -86,7 +86,7 @@
|
|||
"@storybook/addon-actions": "9.0.8",
|
||||
"@storybook/addon-essentials": "8.6.14",
|
||||
"@storybook/addon-interactions": "8.6.14",
|
||||
"@storybook/addon-links": "9.1.0",
|
||||
"@storybook/addon-links": "9.1.3",
|
||||
"@storybook/addon-mdx-gfm": "8.6.14",
|
||||
"@storybook/addon-storysource": "8.6.14",
|
||||
"@storybook/blocks": "8.6.14",
|
||||
|
@ -94,34 +94,34 @@
|
|||
"@storybook/core-events": "8.6.14",
|
||||
"@storybook/manager-api": "8.6.14",
|
||||
"@storybook/preview-api": "8.6.14",
|
||||
"@storybook/react": "9.1.0",
|
||||
"@storybook/react-vite": "9.1.0",
|
||||
"@storybook/react": "9.1.3",
|
||||
"@storybook/react-vite": "9.1.3",
|
||||
"@storybook/test": "8.6.14",
|
||||
"@storybook/theming": "8.6.14",
|
||||
"@storybook/types": "8.6.14",
|
||||
"@storybook/vue3": "9.1.0",
|
||||
"@storybook/vue3-vite": "9.1.0",
|
||||
"@storybook/vue3": "9.1.3",
|
||||
"@storybook/vue3-vite": "9.1.3",
|
||||
"@tabler/icons-webfont": "3.34.1",
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/canvas-confetti": "1.9.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/matter-js": "0.19.8",
|
||||
"@types/matter-js": "0.20.0",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "22.17.0",
|
||||
"@types/node": "22.17.2",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@types/throttle-debounce": "5.0.2",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "8.38.0",
|
||||
"@typescript-eslint/parser": "8.38.0",
|
||||
"@typescript-eslint/eslint-plugin": "8.40.0",
|
||||
"@typescript-eslint/parser": "8.40.0",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"@vue/compiler-core": "3.5.18",
|
||||
"@vue/runtime-core": "3.5.18",
|
||||
"@vue/compiler-core": "3.5.19",
|
||||
"@vue/runtime-core": "3.5.19",
|
||||
"acorn": "8.15.0",
|
||||
"cross-env": "10.0.0",
|
||||
"cypress": "14.5.3",
|
||||
"cypress": "14.5.4",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-vue": "10.4.0",
|
||||
"fast-glob": "3.3.3",
|
||||
|
@ -129,22 +129,22 @@
|
|||
"intersection-observer": "0.12.2",
|
||||
"micromatch": "4.0.8",
|
||||
"minimatch": "10.0.3",
|
||||
"msw": "2.10.4",
|
||||
"msw": "2.10.5",
|
||||
"msw-storybook-addon": "2.0.5",
|
||||
"nodemon": "3.1.10",
|
||||
"prettier": "3.6.2",
|
||||
"react": "19.1.1",
|
||||
"react-dom": "19.1.1",
|
||||
"seedrandom": "3.0.5",
|
||||
"start-server-and-test": "2.0.12",
|
||||
"storybook": "9.1.0",
|
||||
"start-server-and-test": "2.0.13",
|
||||
"storybook": "9.1.3",
|
||||
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
|
||||
"tsx": "4.20.3",
|
||||
"tsx": "4.20.4",
|
||||
"vite-plugin-turbosnap": "1.0.3",
|
||||
"vitest": "3.2.4",
|
||||
"vitest-fetch-mock": "0.4.5",
|
||||
"vue-component-type-helpers": "3.0.5",
|
||||
"vue-component-type-helpers": "3.0.6",
|
||||
"vue-eslint-parser": "10.2.0",
|
||||
"vue-tsc": "3.0.5"
|
||||
"vue-tsc": "3.0.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ const props = withDefaults(defineProps<{
|
|||
const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null);
|
||||
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
|
||||
|
||||
function fetch() {
|
||||
function _fetch_() {
|
||||
misskeyApi('users/achievements', { userId: props.user.id }).then(res => {
|
||||
achievements.value = [];
|
||||
for (const t of ACHIEVEMENT_TYPES) {
|
||||
|
@ -84,11 +84,11 @@ function fetch() {
|
|||
|
||||
function clickHere() {
|
||||
claimAchievement('clickedClickHere');
|
||||
fetch();
|
||||
_fetch_();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetch();
|
||||
_fetch_();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ function initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource
|
|||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||
|
||||
const shaderProgram = gl.createProgram();
|
||||
if (shaderProgram == null || vertexShader == null || fragmentShader == null) return null;
|
||||
if (vertexShader == null || fragmentShader == null) return null;
|
||||
|
||||
gl.attachShader(shaderProgram, vertexShader);
|
||||
gl.attachShader(shaderProgram, fragmentShader);
|
||||
|
@ -71,8 +71,10 @@ onMounted(() => {
|
|||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
|
||||
const gl = canvas.getContext('webgl', { premultipliedAlpha: true });
|
||||
if (gl == null) return;
|
||||
const maybeGl = canvas.getContext('webgl', { premultipliedAlpha: true });
|
||||
if (maybeGl == null) return;
|
||||
|
||||
const gl = maybeGl;
|
||||
|
||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
@ -229,8 +231,8 @@ onMounted(() => {
|
|||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
|
||||
|
||||
if (isChromatic()) {
|
||||
gl!.uniform1f(u_time, 0);
|
||||
gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4);
|
||||
gl.uniform1f(u_time, 0);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
} else {
|
||||
function render(timeStamp: number) {
|
||||
let sizeChanged = false;
|
||||
|
@ -249,8 +251,8 @@ onMounted(() => {
|
|||
gl.viewport(0, 0, width, height);
|
||||
}
|
||||
|
||||
gl!.uniform1f(u_time, timeStamp);
|
||||
gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4);
|
||||
gl.uniform1f(u_time, timeStamp);
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
handle = window.requestAnimationFrame(render);
|
||||
}
|
||||
|
@ -263,6 +265,8 @@ onUnmounted(() => {
|
|||
if (handle) {
|
||||
window.cancelAnimationFrame(handle);
|
||||
}
|
||||
|
||||
// TODO: WebGLリソースの解放
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -363,7 +363,7 @@ function onDrop(ev: DragEvent) {
|
|||
//#endregion
|
||||
}
|
||||
|
||||
function onUploadRequested(files: File[], folder: Misskey.entities.DriveFolder | null) {
|
||||
function onUploadRequested(files: File[], folder?: Misskey.entities.DriveFolder | null) {
|
||||
os.launchUploader(files, {
|
||||
folderId: folder?.id ?? null,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
:forceBlurhash="forceBlurhash"
|
||||
/>
|
||||
<img
|
||||
v-else-if="isThumbnailAvailable"
|
||||
v-else-if="isThumbnailAvailable && file.thumbnailUrl != null"
|
||||
:src="file.thumbnailUrl"
|
||||
:alt="file.name"
|
||||
:title="file.name"
|
||||
|
|
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<Mfm class="summaryMfm" :text="flash.summary" :plain="true" :nowrap="true"/>
|
||||
</p>
|
||||
<footer>
|
||||
<img class="icon" :src="flash.user.avatarUrl"/>
|
||||
<img v-if="flash.user.avatarUrl != null" class="icon" :src="flash.user.avatarUrl"/>
|
||||
<p>{{ userName(flash.user) }}</p>
|
||||
</footer>
|
||||
</article>
|
||||
|
|
|
@ -17,11 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<script lang="ts" setup>
|
||||
import { defineAsyncComponent, ref } from 'vue';
|
||||
import { url as local } from '@@/js/config.js';
|
||||
import { maybeMakeRelative } from '@@/js/url.js';
|
||||
import type { MkABehavior } from '@/components/global/MkA.vue';
|
||||
import { useTooltip } from '@/composables/use-tooltip.js';
|
||||
import * as os from '@/os.js';
|
||||
import { isEnabledUrlPreview } from '@/utility/url-preview.js';
|
||||
import type { MkABehavior } from '@/components/global/MkA.vue';
|
||||
import { maybeMakeRelative } from '@@/js/url.js';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
url: string;
|
||||
|
@ -42,7 +42,7 @@ if (isEnabledUrlPreview.value) {
|
|||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
|
||||
showing,
|
||||
url: props.url,
|
||||
source: el.value instanceof HTMLElement ? el.value : el.value?.$el,
|
||||
anchorElement: el.value instanceof HTMLElement ? el.value : el.value?.$el,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import type { MenuItem } from '@/types/menu.js';
|
|||
|
||||
const props = defineProps<{
|
||||
items: MenuItem[];
|
||||
targetElement: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
rootElement: HTMLElement;
|
||||
width?: number;
|
||||
}>();
|
||||
|
@ -36,10 +36,10 @@ const SCROLLBAR_THICKNESS = 16;
|
|||
function setPosition() {
|
||||
if (el.value == null) return;
|
||||
const rootRect = props.rootElement.getBoundingClientRect();
|
||||
const parentRect = props.targetElement.getBoundingClientRect();
|
||||
const parentRect = props.anchorElement.getBoundingClientRect();
|
||||
const myRect = el.value.getBoundingClientRect();
|
||||
|
||||
let left = props.targetElement.offsetWidth;
|
||||
let left = props.anchorElement.offsetWidth;
|
||||
let top = (parentRect.top - rootRect.top) - 8;
|
||||
if (rootRect.left + left + myRect.width >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
|
||||
left = -myRect.width;
|
||||
|
@ -59,7 +59,7 @@ function onChildClosed(actioned?: boolean) {
|
|||
}
|
||||
}
|
||||
|
||||
watch(() => props.targetElement, () => {
|
||||
watch(() => props.anchorElement, () => {
|
||||
setPosition();
|
||||
});
|
||||
|
||||
|
|
|
@ -208,7 +208,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</span>
|
||||
</div>
|
||||
<div v-if="childMenu">
|
||||
<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/>
|
||||
<XChild ref="child" :items="childMenu" :anchorElement="childTarget!" :rootElement="itemsEl!" @actioned="childActioned" @closed="closeChild"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -429,7 +429,7 @@ if (!props.mock) {
|
|||
showing,
|
||||
users,
|
||||
count: appearNote.renoteCount,
|
||||
targetElement: renoteButton.value,
|
||||
anchorElement: renoteButton.value,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
@ -452,7 +452,7 @@ if (!props.mock) {
|
|||
reaction: '❤️',
|
||||
users,
|
||||
count: $appearNote.reactionCount,
|
||||
targetElement: reactButton.value!,
|
||||
anchorElement: reactButton.value!,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
@ -460,14 +460,12 @@ if (!props.mock) {
|
|||
}
|
||||
}
|
||||
|
||||
function renote(viaKeyboard = false) {
|
||||
function renote() {
|
||||
pleaseLogin({ openOnRemote: pleaseLoginContext.value });
|
||||
showMovedDialog();
|
||||
|
||||
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
|
||||
os.popupMenu(menu, renoteButton.value, {
|
||||
viaKeyboard,
|
||||
});
|
||||
os.popupMenu(menu, renoteButton.value);
|
||||
|
||||
subscribeManuallyToNoteCapture();
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ if (noteViewInterruptors.length > 0) {
|
|||
}
|
||||
|
||||
const isRenote = Misskey.note.isPureRenote(note);
|
||||
const appearNote = getAppearNote(note);
|
||||
const appearNote = getAppearNote(note) ?? note;
|
||||
const { $note: $appearNote, subscribe: subscribeManuallyToNoteCapture } = useNoteCapture({
|
||||
note: appearNote,
|
||||
parentNote: note,
|
||||
|
@ -405,7 +405,7 @@ useTooltip(renoteButton, async (showing) => {
|
|||
showing,
|
||||
users,
|
||||
count: appearNote.renoteCount,
|
||||
targetElement: renoteButton.value,
|
||||
anchorElement: renoteButton.value,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
@ -428,7 +428,7 @@ if (appearNote.reactionAcceptance === 'likeOnly') {
|
|||
reaction: '❤️',
|
||||
users,
|
||||
count: $appearNote.reactionCount,
|
||||
targetElement: reactButton.value!,
|
||||
anchorElement: reactButton.value!,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkInput>
|
||||
</section>
|
||||
<section v-else-if="expiration === 'after'">
|
||||
<MkInput v-model="after" small type="number" min="1" class="input">
|
||||
<MkInput v-model="after" small type="number" :min="1" class="input">
|
||||
<template #label>{{ i18n.ts._poll.duration }}</template>
|
||||
</MkInput>
|
||||
<MkSelect v-model="unit" small>
|
||||
|
|
|
@ -959,7 +959,16 @@ async function post(ev?: MouseEvent) {
|
|||
|
||||
if (postAccount.value) {
|
||||
const storedAccounts = await getAccounts();
|
||||
token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
|
||||
const storedAccount = storedAccounts.find(x => x.id === postAccount.value?.id);
|
||||
if (storedAccount && storedAccount.token != null) {
|
||||
token = storedAccount.token;
|
||||
} else {
|
||||
await os.alert({
|
||||
type: 'error',
|
||||
text: 'cannot find the token of the selected account.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
posting.value = true;
|
||||
|
|
|
@ -167,7 +167,7 @@ function onMouseenter() {
|
|||
text: computed(() => {
|
||||
return props.textConverter(finalValue.value);
|
||||
}),
|
||||
targetElement: thumbEl.value ?? undefined,
|
||||
anchorElement: thumbEl.value ?? undefined,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
@ -191,7 +191,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) {
|
|||
text: computed(() => {
|
||||
return props.textConverter(finalValue.value);
|
||||
}),
|
||||
targetElement: thumbEl.value ?? undefined,
|
||||
anchorElement: thumbEl.value ?? undefined,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ if (props.withTooltip) {
|
|||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkReactionTooltip.vue')), {
|
||||
showing,
|
||||
reaction: props.reaction.replace(/^:(\w+):$/, ':$1@.:'),
|
||||
targetElement: elRef.value.$el,
|
||||
anchorElement: elRef.value.$el,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :showing="showing" :anchorElement="anchorElement" :maxWidth="340" @closed="emit('closed')">
|
||||
<div :class="$style.root">
|
||||
<MkReactionIcon :reaction="reaction" :class="$style.icon" :noStyle="true"/>
|
||||
<div :class="$style.name">{{ reaction.replace('@.', '') }}</div>
|
||||
|
@ -20,7 +20,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
|||
defineProps<{
|
||||
showing: boolean;
|
||||
reaction: string;
|
||||
targetElement: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :showing="showing" :anchorElement="anchorElement" :maxWidth="340" @closed="emit('closed')">
|
||||
<div :class="$style.root">
|
||||
<div :class="$style.reaction">
|
||||
<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :noStyle="true"/>
|
||||
|
@ -33,7 +33,7 @@ defineProps<{
|
|||
reaction: string;
|
||||
users: Misskey.entities.UserLite[];
|
||||
count: number;
|
||||
targetElement: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -231,7 +231,7 @@ if (!mock) {
|
|||
reaction: props.reaction,
|
||||
users,
|
||||
count: props.count,
|
||||
targetElement: buttonEl.value,
|
||||
anchorElement: buttonEl.value,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -39,13 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import type { VNode, VNodeChild } from 'vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
<script lang="ts">
|
||||
type ItemOption = {
|
||||
type?: 'option';
|
||||
value: string | number | null;
|
||||
|
@ -60,11 +54,32 @@ type ItemGroup = {
|
|||
|
||||
export type MkSelectItem = ItemOption | ItemGroup;
|
||||
|
||||
type ValuesOfItems<T> = T extends (infer U)[]
|
||||
? U extends { type: 'group'; items: infer V }
|
||||
? V extends (infer W)[]
|
||||
? W extends { value: infer X }
|
||||
? X
|
||||
: never
|
||||
: never
|
||||
: U extends { value: infer Y }
|
||||
? Y
|
||||
: never
|
||||
: never;
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup generic="T extends MkSelectItem[]">
|
||||
import { onMounted, nextTick, ref, watch, computed, toRefs, useSlots } from 'vue';
|
||||
import { useInterval } from '@@/js/use-interval.js';
|
||||
import type { VNode, VNodeChild } from 'vue';
|
||||
import type { MenuItem } from '@/types/menu.js';
|
||||
import * as os from '@/os.js';
|
||||
|
||||
// TODO: itemsをslot内のoptionで指定する用法は廃止する(props.itemsを必須化する)
|
||||
// see: https://github.com/misskey-dev/misskey/issues/15558
|
||||
// あと型推論と相性が良くない
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: string | number | null;
|
||||
modelValue: ValuesOfItems<T>;
|
||||
required?: boolean;
|
||||
readonly?: boolean;
|
||||
disabled?: boolean;
|
||||
|
@ -73,11 +88,11 @@ const props = defineProps<{
|
|||
inline?: boolean;
|
||||
small?: boolean;
|
||||
large?: boolean;
|
||||
items?: MkSelectItem[];
|
||||
items?: T;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'update:modelValue', value: string | number | null): void;
|
||||
(ev: 'update:modelValue', value: ValuesOfItems<T>): void;
|
||||
}>();
|
||||
|
||||
const slots = useSlots();
|
||||
|
|
|
@ -31,7 +31,7 @@ import { prefer } from '@/preferences.js';
|
|||
|
||||
const props = withDefaults(defineProps<{
|
||||
showing: boolean;
|
||||
targetElement?: HTMLElement;
|
||||
anchorElement?: HTMLElement;
|
||||
x?: number;
|
||||
y?: number;
|
||||
text?: string;
|
||||
|
@ -58,7 +58,7 @@ const zIndex = os.claimZIndex('high');
|
|||
function setPosition() {
|
||||
if (el.value == null) return;
|
||||
const data = calcPopupPosition(el.value, {
|
||||
anchorElement: props.targetElement,
|
||||
anchorElement: props.anchorElement,
|
||||
direction: props.direction,
|
||||
align: 'center',
|
||||
innerMargin: props.innerMargin,
|
||||
|
|
|
@ -20,7 +20,7 @@ import { prefer } from '@/preferences.js';
|
|||
const props = defineProps<{
|
||||
showing: boolean;
|
||||
url: string;
|
||||
source: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -32,9 +32,9 @@ const top = ref(0);
|
|||
const left = ref(0);
|
||||
|
||||
onMounted(() => {
|
||||
const rect = props.source.getBoundingClientRect();
|
||||
const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.scrollX;
|
||||
const y = rect.top + props.source.offsetHeight + window.scrollY;
|
||||
const rect = props.anchorElement.getBoundingClientRect();
|
||||
const x = Math.max((rect.left + (props.anchorElement.offsetWidth / 2)) - (300 / 2), 6) + window.scrollX;
|
||||
const y = rect.top + props.anchorElement.offsetHeight + window.scrollY;
|
||||
|
||||
top.value = y;
|
||||
left.value = x;
|
||||
|
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :showing="showing" :anchorElement="anchorElement" :maxWidth="250" @closed="emit('closed')">
|
||||
<div :class="$style.root">
|
||||
<div v-for="u in users" :key="u.id" :class="$style.user">
|
||||
<MkAvatar :class="$style.avatar" :user="u"/>
|
||||
|
@ -23,7 +23,7 @@ defineProps<{
|
|||
showing: boolean;
|
||||
users: Misskey.entities.UserLite[];
|
||||
count: number;
|
||||
targetElement: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{
|
|||
currentVisibility: typeof Misskey.noteVisibilities[number];
|
||||
isSilenced: boolean;
|
||||
localOnly: boolean;
|
||||
anchorElement?: HTMLElement;
|
||||
anchorElement?: HTMLElement | null;
|
||||
isReplyVisibilitySpecified?: boolean;
|
||||
}>(), {
|
||||
});
|
||||
|
|
|
@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="stats && instance.clientOptions.showActivityiesForVisitor !== false" :class="$style.stats">
|
||||
<div v-if="stats && instance.clientOptions.showActivitiesForVisitor !== false" :class="$style.stats">
|
||||
<div :class="[$style.statsItem, $style.panel]">
|
||||
<div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div>
|
||||
<div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div>
|
||||
|
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkStreamingNotesTimeline src="local"/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="instance.clientOptions.showActivityiesForVisitor !== false" :class="$style.panel">
|
||||
<div v-if="instance.clientOptions.showActivitiesForVisitor !== false" :class="$style.panel">
|
||||
<XActiveUsersChart/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -72,7 +72,7 @@ import { openInstanceMenu } from '@/ui/_common_/common.js';
|
|||
|
||||
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
||||
|
||||
if (instance.clientOptions.showActivityiesForVisitor !== false) {
|
||||
if (instance.clientOptions.showActivitiesForVisitor !== false) {
|
||||
misskeyApi('stats', {}).then((res) => {
|
||||
stats.value = res;
|
||||
});
|
||||
|
|
|
@ -62,7 +62,7 @@ if (props.showUrlPreview && isEnabledUrlPreview.value) {
|
|||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
|
||||
showing,
|
||||
url: props.url,
|
||||
source: el.value instanceof HTMLElement ? el.value : el.value?.$el,
|
||||
anchorElement: el.value instanceof HTMLElement ? el.value : el.value?.$el,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<!--
|
||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<template>
|
||||
<slot></slot>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style lang="scss" module>
|
||||
</style>
|
|
@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
-->
|
||||
|
||||
<template>
|
||||
<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')">
|
||||
<MkTooltip ref="tooltip" :showing="showing" :anchorElement="anchorElement" :maxWidth="250" @closed="emit('closed')">
|
||||
<div :class="$style.root">
|
||||
{{ content }}
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ import MkTooltip from '@/components/MkTooltip.vue';
|
|||
defineProps<{
|
||||
showing: boolean;
|
||||
content: string;
|
||||
targetElement: HTMLElement;
|
||||
anchorElement: HTMLElement;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
|
|
@ -300,7 +300,7 @@ useTooltip(rootEl, (showing) => {
|
|||
const result = os.popup(defineAsyncComponent(() => import('@/components/grid/MkCellTooltip.vue')), {
|
||||
showing,
|
||||
content,
|
||||
targetElement: rootEl.value!,
|
||||
anchorElement: rootEl.value!,
|
||||
}, {
|
||||
closed: () => {
|
||||
result.dispose();
|
||||
|
|
|
@ -57,7 +57,7 @@ export default {
|
|||
text: self.text,
|
||||
asMfm: binding.modifiers.mfm,
|
||||
direction: binding.modifiers.left ? 'left' : binding.modifiers.right ? 'right' : binding.modifiers.top ? 'top' : binding.modifiers.bottom ? 'bottom' : 'top',
|
||||
targetElement: el,
|
||||
anchorElement: el,
|
||||
}, {
|
||||
closed: () => dispose(),
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { markRaw, ref, defineAsyncComponent, nextTick } from 'vue';
|
|||
import { EventEmitter } from 'eventemitter3';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import type { Component, Ref } from 'vue';
|
||||
import type { ComponentProps as CP } from 'vue-component-type-helpers';
|
||||
import type { ComponentEmit, 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';
|
||||
|
@ -157,28 +157,9 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
|
|||
return zIndexes[priority];
|
||||
}
|
||||
|
||||
// InstanceType<typeof Component>['$emit'] だとインターセクション型が返ってきて
|
||||
// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する
|
||||
// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい
|
||||
type ComponentEmit<T> = T extends new () => { $props: infer Props }
|
||||
? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
|
||||
? Record<string, unknown> // *.ts ファイルから型がうまく取れないとき用(これがないと {} になって型エラーがうるさい)
|
||||
: EmitsExtractor<Props>
|
||||
: T extends (...args: any) => any
|
||||
? ReturnType<T> extends { [x: string]: any; __ctx?: { [x: string]: any; props: infer Props } }
|
||||
? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
|
||||
? Record<string, unknown>
|
||||
: EmitsExtractor<Props>
|
||||
: never
|
||||
: never;
|
||||
|
||||
// props に ref を許可するようにする
|
||||
type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
|
||||
|
||||
type EmitsExtractor<T> = {
|
||||
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
|
||||
};
|
||||
|
||||
export function popup<T extends Component>(
|
||||
component: T,
|
||||
props: ComponentProps<T>,
|
||||
|
|
|
@ -22,20 +22,46 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<option value="blocked">{{ i18n.ts.blocked }}</option>
|
||||
<option value="notResponding">{{ i18n.ts.notResponding }}</option>
|
||||
</MkSelect>
|
||||
<MkSelect v-model="sort">
|
||||
<MkSelect
|
||||
v-model="sort" :items="[{
|
||||
label: `${i18n.ts.pubSub} (${i18n.ts.descendingOrder})`,
|
||||
value: '+pubSub',
|
||||
}, {
|
||||
label: `${i18n.ts.pubSub} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-pubSub',
|
||||
}, {
|
||||
label: `${i18n.ts.notes} (${i18n.ts.descendingOrder})`,
|
||||
value: '+notes',
|
||||
}, {
|
||||
label: `${i18n.ts.notes} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-notes',
|
||||
}, {
|
||||
label: `${i18n.ts.users} (${i18n.ts.descendingOrder})`,
|
||||
value: '+users',
|
||||
}, {
|
||||
label: `${i18n.ts.users} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-users',
|
||||
}, {
|
||||
label: `${i18n.ts.following} (${i18n.ts.descendingOrder})`,
|
||||
value: '+following',
|
||||
}, {
|
||||
label: `${i18n.ts.following} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-following',
|
||||
}, {
|
||||
label: `${i18n.ts.followers} (${i18n.ts.descendingOrder})`,
|
||||
value: '+followers',
|
||||
}, {
|
||||
label: `${i18n.ts.followers} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-followers',
|
||||
}, {
|
||||
label: `${i18n.ts.registeredAt} (${i18n.ts.descendingOrder})`,
|
||||
value: '+firstRetrievedAt',
|
||||
}, {
|
||||
label: `${i18n.ts.registeredAt} (${i18n.ts.ascendingOrder})`,
|
||||
value: '-firstRetrievedAt',
|
||||
}] as const"
|
||||
>
|
||||
<template #label>{{ i18n.ts.sort }}</template>
|
||||
<option value="+pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
<option value="+notes">{{ i18n.ts.notes }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-notes">{{ i18n.ts.notes }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
<option value="+users">{{ i18n.ts.users }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-users">{{ i18n.ts.users }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
<option value="+following">{{ i18n.ts.following }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-following">{{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
<option value="+followers">{{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
<option value="+firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
|
||||
<option value="-firstRetrievedAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
|
||||
</MkSelect>
|
||||
</FormSplit>
|
||||
</div>
|
||||
|
@ -52,6 +78,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { computed, markRaw, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkInput from '@/components/MkInput.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
|
@ -62,7 +89,7 @@ import { Paginator } from '@/utility/paginator.js';
|
|||
|
||||
const host = ref('');
|
||||
const state = ref('federating');
|
||||
const sort = ref('+pubSub');
|
||||
const sort = ref<NonNullable<Misskey.entities.FederationInstancesRequest['sort']>>('+pubSub');
|
||||
const paginator = markRaw(new Paginator('federation/instances', {
|
||||
limit: 10,
|
||||
offsetMode: true,
|
||||
|
|
|
@ -48,34 +48,22 @@ watch(tab, () => {
|
|||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
const headerTabs = computed(() => {
|
||||
const items = [];
|
||||
|
||||
items.push({
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
}, {
|
||||
key: 'emojis',
|
||||
title: i18n.ts.customEmojis,
|
||||
icon: 'ti ti-icons',
|
||||
});
|
||||
|
||||
if (instance.federation !== 'none') {
|
||||
items.push({
|
||||
key: 'federation',
|
||||
title: i18n.ts.federation,
|
||||
icon: 'ti ti-whirl',
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
key: 'charts',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ti ti-chart-line',
|
||||
});
|
||||
|
||||
return items;
|
||||
});
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'overview',
|
||||
title: i18n.ts.overview,
|
||||
}, {
|
||||
key: 'emojis',
|
||||
title: i18n.ts.customEmojis,
|
||||
icon: 'ti ti-icons',
|
||||
}, ...(instance.federation !== 'none' ? [{
|
||||
key: 'federation',
|
||||
title: i18n.ts.federation,
|
||||
icon: 'ti ti-whirl',
|
||||
}] : []), {
|
||||
key: 'charts',
|
||||
title: i18n.ts.charts,
|
||||
icon: 'ti ti-chart-line',
|
||||
}]);
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts.instanceInfo,
|
||||
|
|
|
@ -111,13 +111,13 @@ const props = defineProps<{
|
|||
fileId: string,
|
||||
}>();
|
||||
|
||||
async function fetch() {
|
||||
async function _fetch_() {
|
||||
file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
|
||||
info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
|
||||
isSensitive.value = file.value.isSensitive;
|
||||
}
|
||||
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
|
|
|
@ -6,58 +6,57 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template>
|
||||
<PageWithHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs">
|
||||
<div class="_spacer" style="--MI_SPACER-w: 600px; --MI_SPACER-min: 16px; --MI_SPACER-max: 32px;">
|
||||
<FormSuspense :p="init">
|
||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
||||
<div class="aeakzknw">
|
||||
<MkAvatar class="avatar" :user="user" indicator link preview/>
|
||||
<div class="body">
|
||||
<span class="name"><MkUserName class="name" :user="user"/></span>
|
||||
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||
<span class="state">
|
||||
<span v-if="suspended" class="suspended">Suspended</span>
|
||||
<span v-if="silenced" class="silenced">Silenced</span>
|
||||
<span v-if="moderator" class="moderator">Moderator</span>
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="tab === 'overview'" class="_gaps_m">
|
||||
<div class="aeakzknw">
|
||||
<MkAvatar class="avatar" :user="user" indicator link preview/>
|
||||
<div class="body">
|
||||
<span class="name"><MkUserName class="name" :user="user"/></span>
|
||||
<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
|
||||
<span class="state">
|
||||
<span v-if="suspended" class="suspended">Suspended</span>
|
||||
<span v-if="silenced" class="silenced">Silenced</span>
|
||||
<span v-if="moderator" class="moderator">Moderator</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||
<MkInfo v-if="isSystem">{{ i18n.ts.isSystemAccount }}</MkInfo>
|
||||
|
||||
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
||||
<FormLink v-if="user.host" :to="`/instance-info/${user.host}`">{{ i18n.ts.instanceInfo }}</FormLink>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||
<MkKeyValue :copy="user.id" oneline>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<!-- 要る?
|
||||
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||
<MkKeyValue :copy="user.id" oneline>
|
||||
<template #key>ID</template>
|
||||
<template #value><span class="_monospace">{{ user.id }}</span></template>
|
||||
</MkKeyValue>
|
||||
<!-- 要る?
|
||||
<MkKeyValue v-if="ips.length > 0" :copy="user.id" oneline>
|
||||
<template #key>IP (recent)</template>
|
||||
<template #value><span class="_monospace">{{ ips[0].ip }}</span></template>
|
||||
</MkKeyValue>
|
||||
-->
|
||||
<template v-if="!isSystem">
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="info" oneline>
|
||||
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="info" oneline>
|
||||
<template #key>{{ i18n.ts.email }}</template>
|
||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||
</MkKeyValue>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="!isSystem">
|
||||
<MkKeyValue oneline>
|
||||
<template #key>{{ i18n.ts.createdAt }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="user.createdAt" :mode="'detail'"/></span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="info" oneline>
|
||||
<template #key>{{ i18n.ts.lastActiveDate }}</template>
|
||||
<template #value><span class="_monospace"><MkTime :time="info.lastActiveDate" :mode="'detail'"/></span></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue v-if="info" oneline>
|
||||
<template #key>{{ i18n.ts.email }}</template>
|
||||
<template #value><span class="_monospace">{{ info.email }}</span></template>
|
||||
</MkKeyValue>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||
</MkTextarea>
|
||||
<MkTextarea v-if="!isSystem" v-model="moderationNote" manualSave>
|
||||
<template #label>{{ i18n.ts.moderationNote }}</template>
|
||||
<template #caption>{{ i18n.ts.moderationNoteDescription }}</template>
|
||||
</MkTextarea>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
<FormSection>
|
||||
<template #label>ActivityPub</template>
|
||||
|
||||
|
@ -93,119 +92,118 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</FormSection>
|
||||
-->
|
||||
|
||||
<FormSection v-if="!isSystem">
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||
<FormSection v-if="!isSystem">
|
||||
<div class="_gaps">
|
||||
<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
|
||||
|
||||
<div>
|
||||
<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
||||
</div>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-license"></i></template>
|
||||
<template #label>{{ i18n.ts._role.policies }}</template>
|
||||
<div class="_gaps">
|
||||
<div v-for="policy in Object.keys(info.policies)" :key="policy">
|
||||
{{ policy }} ... {{ info.policies[policy] }}
|
||||
</div>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-password"></i></template>
|
||||
<template #label>IP</template>
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
||||
<template v-if="iAmAdmin && ips">
|
||||
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
||||
<span class="date">{{ record.createdAt }}</span>
|
||||
<span class="ip">{{ record.ip }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</MkFolder>
|
||||
|
||||
<div>
|
||||
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||
</div>
|
||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
||||
<div>
|
||||
<MkButton v-if="user.host == null" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'roles'" class="_gaps">
|
||||
<MkButton v-if="user.host == null" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||
|
||||
<div v-for="role in info.roles" :key="role.id">
|
||||
<div :class="$style.roleItemMain">
|
||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
|
||||
<button class="_button" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedRoles.includes(role.id)" :class="$style.roleItemSub">
|
||||
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
|
||||
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'announcements'" class="_gaps">
|
||||
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
|
||||
|
||||
<MkSelect v-model="announcementsStatus">
|
||||
<template #label>{{ i18n.ts.filter }}</template>
|
||||
<option value="active">{{ i18n.ts.active }}</option>
|
||||
<option value="archived">{{ i18n.ts.archived }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkPagination :paginator="announcementsPaginator">
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
||||
<span style="margin-right: 0.5em;">
|
||||
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
||||
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
|
||||
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
|
||||
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
|
||||
</span>
|
||||
<span>{{ announcement.title }}</span>
|
||||
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-license"></i></template>
|
||||
<template #label>{{ i18n.ts._role.policies }}</template>
|
||||
<div class="_gaps">
|
||||
<div v-for="policy in Object.keys(info.policies)" :key="policy">
|
||||
{{ policy }} ... {{ info.policies[policy] }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
</MkFolder>
|
||||
|
||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||
<MkFileListForAdmin :paginator="filesPaginator" viewMode="grid"/>
|
||||
</div>
|
||||
<MkFolder>
|
||||
<template #icon><i class="ti ti-password"></i></template>
|
||||
<template #label>IP</template>
|
||||
<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
|
||||
<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
|
||||
<template v-if="iAmAdmin && ips">
|
||||
<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
|
||||
<span class="date">{{ record.createdAt }}</span>
|
||||
<span class="ip">{{ record.ip }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</MkFolder>
|
||||
|
||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
||||
<div class="cmhjzshm">
|
||||
<div class="selects">
|
||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||
<option value="per-user-notes">{{ i18n.ts.notes }}</option>
|
||||
</MkSelect>
|
||||
</div>
|
||||
<div class="charts">
|
||||
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||
<div>
|
||||
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
|
||||
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
|
||||
</div>
|
||||
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'roles'" class="_gaps">
|
||||
<MkButton v-if="user.host == null" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
|
||||
|
||||
<div v-for="role in info.roles" :key="role.id">
|
||||
<div :class="$style.roleItemMain">
|
||||
<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
|
||||
<button class="_button" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button>
|
||||
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
|
||||
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
|
||||
</div>
|
||||
<div v-if="expandedRoleIds.includes(role.id)" :class="$style.roleItemSub">
|
||||
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
|
||||
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
|
||||
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||
</MkObjectView>
|
||||
<div v-else-if="tab === 'announcements'" class="_gaps">
|
||||
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
|
||||
|
||||
<MkObjectView tall :value="user">
|
||||
</MkObjectView>
|
||||
<MkSelect v-model="announcementsStatus">
|
||||
<template #label>{{ i18n.ts.filter }}</template>
|
||||
<option value="active">{{ i18n.ts.active }}</option>
|
||||
<option value="archived">{{ i18n.ts.archived }}</option>
|
||||
</MkSelect>
|
||||
|
||||
<MkPagination :paginator="announcementsPaginator">
|
||||
<template #default="{ items }">
|
||||
<div class="_gaps_s">
|
||||
<div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)">
|
||||
<span style="margin-right: 0.5em;">
|
||||
<i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i>
|
||||
<i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i>
|
||||
<i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i>
|
||||
<i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i>
|
||||
</span>
|
||||
<span>{{ announcement.title }}</span>
|
||||
<span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'drive'" class="_gaps">
|
||||
<MkFileListForAdmin :paginator="filesPaginator" viewMode="grid"/>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'chart'" class="_gaps_m">
|
||||
<div class="cmhjzshm">
|
||||
<div class="selects">
|
||||
<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
|
||||
<option value="per-user-notes">{{ i18n.ts.notes }}</option>
|
||||
</MkSelect>
|
||||
</div>
|
||||
<div class="charts">
|
||||
<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
|
||||
<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||
<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
|
||||
<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
|
||||
</div>
|
||||
</div>
|
||||
</FormSuspense>
|
||||
</div>
|
||||
|
||||
<div v-else-if="tab === 'raw'" class="_gaps_m">
|
||||
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
|
||||
</MkObjectView>
|
||||
|
||||
<MkObjectView tall :value="user">
|
||||
</MkObjectView>
|
||||
</div>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
@ -224,7 +222,6 @@ import MkButton from '@/components/MkButton.vue';
|
|||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkSelect from '@/components/MkSelect.vue';
|
||||
import FormSuspense from '@/components/form/suspense.vue';
|
||||
import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
import * as os from '@/os.js';
|
||||
|
@ -232,11 +229,13 @@ import { misskeyApi } from '@/utility/misskey-api.js';
|
|||
import { acct } from '@/filters/user.js';
|
||||
import { definePage } from '@/page.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { iAmAdmin, $i, iAmModerator } from '@/i.js';
|
||||
import { ensureSignin, iAmAdmin, iAmModerator } from '@/i.js';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import MkPagination from '@/components/MkPagination.vue';
|
||||
import { Paginator } from '@/utility/paginator.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
userId: string;
|
||||
initialTab?: string;
|
||||
|
@ -244,18 +243,19 @@ const props = withDefaults(defineProps<{
|
|||
initialTab: 'overview',
|
||||
});
|
||||
|
||||
const result = await _fetch_();
|
||||
|
||||
const tab = ref(props.initialTab);
|
||||
const chartSrc = ref('per-user-notes');
|
||||
const user = ref<null | Misskey.entities.UserDetailed>();
|
||||
const init = ref<ReturnType<typeof createFetcher>>();
|
||||
const info = ref<any>();
|
||||
const ips = ref<Misskey.entities.AdminGetUserIpsResponse | null>(null);
|
||||
const user = ref(result.user);
|
||||
const info = ref(result.info);
|
||||
const ips = ref(result.ips);
|
||||
const ap = ref<any>(null);
|
||||
const moderator = ref(false);
|
||||
const silenced = ref(false);
|
||||
const suspended = ref(false);
|
||||
const isSystem = ref(false);
|
||||
const moderationNote = ref('');
|
||||
const moderator = ref(info.value.isModerator);
|
||||
const silenced = ref(info.value.isSilenced);
|
||||
const suspended = ref(info.value.isSuspended);
|
||||
const isSystem = ref(user.value.host == null && user.value.username.includes('.'));
|
||||
const moderationNote = ref(info.value.moderationNote);
|
||||
const filesPaginator = markRaw(new Paginator('admin/drive/files', {
|
||||
limit: 10,
|
||||
computedParams: computed(() => ({
|
||||
|
@ -272,34 +272,37 @@ const announcementsPaginator = markRaw(new Paginator('admin/announcements/list',
|
|||
status: announcementsStatus.value,
|
||||
})),
|
||||
}));
|
||||
const expandedRoles = ref([]);
|
||||
const expandedRoleIds = ref<(typeof info.value.roles[number]['id'])[]>([]);
|
||||
|
||||
function createFetcher() {
|
||||
return () => Promise.all([misskeyApi('users/show', {
|
||||
function _fetch_() {
|
||||
return Promise.all([misskeyApi('users/show', {
|
||||
userId: props.userId,
|
||||
}), misskeyApi('admin/show-user', {
|
||||
userId: props.userId,
|
||||
}), iAmAdmin ? misskeyApi('admin/get-user-ips', {
|
||||
userId: props.userId,
|
||||
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
|
||||
user.value = _user;
|
||||
info.value = _info;
|
||||
ips.value = _ips;
|
||||
moderator.value = info.value.isModerator;
|
||||
silenced.value = info.value.isSilenced;
|
||||
suspended.value = info.value.isSuspended;
|
||||
moderationNote.value = info.value.moderationNote;
|
||||
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
||||
|
||||
watch(moderationNote, async () => {
|
||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||
await refreshUser();
|
||||
});
|
||||
});
|
||||
}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => ({
|
||||
user: _user,
|
||||
info: _info,
|
||||
ips: _ips,
|
||||
}));
|
||||
}
|
||||
|
||||
function refreshUser() {
|
||||
init.value = createFetcher();
|
||||
watch(moderationNote, async () => {
|
||||
await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
|
||||
await refreshUser();
|
||||
});
|
||||
|
||||
async function refreshUser() {
|
||||
const result = await _fetch_();
|
||||
user.value = result.user;
|
||||
info.value = result.info;
|
||||
ips.value = result.ips;
|
||||
moderator.value = info.value.isModerator;
|
||||
silenced.value = info.value.isSilenced;
|
||||
suspended.value = info.value.isSuspended;
|
||||
isSystem.value = user.value.host == null && user.value.username.includes('.');
|
||||
moderationNote.value = info.value.moderationNote;
|
||||
}
|
||||
|
||||
async function updateRemoteUser() {
|
||||
|
@ -456,7 +459,7 @@ async function assignRole() {
|
|||
refreshUser();
|
||||
}
|
||||
|
||||
async function unassignRole(role, ev) {
|
||||
async function unassignRole(role: typeof info.value.roles[number], ev: MouseEvent) {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.unassign,
|
||||
icon: 'ti ti-x',
|
||||
|
@ -468,11 +471,11 @@ async function unassignRole(role, ev) {
|
|||
}], ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
function toggleRoleItem(role) {
|
||||
if (expandedRoles.value.includes(role.id)) {
|
||||
expandedRoles.value = expandedRoles.value.filter(x => x !== role.id);
|
||||
function toggleRoleItem(role: typeof info.value.roles[number]) {
|
||||
if (expandedRoleIds.value.includes(role.id)) {
|
||||
expandedRoleIds.value = expandedRoleIds.value.filter(x => x !== role.id);
|
||||
} else {
|
||||
expandedRoles.value.push(role.id);
|
||||
expandedRoleIds.value.push(role.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,12 +496,6 @@ async function editAnnouncement(announcement) {
|
|||
});
|
||||
}
|
||||
|
||||
watch(() => props.userId, () => {
|
||||
init.value = createFetcher();
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
|
||||
watch(user, () => {
|
||||
misskeyApi('ap/get', {
|
||||
uri: user.value.uri ?? `${url}/users/${user.value.id}`,
|
||||
|
|
|
@ -23,8 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['activity', 'activities']">
|
||||
<MkSwitch v-model="showActivityiesForVisitor">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.showActivityiesForVisitor }}</SearchLabel></template>
|
||||
<MkSwitch v-model="showActivitiesForVisitor">
|
||||
<template #label><SearchLabel>{{ i18n.ts._serverSettings.showActivitiesForVisitor }}</SearchLabel></template>
|
||||
</MkSwitch>
|
||||
</SearchMarker>
|
||||
|
||||
|
@ -168,7 +168,7 @@ const meta = await misskeyApi('admin/meta');
|
|||
|
||||
const entrancePageStyle = ref(meta.clientOptions.entrancePageStyle ?? 'classic');
|
||||
const showTimelineForVisitor = ref(meta.clientOptions.showTimelineForVisitor ?? true);
|
||||
const showActivityiesForVisitor = ref(meta.clientOptions.showActivityiesForVisitor ?? true);
|
||||
const showActivitiesForVisitor = ref(meta.clientOptions.showActivitiesForVisitor ?? true);
|
||||
const iconUrl = ref(meta.iconUrl);
|
||||
const app192IconUrl = ref(meta.app192IconUrl);
|
||||
const app512IconUrl = ref(meta.app512IconUrl);
|
||||
|
@ -189,7 +189,7 @@ function save() {
|
|||
clientOptions: {
|
||||
entrancePageStyle: entrancePageStyle.value,
|
||||
showTimelineForVisitor: showTimelineForVisitor.value,
|
||||
showActivityiesForVisitor: showActivityiesForVisitor.value,
|
||||
showActivitiesForVisitor: showActivitiesForVisitor.value,
|
||||
},
|
||||
iconUrl: iconUrl.value,
|
||||
app192IconUrl: app192IconUrl.value,
|
||||
|
|
|
@ -41,7 +41,7 @@ async function addRelay() {
|
|||
type: 'url',
|
||||
placeholder: i18n.ts.inboxUrl,
|
||||
});
|
||||
if (canceled) return;
|
||||
if (canceled || inbox == null) return;
|
||||
misskeyApi('admin/relays/add', {
|
||||
inbox,
|
||||
}).then((relay: any) => {
|
||||
|
|
|
@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton primary @click="read(announcement)"><i class="ti ti-check"></i> {{ i18n.ts.gotIt }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
<MkError v-else-if="error" @retry="fetch()"/>
|
||||
<MkError v-else-if="error" @retry="_fetch_()"/>
|
||||
<MkLoading v-else/>
|
||||
</Transition>
|
||||
</div>
|
||||
|
@ -66,7 +66,7 @@ const announcement = ref<Misskey.entities.Announcement | null>(null);
|
|||
const error = ref<any>(null);
|
||||
const path = computed(() => props.announcementId);
|
||||
|
||||
function fetch() {
|
||||
function _fetch_() {
|
||||
announcement.value = null;
|
||||
misskeyApi('announcements/show', {
|
||||
announcementId: props.announcementId,
|
||||
|
@ -96,7 +96,7 @@ async function read(target: Misskey.entities.Announcement): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
watch(() => path.value, fetch, { immediate: true });
|
||||
watch(() => path.value, _fetch_, { immediate: true });
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
|
|
|
@ -37,27 +37,14 @@ const props = defineProps<{
|
|||
const antenna = ref<Misskey.entities.Antenna | null>(null);
|
||||
const tlEl = useTemplateRef('tlEl');
|
||||
|
||||
async function timetravel() {
|
||||
const { canceled, result: date } = await os.inputDate({
|
||||
title: i18n.ts.date,
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
tlEl.value.timetravel(date);
|
||||
}
|
||||
|
||||
function settings() {
|
||||
router.push('/my/antennas/:antennaId', {
|
||||
params: {
|
||||
antennaId: props.antennaId,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function focus() {
|
||||
tlEl.value.focus();
|
||||
}
|
||||
|
||||
watch(() => props.antennaId, async () => {
|
||||
antenna.value = await misskeyApi('antennas/show', {
|
||||
antennaId: props.antennaId,
|
||||
|
@ -65,10 +52,6 @@ watch(() => props.antennaId, async () => {
|
|||
}, { immediate: true });
|
||||
|
||||
const headerActions = computed(() => antenna.value ? [{
|
||||
icon: 'ti ti-calendar-time',
|
||||
text: i18n.ts.jumpToSpecifiedDate,
|
||||
handler: timetravel,
|
||||
}, {
|
||||
icon: 'ti ti-settings',
|
||||
text: i18n.ts.settings,
|
||||
handler: settings,
|
||||
|
|
|
@ -44,11 +44,13 @@ const name = computed(() => {
|
|||
});
|
||||
|
||||
function cancel() {
|
||||
misskeyApi('auth/deny', {
|
||||
token: props.session.token,
|
||||
}).then(() => {
|
||||
emit('denied');
|
||||
});
|
||||
//misskeyApi('auth/deny', {
|
||||
// token: props.session.token,
|
||||
//}).then(() => {
|
||||
// emit('denied');
|
||||
//});
|
||||
|
||||
emit('denied');
|
||||
}
|
||||
|
||||
function accept() {
|
||||
|
|
|
@ -92,7 +92,7 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const channel = ref<Misskey.entities.Channel | null>(null);
|
||||
const name = ref<string | null>(null);
|
||||
const name = ref<string>('');
|
||||
const description = ref<string | null>(null);
|
||||
const bannerUrl = ref<string | null>(null);
|
||||
const bannerId = ref<string | null>(null);
|
||||
|
@ -114,20 +114,22 @@ watch(() => bannerId.value, async () => {
|
|||
async function fetchChannel() {
|
||||
if (props.channelId == null) return;
|
||||
|
||||
channel.value = await misskeyApi('channels/show', {
|
||||
const result = await misskeyApi('channels/show', {
|
||||
channelId: props.channelId,
|
||||
});
|
||||
|
||||
name.value = channel.value.name;
|
||||
description.value = channel.value.description;
|
||||
bannerId.value = channel.value.bannerId;
|
||||
bannerUrl.value = channel.value.bannerUrl;
|
||||
isSensitive.value = channel.value.isSensitive;
|
||||
pinnedNotes.value = channel.value.pinnedNoteIds.map(id => ({
|
||||
name.value = result.name;
|
||||
description.value = result.description;
|
||||
bannerId.value = result.bannerId;
|
||||
bannerUrl.value = result.bannerUrl;
|
||||
isSensitive.value = result.isSensitive;
|
||||
pinnedNotes.value = result.pinnedNoteIds.map(id => ({
|
||||
id,
|
||||
}));
|
||||
color.value = channel.value.color;
|
||||
allowRenoteToExternal.value = channel.value.allowRenoteToExternal;
|
||||
color.value = result.color;
|
||||
allowRenoteToExternal.value = result.allowRenoteToExternal;
|
||||
|
||||
channel.value = result;
|
||||
}
|
||||
|
||||
fetchChannel();
|
||||
|
@ -154,15 +156,17 @@ function save() {
|
|||
name: name.value,
|
||||
description: description.value,
|
||||
bannerId: bannerId.value,
|
||||
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
|
||||
color: color.value,
|
||||
isSensitive: isSensitive.value,
|
||||
allowRenoteToExternal: allowRenoteToExternal.value,
|
||||
};
|
||||
} satisfies Misskey.entities.ChannelsCreateRequest;
|
||||
|
||||
if (props.channelId) {
|
||||
params.channelId = props.channelId;
|
||||
os.apiWithDialog('channels/update', params);
|
||||
if (props.channelId != null) {
|
||||
os.apiWithDialog('channels/update', {
|
||||
...params,
|
||||
channelId: props.channelId,
|
||||
pinnedNoteIds: pinnedNotes.value.map(x => x.id),
|
||||
});
|
||||
} else {
|
||||
os.apiWithDialog('channels/create', params).then(created => {
|
||||
router.push('/channels/:channelId', {
|
||||
|
@ -175,12 +179,13 @@ function save() {
|
|||
}
|
||||
|
||||
async function archive() {
|
||||
if (props.channelId == null) return;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
|
||||
text: i18n.ts.channelArchiveConfirmDescription,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
misskeyApi('channels/update', {
|
||||
|
|
|
@ -131,6 +131,8 @@ watch(() => props.channelId, async () => {
|
|||
channel.value = await misskeyApi('channels/show', {
|
||||
channelId: props.channelId,
|
||||
});
|
||||
if (channel.value == null) return; // TSを黙らすため
|
||||
|
||||
favorited.value = channel.value.isFavorited ?? false;
|
||||
if (favorited.value || channel.value.isFollowing) {
|
||||
tab.value = 'timeline';
|
||||
|
@ -150,7 +152,7 @@ function edit() {
|
|||
router.push('/channels/:channelId/edit', {
|
||||
params: {
|
||||
channelId: props.channelId,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,7 @@ const folderHierarchy = computed(() => {
|
|||
});
|
||||
const isImage = computed(() => file.value?.type.startsWith('image/'));
|
||||
|
||||
async function fetch() {
|
||||
async function _fetch_() {
|
||||
fetching.value = true;
|
||||
|
||||
file.value = await misskeyApi('drive/files/show', {
|
||||
|
@ -134,7 +134,7 @@ function move() {
|
|||
fileId: file.value.id,
|
||||
folderId: folder[0] ? folder[0].id : null,
|
||||
}).then(async () => {
|
||||
await fetch();
|
||||
await _fetch_();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ function toggleSensitive() {
|
|||
fileId: file.value.id,
|
||||
isSensitive: !file.value.isSensitive,
|
||||
}).then(async () => {
|
||||
await fetch();
|
||||
await _fetch_();
|
||||
}).catch(err => {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
|
@ -169,7 +169,7 @@ function rename() {
|
|||
fileId: file.value.id,
|
||||
name: name,
|
||||
}).then(async () => {
|
||||
await fetch();
|
||||
await _fetch_();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ async function describe() {
|
|||
fileId: file.value.id,
|
||||
comment: caption.length === 0 ? null : caption,
|
||||
}).then(async () => {
|
||||
await fetch();
|
||||
await _fetch_();
|
||||
});
|
||||
},
|
||||
closed: () => dispose(),
|
||||
|
@ -212,7 +212,7 @@ async function deleteFile() {
|
|||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetch();
|
||||
await _fetch_();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkTextarea>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
|
||||
<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : '' }">
|
||||
<div class="name">{{ file.name }}</div>
|
||||
<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
|
||||
</div>
|
||||
|
@ -88,7 +88,7 @@ async function save() {
|
|||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: props.postId,
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
const created = await os.apiWithDialog('gallery/posts/create', {
|
||||
|
@ -100,7 +100,7 @@ async function save() {
|
|||
router.push('/gallery/:postId', {
|
||||
params: {
|
||||
postId: created.id,
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ function close_(): void {
|
|||
}
|
||||
}
|
||||
|
||||
async function fetch() {
|
||||
async function _fetch_() {
|
||||
if (!url.value || !hash.value) {
|
||||
errorKV.value = {
|
||||
title: i18n.ts._externalResourceInstaller._errors._invalidParams.title,
|
||||
|
@ -229,7 +229,7 @@ async function install() {
|
|||
const urlParams = new URLSearchParams(window.location.search);
|
||||
url.value = urlParams.get('url');
|
||||
hash.value = urlParams.get('hash');
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
definePage(() => ({
|
||||
title: i18n.ts._externalResourceInstaller.title,
|
||||
|
|
|
@ -198,7 +198,7 @@ if (iAmModerator) {
|
|||
});
|
||||
}
|
||||
|
||||
async function fetch(): Promise<void> {
|
||||
async function _fetch_(): Promise<void> {
|
||||
if (iAmAdmin) {
|
||||
meta.value = await misskeyApi('admin/meta');
|
||||
}
|
||||
|
@ -276,7 +276,7 @@ function refreshMetadata(): void {
|
|||
});
|
||||
}
|
||||
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
text: `https://${props.host}`,
|
||||
|
|
|
@ -29,7 +29,7 @@ import MkButton from '@/components/MkButton.vue';
|
|||
|
||||
const state = ref<'fetching' | 'done'>('fetching');
|
||||
|
||||
function fetch() {
|
||||
function _fetch_() {
|
||||
const params = new URL(window.location.href).searchParams;
|
||||
|
||||
// acctのほうはdeprecated
|
||||
|
@ -44,20 +44,18 @@ function fetch() {
|
|||
if (uri.startsWith('https://')) {
|
||||
promise = misskeyApi('ap/show', {
|
||||
uri,
|
||||
});
|
||||
|
||||
promise.then(res => {
|
||||
}).then(res => {
|
||||
if (res.type === 'User') {
|
||||
mainRouter.replace('/@:acct/:page?', {
|
||||
params: {
|
||||
acct: res.host != null ? `${res.object.username}@${res.object.host}` : res.object.username,
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (res.type === 'Note') {
|
||||
mainRouter.replace('/notes/:noteId/:initialTab?', {
|
||||
params: {
|
||||
noteId: res.object.id,
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
os.alert({
|
||||
|
@ -70,12 +68,11 @@ function fetch() {
|
|||
if (uri.startsWith('acct:')) {
|
||||
uri = uri.slice(5);
|
||||
}
|
||||
promise = misskeyApi('users/show', Misskey.acct.parse(uri));
|
||||
promise.then(user => {
|
||||
promise = misskeyApi('users/show', Misskey.acct.parse(uri)).then(user => {
|
||||
mainRouter.replace('/@:acct/:page?', {
|
||||
params: {
|
||||
acct: user.host != null ? `${user.username}@${user.host}` : user.username,
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -96,7 +93,7 @@ function goToMisskey(): void {
|
|||
window.location.href = '/';
|
||||
}
|
||||
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
|
||||
|
|
|
@ -30,11 +30,11 @@ import { antennasCache } from '@/cache.js';
|
|||
|
||||
const antennas = computed(() => antennasCache.value.value ?? []);
|
||||
|
||||
function fetch() {
|
||||
function _fetch_() {
|
||||
antennasCache.fetch();
|
||||
}
|
||||
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
asFullButton: true,
|
||||
|
@ -42,7 +42,7 @@ const headerActions = computed(() => [{
|
|||
text: i18n.ts.reload,
|
||||
handler: () => {
|
||||
antennasCache.delete();
|
||||
fetch();
|
||||
_fetch_();
|
||||
},
|
||||
}]);
|
||||
|
||||
|
|
|
@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div v-if="items.length > 0" class="_gaps">
|
||||
<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
|
||||
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
||||
<MkAvatars :userIds="list.userIds" :limit="10"/>
|
||||
<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds!.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
|
||||
<MkAvatars :userIds="list.userIds!" :limit="10"/>
|
||||
</MkA>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,20 +40,20 @@ const $i = ensureSignin();
|
|||
|
||||
const items = computed(() => userListsCache.value.value ?? []);
|
||||
|
||||
function fetch() {
|
||||
function _fetch_() {
|
||||
userListsCache.fetch();
|
||||
}
|
||||
|
||||
fetch();
|
||||
_fetch_();
|
||||
|
||||
async function create() {
|
||||
const { canceled, result: name } = await os.inputText({
|
||||
title: i18n.ts.enterListName,
|
||||
});
|
||||
if (canceled) return;
|
||||
if (canceled || name == null) return;
|
||||
await os.apiWithDialog('users/lists/create', { name: name });
|
||||
userListsCache.delete();
|
||||
fetch();
|
||||
_fetch_();
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
|
@ -62,7 +62,7 @@ const headerActions = computed(() => [{
|
|||
text: i18n.ts.reload,
|
||||
handler: () => {
|
||||
userListsCache.delete();
|
||||
fetch();
|
||||
_fetch_();
|
||||
},
|
||||
}]);
|
||||
|
||||
|
@ -74,7 +74,7 @@ definePage(() => ({
|
|||
}));
|
||||
|
||||
onActivated(() => {
|
||||
fetch();
|
||||
_fetch_();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<MkFolder defaultOpen>
|
||||
<template #label>{{ i18n.ts.members }}</template>
|
||||
<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
||||
<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds!.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
|
||||
|
|
|
@ -31,7 +31,7 @@ import { Paginator } from '@/utility/paginator.js';
|
|||
|
||||
const tab = ref('all');
|
||||
const includeTypes = ref<string[] | null>(null);
|
||||
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value.includes(t)) : null);
|
||||
const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value!.includes(t)) : null);
|
||||
|
||||
const mentionsPaginator = markRaw(new Paginator('notes/mentions', {
|
||||
limit: 10,
|
||||
|
@ -71,7 +71,7 @@ const headerActions = computed(() => [tab.value === 'all' ? {
|
|||
text: i18n.ts.markAllAsRead,
|
||||
icon: 'ti ti-check',
|
||||
handler: () => {
|
||||
os.apiWithDialog('notifications/mark-all-as-read');
|
||||
os.apiWithDialog('notifications/mark-all-as-read', {});
|
||||
},
|
||||
} : undefined].filter(x => x !== undefined));
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
|
||||
|
||||
<div v-if="scopesWithDomain" class="_gaps_m">
|
||||
<FormSection v-for="domain in scopesWithDomain" :key="domain.domain">
|
||||
<FormSection v-for="domain in scopesWithDomain" :key="domain.domain ?? 'system'">
|
||||
<template #label>{{ domain.domain ? domain.domain.toUpperCase() : i18n.ts.system }}</template>
|
||||
<div class="_gaps_s">
|
||||
<FormLink v-for="scope in domain.scopes" :to="`/registry/keys/${domain.domain ?? '@'}/${scope.join('/')}`" class="_monospace">{{ scope.length === 0 ? '(root)' : scope.join('/') }}</FormLink>
|
||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<div v-if="$i.twoFactorEnabled" class="_gaps_s">
|
||||
<div v-text="i18n.ts._2fa.alreadyRegistered"/>
|
||||
<template v-if="$i.securityKeysList.length > 0">
|
||||
<template v-if="$i.securityKeysList!.length > 0">
|
||||
<MkButton @click="renewTOTP">{{ i18n.ts._2fa.renewTOTP }}</MkButton>
|
||||
<MkInfo>{{ i18n.ts._2fa.whyTOTPOnlyRenew }}</MkInfo>
|
||||
</template>
|
||||
|
@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
|
||||
<template v-else>
|
||||
<MkButton primary @click="addSecurityKey">{{ i18n.ts._2fa.registerSecurityKey }}</MkButton>
|
||||
<MkFolder v-for="key in $i.securityKeysList" :key="key.id">
|
||||
<MkFolder v-for="key in $i.securityKeysList!" :key="key.id">
|
||||
<template #label>{{ key.name }}</template>
|
||||
<template #suffix><I18n :src="i18n.ts.lastUsedAt"><template #t><MkTime :time="key.lastUsed"/></template></I18n></template>
|
||||
<div class="_buttons">
|
||||
|
@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</SearchMarker>
|
||||
|
||||
<SearchMarker :keywords="['password', 'less', 'key', 'passkey', 'login', 'signin']">
|
||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||
<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList!.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
|
||||
<template #label><SearchLabel>{{ i18n.ts.passwordLessLogin }}</SearchLabel></template>
|
||||
<template #caption><SearchText>{{ i18n.ts.passwordLessLoginDescription }}</SearchText></template>
|
||||
</MkSwitch>
|
||||
|
|
|
@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<!--<MkButton @click="refreshAllAccounts"><i class="ti ti-refresh"></i></MkButton>-->
|
||||
</div>
|
||||
|
||||
<MkUserCardMini v-for="x in accounts" :key="x[0] + x[1].id" :user="x[1]" :class="$style.user" @click.prevent="menu(x[0], x[1], $event)"/>
|
||||
<MkUserCardMini v-for="x in accounts" :key="x[0] + x[1].id" :user="x[1]" :class="$style.user" @click.prevent="showMenu(x[0], x[1], $event)"/>
|
||||
</div>
|
||||
</SearchMarker>
|
||||
</template>
|
||||
|
@ -36,7 +36,7 @@ function refreshAllAccounts() {
|
|||
// TODO
|
||||
}
|
||||
|
||||
function menu(host: string, account: Misskey.entities.UserDetailed, ev: MouseEvent) {
|
||||
function showMenu(host: string, account: Misskey.entities.UserDetailed, ev: MouseEvent) {
|
||||
let menu: MenuItem[];
|
||||
|
||||
menu = [{
|
||||
|
|
|
@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</template>
|
||||
<template #label>{{ token.name }}</template>
|
||||
<template #caption>{{ token.description }}</template>
|
||||
<template #suffix><MkTime :time="token.lastUsedAt"/></template>
|
||||
<template v-if="token.lastUsedAt != null" #suffix><MkTime :time="token.lastUsedAt"/></template>
|
||||
<template #footer>
|
||||
<MkButton danger @click="revoke(token)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</template>
|
||||
|
@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #key>{{ i18n.ts.installedDate }}</template>
|
||||
<template #value><MkTime :time="token.createdAt" :mode="'detail'"/></template>
|
||||
</MkKeyValue>
|
||||
<MkKeyValue oneline>
|
||||
<MkKeyValue v-if="token.lastUsedAt != null" oneline>
|
||||
<template #key>{{ i18n.ts.lastUsedDate }}</template>
|
||||
<template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template>
|
||||
</MkKeyValue>
|
||||
|
|
|
@ -17,13 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div :class="$style.decorations">
|
||||
<XDecoration
|
||||
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
||||
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id) ?? { id: '', url: '', name: '?', roleIdsThatCanBeUsedThisDecoration: [] }"
|
||||
:angle="avatarDecoration.angle"
|
||||
:flipH="avatarDecoration.flipH"
|
||||
:offsetX="avatarDecoration.offsetX"
|
||||
:offsetY="avatarDecoration.offsetY"
|
||||
:active="true"
|
||||
@click="openDecoration(avatarDecoration, i)"
|
||||
@click="openAttachedDecoration(i)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -50,6 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { ref, defineAsyncComponent, computed } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import XDecoration from './avatar-decoration.decoration.vue';
|
||||
import XDialog from './avatar-decoration.dialog.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import * as os from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
@ -68,14 +69,24 @@ misskeyApi('get-avatar-decorations').then(_avatarDecorations => {
|
|||
loading.value = false;
|
||||
});
|
||||
|
||||
async function openDecoration(avatarDecoration, index?: number) {
|
||||
const { dispose } = await os.popupAsyncWithDialog(import('./avatar-decoration.dialog.vue').then(x => x.default), {
|
||||
function openAttachedDecoration(index: number) {
|
||||
openDecoration(avatarDecorations.value.find(d => d.id === $i.avatarDecorations[index].id) ?? { id: '', url: '', name: '?', roleIdsThatCanBeUsedThisDecoration: [] }, index);
|
||||
}
|
||||
|
||||
async function openDecoration(avatarDecoration: {
|
||||
id: string;
|
||||
url: string;
|
||||
name: string;
|
||||
roleIdsThatCanBeUsedThisDecoration: string[];
|
||||
}, index?: number) {
|
||||
const { dispose } = os.popup(XDialog, {
|
||||
decoration: avatarDecoration,
|
||||
usingIndex: index,
|
||||
usingIndex: index ?? null,
|
||||
}, {
|
||||
'attach': async (payload) => {
|
||||
const decoration = {
|
||||
id: avatarDecoration.id,
|
||||
url: avatarDecoration.url,
|
||||
angle: payload.angle,
|
||||
flipH: payload.flipH,
|
||||
offsetX: payload.offsetX,
|
||||
|
@ -90,6 +101,7 @@ async function openDecoration(avatarDecoration, index?: number) {
|
|||
'update': async (payload) => {
|
||||
const decoration = {
|
||||
id: avatarDecoration.id,
|
||||
url: avatarDecoration.url,
|
||||
angle: payload.angle,
|
||||
flipH: payload.flipH,
|
||||
offsetX: payload.offsetX,
|
||||
|
|
|
@ -43,7 +43,7 @@ async function edit() {
|
|||
const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkWatermarkEditorDialog.vue')), {
|
||||
preset: deepClone(props.preset),
|
||||
}, {
|
||||
ok: (preset: WatermarkPreset) => {
|
||||
ok: (preset) => {
|
||||
emit('updatePreset', preset);
|
||||
},
|
||||
closed: () => dispose(),
|
||||
|
|
|
@ -74,7 +74,7 @@ import { instance } from '@/instance.js';
|
|||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const emailAddress = ref($i.email);
|
||||
const emailAddress = ref($i.email ?? '');
|
||||
|
||||
const onChangeReceiveAnnouncementEmail = (v) => {
|
||||
misskeyApi('i/update', {
|
||||
|
|
|
@ -188,6 +188,8 @@ const menuDef = computed<SuperMenuDef[]>(() => [{
|
|||
}]);
|
||||
|
||||
onMounted(() => {
|
||||
if (el.value == null) return; // TSを黙らすため
|
||||
|
||||
ro.observe(el.value);
|
||||
|
||||
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||
|
@ -198,6 +200,8 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
onActivated(() => {
|
||||
if (el.value == null) return; // TSを黙らすため
|
||||
|
||||
narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
|
||||
|
||||
if (!narrow.value && currentPage.value?.route.name == null) {
|
||||
|
@ -215,7 +219,7 @@ watch(router.currentRef, (to) => {
|
|||
}
|
||||
});
|
||||
|
||||
const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
|
||||
const emailNotConfigured = computed(() => $i && instance.enableEmail && ($i.email == null || !$i.emailVerified));
|
||||
|
||||
provideMetadataReceiver((metadataGetter) => {
|
||||
const info = metadataGetter();
|
||||
|
|
|
@ -160,10 +160,18 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label><SearchLabel>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkSelect :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null">
|
||||
<option :value="null">{{ i18n.ts.none }}</option>
|
||||
<option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option>
|
||||
<option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option>
|
||||
<MkSelect
|
||||
:items="[{
|
||||
value: null,
|
||||
label: i18n.ts.none
|
||||
}, {
|
||||
value: 'relative',
|
||||
label: i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod
|
||||
}, {
|
||||
value: 'absolute',
|
||||
label: i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime
|
||||
}] as const" :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null"
|
||||
>
|
||||
</MkSelect>
|
||||
|
||||
<MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore_selection">
|
||||
|
@ -262,7 +270,7 @@ const makeNotesFollowersOnlyBefore_presets = [
|
|||
const makeNotesFollowersOnlyBefore_isCustomMode = ref(
|
||||
makeNotesFollowersOnlyBefore.value != null &&
|
||||
makeNotesFollowersOnlyBefore.value < 0 &&
|
||||
!makeNotesFollowersOnlyBefore_presets.some((preset) => preset.value === makeNotesFollowersOnlyBefore.value)
|
||||
!makeNotesFollowersOnlyBefore_presets.some((preset) => preset.value === makeNotesFollowersOnlyBefore.value),
|
||||
);
|
||||
|
||||
const makeNotesFollowersOnlyBefore_selection = computed({
|
||||
|
@ -270,14 +278,14 @@ const makeNotesFollowersOnlyBefore_selection = computed({
|
|||
set(value) {
|
||||
makeNotesFollowersOnlyBefore_isCustomMode.value = value === 'custom';
|
||||
if (value !== 'custom') makeNotesFollowersOnlyBefore.value = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const makeNotesFollowersOnlyBefore_customMonths = computed({
|
||||
get: () => makeNotesFollowersOnlyBefore.value ? Math.abs(makeNotesFollowersOnlyBefore.value) / (30 * 24 * 60 * 60) : null,
|
||||
set(value) {
|
||||
if (value != null && value > 0) makeNotesFollowersOnlyBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const makeNotesHiddenBefore_type = computed(() => {
|
||||
|
@ -303,7 +311,7 @@ const makeNotesHiddenBefore_presets = [
|
|||
const makeNotesHiddenBefore_isCustomMode = ref(
|
||||
makeNotesHiddenBefore.value != null &&
|
||||
makeNotesHiddenBefore.value < 0 &&
|
||||
!makeNotesHiddenBefore_presets.some((preset) => preset.value === makeNotesHiddenBefore.value)
|
||||
!makeNotesHiddenBefore_presets.some((preset) => preset.value === makeNotesHiddenBefore.value),
|
||||
);
|
||||
|
||||
const makeNotesHiddenBefore_selection = computed({
|
||||
|
@ -311,14 +319,14 @@ const makeNotesHiddenBefore_selection = computed({
|
|||
set(value) {
|
||||
makeNotesHiddenBefore_isCustomMode.value = value === 'custom';
|
||||
if (value !== 'custom') makeNotesHiddenBefore.value = value;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const makeNotesHiddenBefore_customMonths = computed({
|
||||
get: () => makeNotesHiddenBefore.value ? Math.abs(makeNotesHiddenBefore.value) / (30 * 24 * 60 * 60) : null,
|
||||
set(value) {
|
||||
if (value != null && value > 0) makeNotesHiddenBefore.value = -Math.abs(Math.floor(Number(value))) * 30 * 24 * 60 * 60;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => {
|
||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<SearchMarker path="/settings/profile" :label="i18n.ts.profile" :keywords="['profile']" icon="ti ti-user">
|
||||
<div class="_gaps_m">
|
||||
<div class="_panel">
|
||||
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
|
||||
<div :class="$style.banner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : '' }">
|
||||
<div :class="$style.bannerEdit">
|
||||
<SearchMarker :keywords="['banner', 'change']">
|
||||
<MkButton primary rounded @click="changeBanner"><SearchLabel>{{ i18n.ts._profile.changeBanner }}</SearchLabel></MkButton>
|
||||
|
|
|
@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<MkSwitch v-model="statusbar.props.shuffle">
|
||||
<template #label>{{ i18n.ts.shuffle }}</template>
|
||||
</MkSwitch>
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" :min="1">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||
|
@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkSwitch>
|
||||
</template>
|
||||
<template v-else-if="statusbar.type === 'federation'">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" min="1">
|
||||
<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number" :min="1">
|
||||
<template #label>{{ i18n.ts.refreshInterval }}</template>
|
||||
</MkInput>
|
||||
<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
|
||||
|
@ -104,7 +104,7 @@ const props = defineProps<{
|
|||
userLists: Misskey.entities.UserList[] | null;
|
||||
}>();
|
||||
|
||||
const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id)));
|
||||
const statusbar = reactive(deepClone(prefer.s.statusbars.find(x => x.id === props._id))!);
|
||||
|
||||
watch(() => statusbar.type, () => {
|
||||
if (statusbar.type === 'rss') {
|
||||
|
|
|
@ -10,8 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</MkCodeEditor>
|
||||
|
||||
<div class="_buttons">
|
||||
<MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" inline @click="() => previewTheme(installThemeCode)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||
<MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" primary inline @click="() => install(installThemeCode)"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
||||
<MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" inline @click="() => previewTheme(installThemeCode!)"><i class="ti ti-eye"></i> {{ i18n.ts.preview }}</MkButton>
|
||||
<MkButton :disabled="installThemeCode == null || installThemeCode.trim() === ''" primary inline @click="() => install(installThemeCode!)"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -39,7 +39,7 @@ async function install(code: string): Promise<void> {
|
|||
});
|
||||
installThemeCode.value = null;
|
||||
router.push('/settings/theme');
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
switch (err.message.toLowerCase()) {
|
||||
case 'this theme is already installed':
|
||||
os.alert({
|
||||
|
|
|
@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts._theme.code }}</template>
|
||||
<template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template>
|
||||
</MkTextarea>
|
||||
<MkButton v-if="!builtinThemes.some(t => t.id == selectedTheme.id)" danger @click="uninstall()"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
|
||||
<MkButton v-if="!builtinThemes.some(t => t.id == selectedTheme!.id)" danger @click="uninstall()"><i class="ti ti-trash"></i> {{ i18n.ts.uninstall }}</MkButton>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -61,7 +61,7 @@ const event_reaction = ref(true);
|
|||
const event_mention = ref(true);
|
||||
|
||||
async function create(): Promise<void> {
|
||||
const events = [];
|
||||
const events: string[] = [];
|
||||
if (event_follow.value) events.push('follow');
|
||||
if (event_followed.value) events.push('followed');
|
||||
if (event_note.value) events.push('note');
|
||||
|
|
|
@ -52,7 +52,7 @@ async function post() {
|
|||
|
||||
const headerActions = computed(() => [{
|
||||
icon: 'ti ti-dots',
|
||||
label: i18n.ts.more,
|
||||
text: i18n.ts.more,
|
||||
handler: (ev: MouseEvent) => {
|
||||
os.popupMenu([{
|
||||
text: i18n.ts.embed,
|
||||
|
|
|
@ -11,12 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.backgroundColor }}</template>
|
||||
<div class="cwepdizn-colors">
|
||||
<div class="row">
|
||||
<button v-for="color in bgColors.filter(x => x.kind === 'light')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
|
||||
<button v-for="color in bgColors.filter(x => x.kind === 'light')" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
|
||||
<div class="preview" :style="{ background: color.forPreview }"></div>
|
||||
</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<button v-for="color in bgColors.filter(x => x.kind === 'dark')" :key="color.color" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
|
||||
<button v-for="color in bgColors.filter(x => x.kind === 'dark')" class="color _button" :class="{ active: theme.props.bg === color.color }" @click="setBgColor(color)">
|
||||
<div class="preview" :style="{ background: color.forPreview }"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.accentColor }}</template>
|
||||
<div class="cwepdizn-colors">
|
||||
<div class="row">
|
||||
<button v-for="color in accentColors" :key="color" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
|
||||
<button v-for="color in accentColors" class="color rounded _button" :class="{ active: theme.props.accent === color }" @click="setAccentColor(color)">
|
||||
<div class="preview" :style="{ background: color }"></div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<template #label>{{ i18n.ts.textColor }}</template>
|
||||
<div class="cwepdizn-colors">
|
||||
<div class="row">
|
||||
<button v-for="color in fgColors" :key="color" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
|
||||
<button v-for="color in fgColors" class="color char _button" :class="{ active: (theme.props.fg === color.forLight) || (theme.props.fg === color.forDark) }" @click="setFgColor(color)">
|
||||
<div class="preview" :style="{ color: color.forPreview ? color.forPreview : theme.base === 'light' ? '#5f5f5f' : '#dadada' }">A</div>
|
||||
</button>
|
||||
</div>
|
||||
|
@ -75,17 +75,17 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
import { watch, ref, computed } from 'vue';
|
||||
import { toUnicode } from 'punycode.js';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import JSON5 from 'json5';
|
||||
import lightTheme from '@@/themes/_light.json5';
|
||||
import darkTheme from '@@/themes/_dark.json5';
|
||||
import { host } from '@@/js/config.js';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import MkCodeEditor from '@/components/MkCodeEditor.vue';
|
||||
import MkTextarea from '@/components/MkTextarea.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import { $i } from '@/i.js';
|
||||
import { ensureSignin } from '@/i.js';
|
||||
import { addTheme, applyTheme } from '@/theme.js';
|
||||
import * as os from '@/os.js';
|
||||
import { store } from '@/store.js';
|
||||
|
@ -94,6 +94,8 @@ import { useLeaveGuard } from '@/composables/use-leave-guard.js';
|
|||
import { definePage } from '@/page.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
const $i = ensureSignin();
|
||||
|
||||
const bgColors = [
|
||||
{ color: '#f5f5f5', kind: 'light', forPreview: '#f5f5f5' },
|
||||
{ color: '#f0eee9', kind: 'light', forPreview: '#f3e2b9' },
|
||||
|
@ -123,12 +125,15 @@ const fgColors = [
|
|||
{ color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
|
||||
];
|
||||
|
||||
const theme = ref<Partial<Theme>>({
|
||||
const theme = ref<Theme>({
|
||||
id: genId(),
|
||||
name: 'untitled',
|
||||
author: `@${$i.username}@${toUnicode(host)}`,
|
||||
base: 'light',
|
||||
props: lightTheme.props,
|
||||
});
|
||||
const description = ref<string | null>(null);
|
||||
const themeCode = ref<string | null>(null);
|
||||
const themeCode = ref<string>('');
|
||||
const changed = ref(false);
|
||||
|
||||
useLeaveGuard(changed);
|
||||
|
@ -194,7 +199,6 @@ async function saveAs() {
|
|||
|
||||
theme.value.id = genId();
|
||||
theme.value.name = name;
|
||||
theme.value.author = `@${$i.username}@${toUnicode(host)}`;
|
||||
if (description.value) theme.value.desc = description.value;
|
||||
await addTheme(theme.value);
|
||||
applyTheme(theme.value);
|
||||
|
|
|
@ -36,7 +36,7 @@ const props = defineProps<{
|
|||
const chartEl = useTemplateRef('chartEl');
|
||||
const legendEl = useTemplateRef('legendEl');
|
||||
const now = new Date();
|
||||
let chartInstance: Chart = null;
|
||||
let chartInstance: Chart | null = null;
|
||||
const chartLimit = 30;
|
||||
const fetching = ref(true);
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div>
|
||||
<MkPagination v-slot="{items}" :paginator="type === 'following' ? followingPaginator : followersPaginator" withControl>
|
||||
<div :class="$style.users">
|
||||
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :user="user"/>
|
||||
<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee! : x.follower!)" :key="user.id" :user="user"/>
|
||||
</div>
|
||||
</MkPagination>
|
||||
</div>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue