Merge branch 'develop' into feat-14194
This commit is contained in:
commit
2f36c689ae
|
@ -1,5 +1,11 @@
|
||||||
|
# misskey settings
|
||||||
|
# MISSKEY_URL=https://example.tld/
|
||||||
|
|
||||||
# db settings
|
# db settings
|
||||||
POSTGRES_PASSWORD=example-misskey-pass
|
POSTGRES_PASSWORD=example-misskey-pass
|
||||||
|
# DATABASE_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
POSTGRES_USER=example-misskey-user
|
POSTGRES_USER=example-misskey-user
|
||||||
|
# DATABASE_USER=${POSTGRES_USER}
|
||||||
POSTGRES_DB=misskey
|
POSTGRES_DB=misskey
|
||||||
|
# DATABASE_DB=${POSTGRES_DB}
|
||||||
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
|
DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#───┘ URL └─────────────────────────────────────────────────────
|
#───┘ URL └─────────────────────────────────────────────────────
|
||||||
|
|
||||||
# Final accessible URL seen by a user.
|
# Final accessible URL seen by a user.
|
||||||
|
# You can set url from an environment variable instead.
|
||||||
url: https://example.tld/
|
url: https://example.tld/
|
||||||
|
|
||||||
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE
|
||||||
|
@ -38,9 +39,11 @@ db:
|
||||||
port: 5432
|
port: 5432
|
||||||
|
|
||||||
# Database name
|
# Database name
|
||||||
|
# You can set db from an environment variable instead.
|
||||||
db: misskey
|
db: misskey
|
||||||
|
|
||||||
# Auth
|
# Auth
|
||||||
|
# You can set user and pass from environment variables instead.
|
||||||
user: example-misskey-user
|
user: example-misskey-user
|
||||||
pass: example-misskey-pass
|
pass: example-misskey-pass
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,11 @@ on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/api-misskey-js.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/api-misskey-js.yml
|
||||||
jobs:
|
jobs:
|
||||||
report:
|
report:
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- name: Checkout head
|
- name: Checkout head
|
||||||
uses: actions/checkout@v4.1.1
|
uses: actions/checkout@v4.1.1
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ jobs:
|
||||||
|
|
||||||
- name: setup node
|
- name: setup node
|
||||||
id: setup-node
|
id: setup-node
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
|
@ -6,12 +6,13 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/package.json
|
- packages/misskey-js/package.json
|
||||||
- package.json
|
- package.json
|
||||||
|
- .github/workflows/check-misskey-js-version.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/package.json
|
- packages/misskey-js/package.json
|
||||||
- package.json
|
- package.json
|
||||||
|
- .github/workflows/check-misskey-js-version.yml
|
||||||
jobs:
|
jobs:
|
||||||
check-version:
|
check-version:
|
||||||
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
# ルートの package.json と packages/misskey-js/package.json のバージョンが一致しているかを確認する
|
||||||
|
|
|
@ -9,7 +9,7 @@ on:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
- .github/workflows/get-api-diff.yml
|
- .github/workflows/get-api-diff.yml
|
||||||
|
- .github/workflows/get-api-diff.yml
|
||||||
jobs:
|
jobs:
|
||||||
get-from-misskey:
|
get-from-misskey:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -34,7 +34,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -11,6 +11,7 @@ on:
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
|
- .github/workflows/lint.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
@ -18,7 +19,7 @@ on:
|
||||||
- packages/sw/**
|
- packages/sw/**
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
- packages/shared/eslint.config.js
|
- packages/shared/eslint.config.js
|
||||||
|
- .github/workflows/lint.yml
|
||||||
jobs:
|
jobs:
|
||||||
pnpm_install:
|
pnpm_install:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -28,7 +29,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -39,6 +40,8 @@ jobs:
|
||||||
needs: [pnpm_install]
|
needs: [pnpm_install]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
|
env:
|
||||||
|
eslint-cache-version: v1
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
workspace:
|
workspace:
|
||||||
|
@ -52,13 +55,20 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- run: pnpm --filter ${{ matrix.workspace }} run eslint
|
- name: Restore eslint cache
|
||||||
|
uses: actions/cache@v4.0.2
|
||||||
|
with:
|
||||||
|
path: node_modules/.cache/eslint
|
||||||
|
key: eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-${{ github.ref_name }}-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
eslint-${{ env.eslint-cache-version }}-${{ hashFiles('/pnpm-lock.yaml') }}-
|
||||||
|
- run: pnpm --filter ${{ matrix.workspace }} run eslint --cache --cache-location node_modules/.cache/eslint --cache-strategy content
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
needs: [pnpm_install]
|
needs: [pnpm_install]
|
||||||
|
@ -75,7 +85,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -4,10 +4,11 @@ on:
|
||||||
push:
|
push:
|
||||||
paths:
|
paths:
|
||||||
- locales/**
|
- locales/**
|
||||||
|
- .github/workflows/locale.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- locales/**
|
- locales/**
|
||||||
|
- .github/workflows/locale.yml
|
||||||
jobs:
|
jobs:
|
||||||
locale_verify:
|
locale_verify:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -18,7 +19,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
submodules: true
|
submodules: true
|
||||||
- uses: pnpm/action-setup@v4
|
- uses: pnpm/action-setup@v4
|
||||||
- uses: actions/setup-node@v4.0.2
|
- uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -26,7 +26,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -6,7 +6,7 @@ on:
|
||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- 'CHANGELOG.md'
|
- 'CHANGELOG.md'
|
||||||
|
# - .github/workflows/release-edit-with-push.yml
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js 20.x
|
- name: Use Node.js 20.x
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: '.node-version'
|
node-version-file: '.node-version'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -9,12 +9,13 @@ on:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
# for permissions
|
# for permissions
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-backend.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
# for permissions
|
# for permissions
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-backend.yml
|
||||||
jobs:
|
jobs:
|
||||||
unit:
|
unit:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -45,7 +46,7 @@ jobs:
|
||||||
- name: Install FFmpeg
|
- name: Install FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -92,7 +93,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -11,7 +11,7 @@ on:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
# for e2e
|
# for e2e
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/test-frontend.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/frontend/**
|
- packages/frontend/**
|
||||||
|
@ -19,7 +19,7 @@ on:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
# for e2e
|
# for e2e
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/test-frontend.yml
|
||||||
jobs:
|
jobs:
|
||||||
vitest:
|
vitest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
@ -90,7 +90,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -8,11 +8,12 @@ on:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-misskey-js.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- packages/misskey-js/**
|
- packages/misskey-js/**
|
||||||
|
- .github/workflows/test-misskey-js.yml
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ jobs:
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
|
|
||||||
- name: Setup Node.js ${{ matrix.node-version }}
|
- name: Setup Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -7,10 +7,11 @@ on:
|
||||||
- develop
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/validate-api-json.yml
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
paths:
|
||||||
- packages/backend/**
|
- packages/backend/**
|
||||||
|
- .github/workflows/validate-api-json.yml
|
||||||
jobs:
|
jobs:
|
||||||
validate-api-json:
|
validate-api-json:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -26,7 +27,7 @@ jobs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v4.0.2
|
uses: actions/setup-node@v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
|
|
|
@ -12,12 +12,16 @@
|
||||||
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
- Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正
|
||||||
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題
|
||||||
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正
|
- Fix: デフォルトテーマに無効なテーマコードを入力するとUIが使用できなくなる問題を修正
|
||||||
|
- Enhance: Allow negative delay for MFM animation elements (`tada`, `jelly`, `twitch`, `shake`, `spin`, `jump`, `bounce`, `rainbow`)
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
- Enhance: 内蔵APIドキュメントのデザイン・パフォーマンスを改善
|
||||||
|
- Enhance: 非ログイン時に他サーバーに遷移するアクションを追加
|
||||||
- Enhance: 非ログイン時のハイライトTLのデザインを改善
|
- Enhance: 非ログイン時のハイライトTLのデザインを改善
|
||||||
- Enhance: フロントエンドのアクセシビリティ改善
|
- Enhance: フロントエンドのアクセシビリティ改善
|
||||||
(Based on https://github.com/taiyme/misskey/pull/226)
|
(Based on https://github.com/taiyme/misskey/pull/226)
|
||||||
|
- Enhance: サーバー情報ページ・お問い合わせページを改善
|
||||||
|
(Cherry-picked from https://github.com/taiyme/misskey/pull/238)
|
||||||
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
- Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正
|
||||||
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
- Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968)
|
||||||
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
- Fix: リバーシの対局を正しく共有できないことがある問題を修正
|
||||||
|
@ -35,6 +39,7 @@
|
||||||
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
- Enhance: エンドポイント`gallery/posts/update`の必須項目を`postId`のみに
|
||||||
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
- Enhance: エンドポイント`i/webhook/update`の必須項目を`webhookId`のみに
|
||||||
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
- Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに
|
||||||
|
- Enhance: `default.yml`内の`url`, `db.db`, `db.user`, `db.pass`を環境変数から読み込めるように
|
||||||
- Fix: チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
- Fix: チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正
|
||||||
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
- Fix: ユーザーのフィードページのMFMをHTMLに展開するように (#14006)
|
||||||
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
- Fix: アンテナ・クリップ・リスト・ウェブフックがロールポリシーの上限より一つ多く作れてしまうのを修正 (#14036)
|
||||||
|
@ -52,6 +57,8 @@
|
||||||
4. フォローしていない非アクティブなユーザ
|
4. フォローしていない非アクティブなユーザ
|
||||||
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
|
- Fix: 一般ユーザーから見たユーザーのバッジの一覧に公開されていないものが含まれることがある問題を修正
|
||||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
|
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/652)
|
||||||
|
- Fix: ユーザーのリアクション一覧でミュート/ブロックが機能していなかった問題を修正
|
||||||
|
- Fix: エラーメッセージの誤字を修正 (#14213)
|
||||||
|
|
||||||
### Misskey.js
|
### Misskey.js
|
||||||
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
- Feat: `/drive/files/create` のリクエストに対応(`multipart/form-data`に対応)
|
||||||
|
|
|
@ -17,6 +17,8 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- internal_network
|
- internal_network
|
||||||
- external_network
|
- external_network
|
||||||
|
# env_file:
|
||||||
|
# - .config/docker.env
|
||||||
volumes:
|
volumes:
|
||||||
- ./files:/misskey/files
|
- ./files:/misskey/files
|
||||||
- ./.config:/misskey/.config:ro
|
- ./.config:/misskey/.config:ro
|
||||||
|
|
|
@ -736,6 +736,22 @@ export interface Locale extends ILocale {
|
||||||
* リモートで表示
|
* リモートで表示
|
||||||
*/
|
*/
|
||||||
"showOnRemote": string;
|
"showOnRemote": string;
|
||||||
|
/**
|
||||||
|
* リモートで続行
|
||||||
|
*/
|
||||||
|
"continueOnRemote": string;
|
||||||
|
/**
|
||||||
|
* Misskey Hubからサーバーを選択
|
||||||
|
*/
|
||||||
|
"chooseServerOnMisskeyHub": string;
|
||||||
|
/**
|
||||||
|
* サーバーのドメインを直接指定
|
||||||
|
*/
|
||||||
|
"specifyServerHost": string;
|
||||||
|
/**
|
||||||
|
* ドメインを入力してください
|
||||||
|
*/
|
||||||
|
"inputHostName": string;
|
||||||
/**
|
/**
|
||||||
* 全般
|
* 全般
|
||||||
*/
|
*/
|
||||||
|
@ -1921,9 +1937,13 @@ export interface Locale extends ILocale {
|
||||||
*/
|
*/
|
||||||
"onlyOneFileCanBeAttached": string;
|
"onlyOneFileCanBeAttached": string;
|
||||||
/**
|
/**
|
||||||
* 続行する前に、サインアップまたはサインインが必要です
|
* 続行する前に、登録またはログインが必要です
|
||||||
*/
|
*/
|
||||||
"signinRequired": string;
|
"signinRequired": string;
|
||||||
|
/**
|
||||||
|
* 続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります
|
||||||
|
*/
|
||||||
|
"signinOrContinueOnRemote": string;
|
||||||
/**
|
/**
|
||||||
* 招待
|
* 招待
|
||||||
*/
|
*/
|
||||||
|
@ -4984,6 +5004,10 @@ export interface Locale extends ILocale {
|
||||||
* お問い合わせ
|
* お問い合わせ
|
||||||
*/
|
*/
|
||||||
"inquiry": string;
|
"inquiry": string;
|
||||||
|
/**
|
||||||
|
* もう一度お試しください。
|
||||||
|
*/
|
||||||
|
"tryAgain": string;
|
||||||
/**
|
/**
|
||||||
* ページ閲覧数
|
* ページ閲覧数
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -180,6 +180,10 @@ addAccount: "アカウントを追加"
|
||||||
reloadAccountsList: "アカウントリストの情報を更新"
|
reloadAccountsList: "アカウントリストの情報を更新"
|
||||||
loginFailed: "ログインに失敗しました"
|
loginFailed: "ログインに失敗しました"
|
||||||
showOnRemote: "リモートで表示"
|
showOnRemote: "リモートで表示"
|
||||||
|
continueOnRemote: "リモートで続行"
|
||||||
|
chooseServerOnMisskeyHub: "Misskey Hubからサーバーを選択"
|
||||||
|
specifyServerHost: "サーバーのドメインを直接指定"
|
||||||
|
inputHostName: "ドメインを入力してください"
|
||||||
general: "全般"
|
general: "全般"
|
||||||
wallpaper: "壁紙"
|
wallpaper: "壁紙"
|
||||||
setWallpaper: "壁紙を設定"
|
setWallpaper: "壁紙を設定"
|
||||||
|
@ -476,7 +480,8 @@ attachAsFileQuestion: "クリップボードのテキストが長いです。テ
|
||||||
noMessagesYet: "まだチャットはありません"
|
noMessagesYet: "まだチャットはありません"
|
||||||
newMessageExists: "新しいメッセージがあります"
|
newMessageExists: "新しいメッセージがあります"
|
||||||
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
onlyOneFileCanBeAttached: "メッセージに添付できるファイルはひとつです"
|
||||||
signinRequired: "続行する前に、サインアップまたはサインインが必要です"
|
signinRequired: "続行する前に、登録またはログインが必要です"
|
||||||
|
signinOrContinueOnRemote: "続行するには、お使いのサーバーに移動するか、このサーバーに登録・ログインする必要があります"
|
||||||
invitations: "招待"
|
invitations: "招待"
|
||||||
invitationCode: "招待コード"
|
invitationCode: "招待コード"
|
||||||
checking: "確認しています"
|
checking: "確認しています"
|
||||||
|
@ -1242,6 +1247,7 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ
|
||||||
noDescription: "説明文はありません"
|
noDescription: "説明文はありません"
|
||||||
alwaysConfirmFollow: "フォローの際常に確認する"
|
alwaysConfirmFollow: "フォローの際常に確認する"
|
||||||
inquiry: "お問い合わせ"
|
inquiry: "お問い合わせ"
|
||||||
|
tryAgain: "もう一度お試しください。"
|
||||||
pageViewCount: "ページ閲覧数"
|
pageViewCount: "ページ閲覧数"
|
||||||
preferPopularUserFactor: "人気のユーザーの算出基準"
|
preferPopularUserFactor: "人気のユーザーの算出基準"
|
||||||
preferPopularUserFactorDescription: "ページ閲覧数はローカルユーザーにのみ適用されます(リモートユーザーはフォロワー数で表示されます)。「無効」に設定すると、ローカル・リモートどちらの「人気のユーザー」セクションも表示されなくなります。"
|
preferPopularUserFactorDescription: "ページ閲覧数はローカルユーザーにのみ適用されます(リモートユーザーはフォロワー数で表示されます)。「無効」に設定すると、ローカル・リモートどちらの「人気のユーザー」セクションも表示されなくなります。"
|
||||||
|
|
|
@ -23,7 +23,7 @@ type RedisOptionsSource = Partial<RedisOptions> & {
|
||||||
* 設定ファイルの型
|
* 設定ファイルの型
|
||||||
*/
|
*/
|
||||||
type Source = {
|
type Source = {
|
||||||
url: string;
|
url?: string;
|
||||||
port?: number;
|
port?: number;
|
||||||
socket?: string;
|
socket?: string;
|
||||||
chmodSocket?: string;
|
chmodSocket?: string;
|
||||||
|
@ -31,9 +31,9 @@ type Source = {
|
||||||
db: {
|
db: {
|
||||||
host: string;
|
host: string;
|
||||||
port: number;
|
port: number;
|
||||||
db: string;
|
db?: string;
|
||||||
user: string;
|
user?: string;
|
||||||
pass: string;
|
pass?: string;
|
||||||
disableCache?: boolean;
|
disableCache?: boolean;
|
||||||
extra?: { [x: string]: string };
|
extra?: { [x: string]: string };
|
||||||
};
|
};
|
||||||
|
@ -202,13 +202,17 @@ export function loadConfig(): Config {
|
||||||
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
|
||||||
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
|
||||||
|
|
||||||
const url = tryCreateUrl(config.url);
|
const url = tryCreateUrl(config.url ?? process.env.MISSKEY_URL ?? '');
|
||||||
const version = meta.version;
|
const version = meta.version;
|
||||||
const host = url.host;
|
const host = url.host;
|
||||||
const hostname = url.hostname;
|
const hostname = url.hostname;
|
||||||
const scheme = url.protocol.replace(/:$/, '');
|
const scheme = url.protocol.replace(/:$/, '');
|
||||||
const wsScheme = scheme.replace('http', 'ws');
|
const wsScheme = scheme.replace('http', 'ws');
|
||||||
|
|
||||||
|
const dbDb = config.db.db ?? process.env.DATABASE_DB ?? '';
|
||||||
|
const dbUser = config.db.user ?? process.env.DATABASE_USER ?? '';
|
||||||
|
const dbPass = config.db.pass ?? process.env.DATABASE_PASSWORD ?? '';
|
||||||
|
|
||||||
const externalMediaProxy = config.mediaProxy ?
|
const externalMediaProxy = config.mediaProxy ?
|
||||||
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
config.mediaProxy.endsWith('/') ? config.mediaProxy.substring(0, config.mediaProxy.length - 1) : config.mediaProxy
|
||||||
: null;
|
: null;
|
||||||
|
@ -231,7 +235,7 @@ export function loadConfig(): Config {
|
||||||
apiUrl: `${scheme}://${host}/api`,
|
apiUrl: `${scheme}://${host}/api`,
|
||||||
authUrl: `${scheme}://${host}/auth`,
|
authUrl: `${scheme}://${host}/auth`,
|
||||||
driveUrl: `${scheme}://${host}/files`,
|
driveUrl: `${scheme}://${host}/files`,
|
||||||
db: config.db,
|
db: { ...config.db, db: dbDb, user: dbUser, pass: dbPass },
|
||||||
dbReplications: config.dbReplications,
|
dbReplications: config.dbReplications,
|
||||||
dbSlaves: config.dbSlaves,
|
dbSlaves: config.dbSlaves,
|
||||||
meilisearch: config.meilisearch,
|
meilisearch: config.meilisearch,
|
||||||
|
@ -259,7 +263,7 @@ export function loadConfig(): Config {
|
||||||
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
deliverJobMaxAttempts: config.deliverJobMaxAttempts,
|
||||||
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
inboxJobMaxAttempts: config.inboxJobMaxAttempts,
|
||||||
proxyRemoteFiles: config.proxyRemoteFiles,
|
proxyRemoteFiles: config.proxyRemoteFiles,
|
||||||
signToActivityPubGet: config.signToActivityPubGet,
|
signToActivityPubGet: config.signToActivityPubGet ?? true,
|
||||||
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
mediaProxy: externalMediaProxy ?? internalMediaProxy,
|
||||||
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
|
||||||
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
videoThumbnailGenerator: config.videoThumbnailGenerator ?
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// dummy
|
||||||
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
export const MAX_NOTE_TEXT_LENGTH = 3000;
|
||||||
|
|
||||||
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
|
||||||
|
|
|
@ -74,10 +74,10 @@ export class ApQuestionService {
|
||||||
|
|
||||||
//#region このサーバーに既に登録されているか
|
//#region このサーバーに既に登録されているか
|
||||||
const note = await this.notesRepository.findOneBy({ uri });
|
const note = await this.notesRepository.findOneBy({ uri });
|
||||||
if (note == null) throw new Error('Question is not registed');
|
if (note == null) throw new Error('Question is not registered');
|
||||||
|
|
||||||
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
|
||||||
if (poll == null) throw new Error('Question is not registed');
|
if (poll == null) throw new Error('Question is not registered');
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
// resolve new Question object
|
// resolve new Question object
|
||||||
|
|
|
@ -4,6 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
|
||||||
|
if (!note) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (userIds.has(note.userId) && !ignoreAuthor) {
|
if (userIds.has(note.userId) && !ignoreAuthor) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { DI } from '@/di-symbols.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -74,6 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
) {
|
) {
|
||||||
super(meta, paramDef, async (ps, me) => {
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const userIdsWhoBlockingMe = me ? await this.cacheService.userBlockedCache.fetch(me.id) : new Set<string>();
|
||||||
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
|
const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
|
||||||
if (!iAmModerator) {
|
if (!iAmModerator) {
|
||||||
const user = await this.cacheService.findUserById(ps.userId);
|
const user = await this.cacheService.findUserById(ps.userId);
|
||||||
|
@ -85,8 +87,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
|
if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
|
||||||
throw new ApiError(meta.errors.reactionsNotPublic);
|
throw new ApiError(meta.errors.reactionsNotPublic);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// early return if me is blocked by requesting user
|
||||||
|
if (userIdsWhoBlockingMe.has(ps.userId)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userIdsWhoMeMuting = me ? await this.cacheService.userMutingsCache.fetch(me.id) : new Set<string>();
|
||||||
|
|
||||||
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
|
||||||
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
.andWhere('reaction.userId = :userId', { userId: ps.userId })
|
||||||
|
@ -94,9 +103,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
|
|
||||||
this.queryService.generateVisibilityQuery(query, me);
|
this.queryService.generateVisibilityQuery(query, me);
|
||||||
|
|
||||||
const reactions = await query
|
const reactions = (await query
|
||||||
.limit(ps.limit)
|
.limit(ps.limit)
|
||||||
.getMany();
|
.getMany()).filter(reaction => {
|
||||||
|
if (reaction.note?.userId === ps.userId) return true; // we can see reactions to note of requesting user
|
||||||
|
if (me && isUserRelated(reaction.note, userIdsWhoBlockingMe)) return false;
|
||||||
|
if (me && isUserRelated(reaction.note, userIdsWhoMeMuting)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,6 +42,8 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { useStream } from '@/stream.js';
|
import { useStream } from '@/stream.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
|
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
||||||
|
@ -63,7 +65,7 @@ const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFro
|
||||||
const wait = ref(false);
|
const wait = ref(false);
|
||||||
const connection = useStream().useChannel('main');
|
const connection = useStream().useChannel('main');
|
||||||
|
|
||||||
if (props.user.isFollowing == null) {
|
if (props.user.isFollowing == null && $i) {
|
||||||
misskeyApi('users/show', {
|
misskeyApi('users/show', {
|
||||||
userId: props.user.id,
|
userId: props.user.id,
|
||||||
})
|
})
|
||||||
|
@ -78,6 +80,8 @@ function onFollowChange(user: Misskey.entities.UserDetailed) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
|
pleaseLogin(undefined, { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` });
|
||||||
|
|
||||||
wait.value = true;
|
wait.value = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -54,6 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
:class="['_button', $style.item]"
|
:class="['_button', $style.item]"
|
||||||
:href="item.href"
|
:href="item.href"
|
||||||
:target="item.target"
|
:target="item.target"
|
||||||
|
:rel="item.target === '_blank' ? 'noopener noreferrer' : undefined"
|
||||||
:download="item.download"
|
:download="item.download"
|
||||||
@click.passive="close(true)"
|
@click.passive="close(true)"
|
||||||
@mouseenter.passive="onItemMouseEnter"
|
@mouseenter.passive="onItemMouseEnter"
|
||||||
|
|
|
@ -174,7 +174,7 @@ import MkPoll from '@/components/MkPoll.vue';
|
||||||
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
import number from '@/filters/number.js';
|
import number from '@/filters/number.js';
|
||||||
|
@ -196,6 +196,7 @@ import { MenuItem } from '@/types/menu.js';
|
||||||
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
import MkRippleEffect from '@/components/MkRippleEffect.vue';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||||
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
import { shouldCollapsed } from '@/scripts/collapsed.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
import { isEnabledUrlPreview } from '@/instance.js';
|
import { isEnabledUrlPreview } from '@/instance.js';
|
||||||
import { type Keymap } from '@/scripts/hotkey.js';
|
import { type Keymap } from '@/scripts/hotkey.js';
|
||||||
import { focusPrev, focusNext } from '@/scripts/focus.js';
|
import { focusPrev, focusNext } from '@/scripts/focus.js';
|
||||||
|
@ -278,6 +279,11 @@ const renoteCollapsed = ref(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
|
type: 'lookup',
|
||||||
|
url: `https://${host}/notes/${appearNote.value.id}`,
|
||||||
|
}));
|
||||||
|
|
||||||
/* Overload FunctionにLintが対応していないのでコメントアウト
|
/* Overload FunctionにLintが対応していないのでコメントアウト
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
|
||||||
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
|
||||||
|
@ -411,7 +417,7 @@ if (!props.mock) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function renote(viaKeyboard = false) {
|
function renote(viaKeyboard = false) {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
|
|
||||||
const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
|
const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
|
||||||
|
@ -421,7 +427,7 @@ function renote(viaKeyboard = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reply(): void {
|
function reply(): void {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
if (props.mock) {
|
if (props.mock) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -434,7 +440,7 @@ function reply(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function react(): void {
|
function react(): void {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
@ -565,7 +571,7 @@ function showRenoteMenu(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMyRenote) {
|
if (isMyRenote) {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
os.popupMenu([
|
os.popupMenu([
|
||||||
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
|
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
|
||||||
{ type: 'divider' },
|
{ type: 'divider' },
|
||||||
|
|
|
@ -209,7 +209,7 @@ import MkPoll from '@/components/MkPoll.vue';
|
||||||
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
|
||||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||||
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
|
||||||
import { pleaseLogin } from '@/scripts/please-login.js';
|
import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||||
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
import { checkWordMute } from '@/scripts/check-word-mute.js';
|
||||||
import { userPage } from '@/filters/user.js';
|
import { userPage } from '@/filters/user.js';
|
||||||
import { notePage } from '@/filters/note.js';
|
import { notePage } from '@/filters/note.js';
|
||||||
|
@ -222,6 +222,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
|
||||||
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js';
|
import { getNoteClipMenu, getNoteMenu, getRenoteMenu } from '@/scripts/get-note-menu.js';
|
||||||
import { useNoteCapture } from '@/scripts/use-note-capture.js';
|
import { useNoteCapture } from '@/scripts/use-note-capture.js';
|
||||||
import { deepClone } from '@/scripts/clone.js';
|
import { deepClone } from '@/scripts/clone.js';
|
||||||
|
@ -296,6 +297,11 @@ const conversation = ref<Misskey.entities.Note[]>([]);
|
||||||
const replies = ref<Misskey.entities.Note[]>([]);
|
const replies = ref<Misskey.entities.Note[]>([]);
|
||||||
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i?.id);
|
||||||
|
|
||||||
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
|
type: 'lookup',
|
||||||
|
url: `https://${host}/notes/${appearNote.value.id}`,
|
||||||
|
}));
|
||||||
|
|
||||||
const keymap = {
|
const keymap = {
|
||||||
'r': () => reply(),
|
'r': () => reply(),
|
||||||
'e|a|plus': () => react(),
|
'e|a|plus': () => react(),
|
||||||
|
@ -396,7 +402,7 @@ if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
}
|
}
|
||||||
|
|
||||||
function renote() {
|
function renote() {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
|
|
||||||
const { menu } = getRenoteMenu({ note: note.value, renoteButton });
|
const { menu } = getRenoteMenu({ note: note.value, renoteButton });
|
||||||
|
@ -404,7 +410,7 @@ function renote() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function reply(): void {
|
function reply(): void {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
os.post({
|
os.post({
|
||||||
reply: appearNote.value,
|
reply: appearNote.value,
|
||||||
|
@ -415,7 +421,7 @@ function reply(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function react(): void {
|
function react(): void {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
if (appearNote.value.reactionAcceptance === 'likeOnly') {
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
|
@ -499,7 +505,7 @@ async function clip(): Promise<void> {
|
||||||
|
|
||||||
function showRenoteMenu(): void {
|
function showRenoteMenu(): void {
|
||||||
if (!isMyRenote) return;
|
if (!isMyRenote) return;
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
os.popupMenu([{
|
os.popupMenu([{
|
||||||
text: i18n.ts.unrenote,
|
text: i18n.ts.unrenote,
|
||||||
icon: 'ti ti-trash',
|
icon: 'ti ti-trash',
|
||||||
|
|
|
@ -34,7 +34,9 @@ import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { host } from '@/config.js';
|
||||||
import { useInterval } from '@/scripts/use-interval.js';
|
import { useInterval } from '@/scripts/use-interval.js';
|
||||||
|
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
noteId: string;
|
noteId: string;
|
||||||
|
@ -60,6 +62,11 @@ const timer = computed(() => i18n.tsx._poll[
|
||||||
|
|
||||||
const showResult = ref(props.readOnly || isVoted.value);
|
const showResult = ref(props.readOnly || isVoted.value);
|
||||||
|
|
||||||
|
const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({
|
||||||
|
type: 'lookup',
|
||||||
|
url: `https://${host}/notes/${props.noteId}`,
|
||||||
|
}));
|
||||||
|
|
||||||
// 期限付きアンケート
|
// 期限付きアンケート
|
||||||
if (props.poll.expiresAt) {
|
if (props.poll.expiresAt) {
|
||||||
const tick = () => {
|
const tick = () => {
|
||||||
|
@ -76,7 +83,7 @@ if (props.poll.expiresAt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const vote = async (id) => {
|
const vote = async (id) => {
|
||||||
pleaseLogin();
|
pleaseLogin(undefined, pleaseLoginContext.value);
|
||||||
|
|
||||||
if (props.readOnly || closed.value || isVoted.value) return;
|
if (props.readOnly || closed.value || isVoted.value) return;
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,23 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
|
<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
|
<div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${user.avatarUrl}')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div>
|
||||||
<MkInfo v-if="message">
|
<MkInfo v-if="message">
|
||||||
{{ message }}
|
{{ message }}
|
||||||
</MkInfo>
|
</MkInfo>
|
||||||
|
<div v-if="openOnRemote" class="_gaps_m">
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<MkButton type="button" rounded primary style="margin: 0 auto;" @click="openRemote(openOnRemote)">
|
||||||
|
{{ i18n.ts.continueOnRemote }} <i class="ti ti-external-link"></i>
|
||||||
|
</MkButton>
|
||||||
|
<button type="button" class="_button" :class="$style.instanceManualSelectButton" @click="specifyHostAndOpenRemote(openOnRemote)">
|
||||||
|
{{ i18n.ts.specifyServerHost }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div :class="$style.orHr">
|
||||||
|
<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div v-if="!totpLogin" class="normal-signin _gaps_m">
|
<div v-if="!totpLogin" class="normal-signin _gaps_m">
|
||||||
<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
|
||||||
<template #prefix>@</template>
|
<template #prefix>@</template>
|
||||||
|
@ -28,8 +41,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
{{ i18n.ts.retry }}
|
{{ i18n.ts.retry }}
|
||||||
</MkButton>
|
</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="user && user.securityKeys" class="or-hr">
|
<div v-if="user && user.securityKeys" :class="$style.orHr">
|
||||||
<p class="or-msg">{{ i18n.ts.or }}</p>
|
<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="twofa-group totp-group _gaps">
|
<div class="twofa-group totp-group _gaps">
|
||||||
<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required>
|
<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :withPasswordToggle="true" required>
|
||||||
|
@ -53,6 +66,7 @@ import { defineAsyncComponent, ref } from 'vue';
|
||||||
import { toUnicode } from 'punycode/';
|
import { toUnicode } from 'punycode/';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
|
||||||
|
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||||
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
@ -60,6 +74,7 @@ import MkInfo from '@/components/MkInfo.vue';
|
||||||
import { host as configHost } from '@/config.js';
|
import { host as configHost } from '@/config.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import { query, extractDomain } from '@/scripts/url.js';
|
||||||
import { login } from '@/account.js';
|
import { login } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
@ -78,22 +93,16 @@ const emit = defineEmits<{
|
||||||
(ev: 'login', v: any): void;
|
(ev: 'login', v: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const props = defineProps({
|
const props = withDefaults(defineProps<{
|
||||||
withAvatar: {
|
withAvatar?: boolean;
|
||||||
type: Boolean,
|
autoSet?: boolean;
|
||||||
required: false,
|
message?: string,
|
||||||
default: true,
|
openOnRemote?: OpenOnRemoteOptions,
|
||||||
},
|
}>(), {
|
||||||
autoSet: {
|
withAvatar: true,
|
||||||
type: Boolean,
|
autoSet: false,
|
||||||
required: false,
|
message: '',
|
||||||
default: false,
|
openOnRemote: undefined,
|
||||||
},
|
|
||||||
message: {
|
|
||||||
type: String,
|
|
||||||
required: false,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function onUsernameChange(): void {
|
function onUsernameChange(): void {
|
||||||
|
@ -222,6 +231,62 @@ function resetPassword(): void {
|
||||||
closed: () => dispose(),
|
closed: () => dispose(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void {
|
||||||
|
switch (options.type) {
|
||||||
|
case 'web':
|
||||||
|
case 'lookup': {
|
||||||
|
let _path: string;
|
||||||
|
|
||||||
|
if (options.type === 'lookup') {
|
||||||
|
// TODO: v2024.7.0以降が浸透してきたら正式なURLに変更する▼
|
||||||
|
// _path = `/lookup?uri=${encodeURIComponent(_path)}`;
|
||||||
|
_path = `/authorize-follow?acct=${encodeURIComponent(options.url)}`;
|
||||||
|
} else {
|
||||||
|
_path = options.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetHost) {
|
||||||
|
window.open(`https://${targetHost}${_path}`, '_blank', 'noopener');
|
||||||
|
} else {
|
||||||
|
window.open(`https://misskey-hub.net/mi-web/?path=${encodeURIComponent(_path)}`, '_blank', 'noopener');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'share': {
|
||||||
|
const params = query(options.params);
|
||||||
|
if (targetHost) {
|
||||||
|
window.open(`https://${targetHost}/share?${params}`, '_blank', 'noopener');
|
||||||
|
} else {
|
||||||
|
window.open(`https://misskey-hub.net/share/?${params}`, '_blank', 'noopener');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function specifyHostAndOpenRemote(options: OpenOnRemoteOptions): Promise<void> {
|
||||||
|
const { canceled, result: hostTemp } = await os.inputText({
|
||||||
|
title: i18n.ts.inputHostName,
|
||||||
|
placeholder: 'misskey.example.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (canceled) return;
|
||||||
|
|
||||||
|
let targetHost: string | null = hostTemp;
|
||||||
|
|
||||||
|
// ドメイン部分だけを取り出す
|
||||||
|
targetHost = extractDomain(targetHost);
|
||||||
|
if (targetHost == null) {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
title: i18n.ts.invalidValue,
|
||||||
|
text: i18n.ts.tryAgain,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openRemote(options, targetHost);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -234,4 +299,36 @@ function resetPassword(): void {
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.instanceManualSelectButton {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
opacity: .7;
|
||||||
|
font-size: .8em;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.orHr {
|
||||||
|
position: relative;
|
||||||
|
margin: .4em auto;
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--divider);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orMsg {
|
||||||
|
position: absolute;
|
||||||
|
top: -.6em;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 1em;
|
||||||
|
background: var(--panel);
|
||||||
|
font-size: 0.8em;
|
||||||
|
color: var(--fgOnPanel);
|
||||||
|
margin: 0;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -6,21 +6,22 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template>
|
<template>
|
||||||
<MkModalWindow
|
<MkModalWindow
|
||||||
ref="dialog"
|
ref="dialog"
|
||||||
:width="370"
|
:width="400"
|
||||||
:height="400"
|
:height="430"
|
||||||
@close="onClose"
|
@close="onClose"
|
||||||
@closed="emit('closed')"
|
@closed="emit('closed')"
|
||||||
>
|
>
|
||||||
<template #header>{{ i18n.ts.login }}</template>
|
<template #header>{{ i18n.ts.login }}</template>
|
||||||
|
|
||||||
<MkSpacer :marginMin="20" :marginMax="28">
|
<MkSpacer :marginMin="20" :marginMax="28">
|
||||||
<MkSignin :autoSet="autoSet" :message="message" @login="onLogin"/>
|
<MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/>
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
</MkModalWindow>
|
</MkModalWindow>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { shallowRef } from 'vue';
|
import { shallowRef } from 'vue';
|
||||||
|
import type { OpenOnRemoteOptions } from '@/scripts/please-login.js';
|
||||||
import MkSignin from '@/components/MkSignin.vue';
|
import MkSignin from '@/components/MkSignin.vue';
|
||||||
import MkModalWindow from '@/components/MkModalWindow.vue';
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
@ -28,9 +29,11 @@ import { i18n } from '@/i18n.js';
|
||||||
withDefaults(defineProps<{
|
withDefaults(defineProps<{
|
||||||
autoSet?: boolean;
|
autoSet?: boolean;
|
||||||
message?: string,
|
message?: string,
|
||||||
|
openOnRemote?: OpenOnRemoteOptions,
|
||||||
}>(), {
|
}>(), {
|
||||||
autoSet: false,
|
autoSet: false,
|
||||||
message: '',
|
message: '',
|
||||||
|
openOnRemote: undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|
|
@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<p :class="$style.statusItemLabel">{{ i18n.ts.followers }}</p><span :class="$style.statusItemValue">{{ number(user.followersCount) }}</span>
|
<p :class="$style.statusItemLabel">{{ i18n.ts.followers }}</p><span :class="$style.statusItemValue">{{ number(user.followersCount) }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkFollowButton v-if="$i && user.id != $i.id" :class="$style.follow" :user="user" mini/>
|
<MkFollowButton v-if="user.id != $i?.id" :class="$style.follow" :user="user" mini/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
<div class="_gaps_s" :class="$style.mainActions">
|
<div class="_gaps_s" :class="$style.mainActions">
|
||||||
<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
|
<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
|
||||||
<MkButton :class="$style.mainAction" full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton>
|
<MkButton :class="$style.mainAction" full rounded link to="https://misskey-hub.net/servers/">{{ i18n.ts.exploreOtherServers }}</MkButton>
|
||||||
<MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
|
<MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -65,7 +65,8 @@ import { i18n } from '@/i18n.js';
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
import MkNumber from '@/components/MkNumber.vue';
|
import MkNumber from '@/components/MkNumber.vue';
|
||||||
import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';
|
import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';
|
||||||
import { openInstanceMenu } from '@/ui/_common_/common';
|
import { openInstanceMenu } from '@/ui/_common_/common.js';
|
||||||
|
import type { MenuItem } from '@/types/menu.js';
|
||||||
|
|
||||||
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
||||||
|
|
||||||
|
@ -89,13 +90,9 @@ function signup() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu(ev) {
|
function showMenu(ev: MouseEvent) {
|
||||||
openInstanceMenu(ev);
|
openInstanceMenu(ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exploreOtherServers() {
|
|
||||||
window.open('https://misskey-hub.net/servers/', '_blank', 'noopener');
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
|
||||||
const validTime = (t: string | boolean | null | undefined) => {
|
const validTime = (t: string | boolean | null | undefined) => {
|
||||||
if (t == null) return null;
|
if (t == null) return null;
|
||||||
if (typeof t === 'boolean') return null;
|
if (typeof t === 'boolean') return null;
|
||||||
return t.match(/^[0-9.]+s$/) ? t : null;
|
return t.match(/^\-?[0-9.]+s$/) ? t : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validColor = (c: unknown): string | null => {
|
const validColor = (c: unknown): string | null => {
|
||||||
|
|
|
@ -60,7 +60,7 @@ function onChange({ resolved, key: newKey }) {
|
||||||
if (current == null || 'redirect' in current.route) return;
|
if (current == null || 'redirect' in current.route) return;
|
||||||
currentPageComponent.value = current.route.component;
|
currentPageComponent.value = current.route.component;
|
||||||
currentPageProps.value = current.props;
|
currentPageProps.value = current.props;
|
||||||
key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
|
key.value = newKey + JSON.stringify(Object.fromEntries(current.props));
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// ページ遷移完了後に再びキャッシュを有効化
|
// ページ遷移完了後に再びキャッシュを有効化
|
||||||
|
|
|
@ -23,6 +23,7 @@ import MkPopupMenu from '@/components/MkPopupMenu.vue';
|
||||||
import MkContextMenu from '@/components/MkContextMenu.vue';
|
import MkContextMenu from '@/components/MkContextMenu.vue';
|
||||||
import { MenuItem } from '@/types/menu.js';
|
import { MenuItem } from '@/types/menu.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
|
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
|
||||||
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js';
|
||||||
import { focusParent } from '@/scripts/focus.js';
|
import { focusParent } from '@/scripts/focus.js';
|
||||||
|
@ -670,6 +671,15 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function post(props: Record<string, any> = {}): Promise<void> {
|
export function post(props: Record<string, any> = {}): Promise<void> {
|
||||||
|
pleaseLogin(undefined, (props.initialText || props.initialNote ? {
|
||||||
|
type: 'share',
|
||||||
|
params: {
|
||||||
|
text: props.initialText ?? props.initialNote.text,
|
||||||
|
visibility: props.initialVisibility ?? props.initialNote?.visibility,
|
||||||
|
localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0',
|
||||||
|
},
|
||||||
|
} : undefined));
|
||||||
|
|
||||||
showMovedDialog();
|
showMovedDialog();
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
|
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
|
||||||
|
|
|
@ -0,0 +1,205 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
||||||
|
<div style="overflow: clip;">
|
||||||
|
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
||||||
|
<div :class="$style.bannerName">
|
||||||
|
<b>{{ instance.name ?? host }}</b>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.description }}</template>
|
||||||
|
<template #value><div v-html="instance.description"></div></template>
|
||||||
|
</MkKeyValue>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<MkKeyValue :copy="version">
|
||||||
|
<template #key>Misskey</template>
|
||||||
|
<template #value>{{ version }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
|
||||||
|
</div>
|
||||||
|
<FormLink to="/about-misskey">
|
||||||
|
<template #icon><i class="ti ti-info-circle"></i></template>
|
||||||
|
{{ i18n.ts.aboutMisskey }}
|
||||||
|
</FormLink>
|
||||||
|
<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/misskey-${version}.tar.gz`" external>
|
||||||
|
<template #icon><i class="ti ti-code"></i></template>
|
||||||
|
{{ i18n.ts.sourceCode }}
|
||||||
|
</FormLink>
|
||||||
|
<MkInfo v-else warn>
|
||||||
|
{{ i18n.ts.sourceCodeIsNotYetProvided }}
|
||||||
|
</MkInfo>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<div class="_gaps_m">
|
||||||
|
<FormSplit>
|
||||||
|
<MkKeyValue :copy="instance.maintainerName">
|
||||||
|
<template #key>{{ i18n.ts.administrator }}</template>
|
||||||
|
<template #value>
|
||||||
|
<template v-if="instance.maintainerName">{{ instance.maintainerName }}</template>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :copy="instance.maintainerEmail">
|
||||||
|
<template #key>{{ i18n.ts.contact }}</template>
|
||||||
|
<template #value>
|
||||||
|
<template v-if="instance.maintainerEmail">{{ instance.maintainerEmail }}</template>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.inquiry }}</template>
|
||||||
|
<template #value>
|
||||||
|
<MkLink v-if="instance.inquiryUrl" :url="instance.inquiryUrl" target="_blank">{{ instance.inquiryUrl }}</MkLink>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</FormSplit>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>
|
||||||
|
<template #icon><i class="ti ti-user-shield"></i></template>
|
||||||
|
<template #default>{{ i18n.ts.impressum }}</template>
|
||||||
|
</FormLink>
|
||||||
|
<MkFolder v-if="instance.serverRules.length > 0">
|
||||||
|
<template #icon><i class="ti ti-checkup-list"></i></template>
|
||||||
|
<template #label>{{ i18n.ts.serverRules }}</template>
|
||||||
|
<ol class="_gaps_s" :class="$style.rules">
|
||||||
|
<li v-for="item in instance.serverRules" :key="item" :class="$style.rule">
|
||||||
|
<div :class="$style.ruleText" v-html="item"></div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</MkFolder>
|
||||||
|
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>
|
||||||
|
<template #icon><i class="ti ti-license"></i></template>
|
||||||
|
<template #default>{{ i18n.ts.termsOfService }}</template>
|
||||||
|
</FormLink>
|
||||||
|
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>
|
||||||
|
<template #icon><i class="ti ti-shield-lock"></i></template>
|
||||||
|
<template #default>{{ i18n.ts.privacyPolicy }}</template>
|
||||||
|
</FormLink>
|
||||||
|
<FormLink v-if="instance.feedbackUrl" :to="instance.feedbackUrl" external>
|
||||||
|
<template #icon><i class="ti ti-message"></i></template>
|
||||||
|
<template #default>{{ i18n.ts.feedback }}</template>
|
||||||
|
</FormLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
|
||||||
|
<FormSuspense v-slot="{ result: stats }" :p="initStats">
|
||||||
|
<FormSection>
|
||||||
|
<template #label>{{ i18n.ts.statistics }}</template>
|
||||||
|
<FormSplit>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.users }}</template>
|
||||||
|
<template #value>{{ number(stats.originalUsersCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.notes }}</template>
|
||||||
|
<template #value>{{ number(stats.originalNotesCount) }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</FormSplit>
|
||||||
|
</FormSection>
|
||||||
|
</FormSuspense>
|
||||||
|
|
||||||
|
<FormSection>
|
||||||
|
<template #label>Well-known resources</template>
|
||||||
|
<div class="_gaps_s">
|
||||||
|
<FormLink to="/.well-known/host-meta" external>host-meta</FormLink>
|
||||||
|
<FormLink to="/.well-known/host-meta.json" external>host-meta.json</FormLink>
|
||||||
|
<FormLink to="/.well-known/nodeinfo" external>nodeinfo</FormLink>
|
||||||
|
<FormLink to="/robots.txt" external>robots.txt</FormLink>
|
||||||
|
<FormLink to="/manifest.json" external>manifest.json</FormLink>
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { host, version } from '@/config.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { instance } from '@/instance.js';
|
||||||
|
import number from '@/filters/number.js';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import FormLink from '@/components/form/link.vue';
|
||||||
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
import FormSplit from '@/components/form/split.vue';
|
||||||
|
import FormSuspense from '@/components/form/suspense.vue';
|
||||||
|
import MkFolder from '@/components/MkFolder.vue';
|
||||||
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
|
||||||
|
const initStats = () => misskeyApi('stats', {});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.banner {
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: clip;
|
||||||
|
background-color: var(--panel);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bannerIcon {
|
||||||
|
display: block;
|
||||||
|
margin: 16px auto 0 auto;
|
||||||
|
height: 64px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bannerName {
|
||||||
|
display: block;
|
||||||
|
padding: 16px;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 8px #000;
|
||||||
|
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
||||||
|
}
|
||||||
|
|
||||||
|
.rules {
|
||||||
|
counter-reset: item;
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rule {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
position: sticky;
|
||||||
|
top: calc(var(--stickyTop, 0px) + 8px);
|
||||||
|
counter-increment: item;
|
||||||
|
content: counter(item);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
line-height: 32px;
|
||||||
|
background-color: var(--accentedBg);
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: bold;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ruleText {
|
||||||
|
padding-top: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -8,113 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
|
||||||
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
|
||||||
<div class="_gaps_m">
|
<XOverview/>
|
||||||
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
|
|
||||||
<div style="overflow: clip;">
|
|
||||||
<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
|
|
||||||
<div :class="$style.bannerName">
|
|
||||||
<b>{{ instance.name ?? host }}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.description }}</template>
|
|
||||||
<template #value><div v-html="instance.description"></div></template>
|
|
||||||
</MkKeyValue>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<MkKeyValue :copy="version">
|
|
||||||
<template #key>Misskey</template>
|
|
||||||
<template #value>{{ version }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
|
|
||||||
</div>
|
|
||||||
<FormLink to="/about-misskey">
|
|
||||||
<template #icon><i class="ti ti-info-circle"></i></template>
|
|
||||||
{{ i18n.ts.aboutMisskey }}
|
|
||||||
</FormLink>
|
|
||||||
<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/misskey-${version}.tar.gz`" external>
|
|
||||||
<template #icon><i class="ti ti-code"></i></template>
|
|
||||||
{{ i18n.ts.sourceCode }}
|
|
||||||
</FormLink>
|
|
||||||
<MkInfo v-else warn>
|
|
||||||
{{ i18n.ts.sourceCodeIsNotYetProvided }}
|
|
||||||
</MkInfo>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<div class="_gaps_m">
|
|
||||||
<FormSplit>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.administrator }}</template>
|
|
||||||
<template #value>{{ instance.maintainerName }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.contact }}</template>
|
|
||||||
<template #value>{{ instance.maintainerEmail }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</FormSplit>
|
|
||||||
<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>
|
|
||||||
<template #icon><i class="ti ti-user-shield"></i></template>
|
|
||||||
{{ i18n.ts.impressum }}
|
|
||||||
</FormLink>
|
|
||||||
<div class="_gaps_s">
|
|
||||||
<MkFolder v-if="instance.serverRules.length > 0">
|
|
||||||
<template #label>
|
|
||||||
<i class="ti ti-checkup-list"></i>
|
|
||||||
{{ i18n.ts.serverRules }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<ol class="_gaps_s" :class="$style.rules">
|
|
||||||
<li v-for="(item, index) in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
|
|
||||||
</ol>
|
|
||||||
</MkFolder>
|
|
||||||
<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>
|
|
||||||
<template #icon><i class="ti ti-license"></i></template>
|
|
||||||
{{ i18n.ts.termsOfService }}
|
|
||||||
</FormLink>
|
|
||||||
<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>
|
|
||||||
<template #icon><i class="ti ti-shield-lock"></i></template>
|
|
||||||
{{ i18n.ts.privacyPolicy }}
|
|
||||||
</FormLink>
|
|
||||||
<FormLink v-if="instance.feedbackUrl" :to="instance.feedbackUrl" external>
|
|
||||||
<template #icon><i class="ti ti-message"></i></template>
|
|
||||||
{{ i18n.ts.feedback }}
|
|
||||||
</FormLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
|
|
||||||
<FormSuspense :p="initStats">
|
|
||||||
<FormSection>
|
|
||||||
<template #label>{{ i18n.ts.statistics }}</template>
|
|
||||||
<FormSplit>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.users }}</template>
|
|
||||||
<template #value>{{ number(stats.originalUsersCount) }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
<MkKeyValue>
|
|
||||||
<template #key>{{ i18n.ts.notes }}</template>
|
|
||||||
<template #value>{{ number(stats.originalNotesCount) }}</template>
|
|
||||||
</MkKeyValue>
|
|
||||||
</FormSplit>
|
|
||||||
</FormSection>
|
|
||||||
</FormSuspense>
|
|
||||||
|
|
||||||
<FormSection>
|
|
||||||
<template #label>Well-known resources</template>
|
|
||||||
<div class="_gaps_s">
|
|
||||||
<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
|
|
||||||
<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
|
|
||||||
<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
|
|
||||||
<FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
|
|
||||||
<FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
|
|
||||||
</div>
|
|
||||||
</FormSection>
|
|
||||||
</div>
|
|
||||||
</MkSpacer>
|
</MkSpacer>
|
||||||
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
|
||||||
<XEmojis/>
|
<XEmojis/>
|
||||||
|
@ -130,26 +24,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, watch, ref } from 'vue';
|
import { computed, defineAsyncComponent, ref, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import XEmojis from './about.emojis.vue';
|
|
||||||
import XFederation from './about.federation.vue';
|
|
||||||
import { version, host } from '@/config.js';
|
|
||||||
import FormLink from '@/components/form/link.vue';
|
|
||||||
import FormSection from '@/components/form/section.vue';
|
|
||||||
import FormSuspense from '@/components/form/suspense.vue';
|
|
||||||
import FormSplit from '@/components/form/split.vue';
|
|
||||||
import MkFolder from '@/components/MkFolder.vue';
|
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
|
||||||
import MkInfo from '@/components/MkInfo.vue';
|
|
||||||
import MkInstanceStats from '@/components/MkInstanceStats.vue';
|
|
||||||
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import number from '@/filters/number.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|
||||||
import { claimAchievement } from '@/scripts/achievements.js';
|
import { claimAchievement } from '@/scripts/achievements.js';
|
||||||
import { instance } from '@/instance.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
|
||||||
|
|
||||||
|
const XOverview = defineAsyncComponent(() => import('@/pages/about.overview.vue'));
|
||||||
|
const XEmojis = defineAsyncComponent(() => import('@/pages/about.emojis.vue'));
|
||||||
|
const XFederation = defineAsyncComponent(() => import('@/pages/about.federation.vue'));
|
||||||
|
const MkInstanceStats = defineAsyncComponent(() => import('@/components/MkInstanceStats.vue'));
|
||||||
|
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
initialTab?: string;
|
initialTab?: string;
|
||||||
|
@ -157,7 +41,6 @@ const props = withDefaults(defineProps<{
|
||||||
initialTab: 'overview',
|
initialTab: 'overview',
|
||||||
});
|
});
|
||||||
|
|
||||||
const stats = ref<Misskey.entities.StatsResponse | null>(null);
|
|
||||||
const tab = ref(props.initialTab);
|
const tab = ref(props.initialTab);
|
||||||
|
|
||||||
watch(tab, () => {
|
watch(tab, () => {
|
||||||
|
@ -166,11 +49,6 @@ watch(tab, () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const initStats = () => misskeyApi('stats', {
|
|
||||||
}).then((res) => {
|
|
||||||
stats.value = res;
|
|
||||||
});
|
|
||||||
|
|
||||||
const headerActions = computed(() => []);
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
const headerTabs = computed(() => [{
|
const headerTabs = computed(() => [{
|
||||||
|
@ -195,64 +73,3 @@ definePageMetadata(() => ({
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
}));
|
}));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.banner {
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 10px;
|
|
||||||
overflow: clip;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerIcon {
|
|
||||||
display: block;
|
|
||||||
margin: 16px auto 0 auto;
|
|
||||||
height: 64px;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerName {
|
|
||||||
display: block;
|
|
||||||
padding: 16px;
|
|
||||||
color: #fff;
|
|
||||||
text-shadow: 0 0 8px #000;
|
|
||||||
background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
|
|
||||||
}
|
|
||||||
|
|
||||||
.rules {
|
|
||||||
counter-reset: item;
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rule {
|
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
word-break: break-word;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
|
||||||
position: sticky;
|
|
||||||
top: calc(var(--stickyTop, 0px) + 8px);
|
|
||||||
counter-increment: item;
|
|
||||||
content: counter(item);
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
line-height: 32px;
|
|
||||||
background-color: var(--accentedBg);
|
|
||||||
color: var(--accent);
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: bold;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 999px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ruleText {
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -7,18 +7,26 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkStickyContainer>
|
<MkStickyContainer>
|
||||||
<template #header><MkPageHeader/></template>
|
<template #header><MkPageHeader/></template>
|
||||||
<MkSpacer :contentMax="600" :marginMin="20">
|
<MkSpacer :contentMax="600" :marginMin="20">
|
||||||
<div class="_gaps">
|
<div class="_gaps_m">
|
||||||
<MkKeyValue>
|
<MkKeyValue :copy="instance.maintainerName">
|
||||||
<template #key>{{ i18n.ts.inquiry }}</template>
|
<template #key>{{ i18n.ts.administrator }}</template>
|
||||||
<template #value>
|
<template #value>
|
||||||
<MkLink :url="instance.inquiryUrl" target="_blank">{{ instance.inquiryUrl }}</MkLink>
|
<template v-if="instance.maintainerName">{{ instance.maintainerName }}</template>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :copy="instance.maintainerEmail">
|
||||||
<MkKeyValue>
|
<template #key>{{ i18n.ts.contact }}</template>
|
||||||
<template #key>{{ i18n.ts.email }}</template>
|
|
||||||
<template #value>
|
<template #value>
|
||||||
<div>{{ instance.maintainerEmail }}</div>
|
<template v-if="instance.maintainerEmail">{{ instance.maintainerEmail }}</template>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :copy="instance.inquiryUrl">
|
||||||
|
<template #key>{{ i18n.ts.inquiry }}</template>
|
||||||
|
<template #value>
|
||||||
|
<MkLink v-if="instance.inquiryUrl" :url="instance.inquiryUrl" target="_blank">{{ instance.inquiryUrl }}</MkLink>
|
||||||
|
<span v-else style="opacity: 0.7;">({{ i18n.ts.none }})</span>
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,8 +36,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|
||||||
import { instance } from '@/instance.js';
|
import { instance } from '@/instance.js';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
import MkLink from '@/components/MkLink.vue';
|
import MkLink from '@/components/MkLink.vue';
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,7 @@ import { defaultStore } from '@/store.js';
|
||||||
import { $i } from '@/account.js';
|
import { $i } from '@/account.js';
|
||||||
import { isSupportShare } from '@/scripts/navigator.js';
|
import { isSupportShare } from '@/scripts/navigator.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
|
import { pleaseLogin } from '@/scripts/please-login.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -143,6 +144,7 @@ function shareWithNote() {
|
||||||
|
|
||||||
function like() {
|
function like() {
|
||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
|
pleaseLogin();
|
||||||
|
|
||||||
os.apiWithDialog('flash/like', {
|
os.apiWithDialog('flash/like', {
|
||||||
flashId: flash.value.id,
|
flashId: flash.value.id,
|
||||||
|
@ -154,6 +156,7 @@ function like() {
|
||||||
|
|
||||||
async function unlike() {
|
async function unlike() {
|
||||||
if (!flash.value) return;
|
if (!flash.value) return;
|
||||||
|
pleaseLogin();
|
||||||
|
|
||||||
const confirm = await os.confirm({
|
const confirm = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { } from 'vue';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import * as os from '@/os.js';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import { mainRouter } from '@/router/main.js';
|
|
||||||
|
|
||||||
async function follow(user): Promise<void> {
|
|
||||||
const { canceled } = await os.confirm({
|
|
||||||
type: 'question',
|
|
||||||
text: i18n.tsx.followConfirm({ name: user.name || user.username }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (canceled) {
|
|
||||||
window.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
os.apiWithDialog('following/create', {
|
|
||||||
userId: user.id,
|
|
||||||
withReplies: defaultStore.state.defaultWithReplies,
|
|
||||||
});
|
|
||||||
user.withReplies = defaultStore.state.defaultWithReplies;
|
|
||||||
}
|
|
||||||
|
|
||||||
const acct = new URL(location.href).searchParams.get('acct');
|
|
||||||
if (acct == null) {
|
|
||||||
throw new Error('acct required');
|
|
||||||
}
|
|
||||||
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (acct.startsWith('https://')) {
|
|
||||||
promise = misskeyApi('ap/show', {
|
|
||||||
uri: acct,
|
|
||||||
});
|
|
||||||
promise.then(res => {
|
|
||||||
if (res.type === 'User') {
|
|
||||||
follow(res.object);
|
|
||||||
} else if (res.type === 'Note') {
|
|
||||||
mainRouter.push(`/notes/${res.object.id}`);
|
|
||||||
} else {
|
|
||||||
os.alert({
|
|
||||||
type: 'error',
|
|
||||||
text: 'Not a user',
|
|
||||||
}).then(() => {
|
|
||||||
window.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
promise = misskeyApi('users/show', Misskey.acct.parse(acct));
|
|
||||||
promise.then(user => {
|
|
||||||
follow(user);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
|
||||||
</script>
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkStickyContainer>
|
||||||
|
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
|
<MkSpacer :contentMax="800">
|
||||||
|
<div v-if="state === 'done'" class="_buttonsCenter">
|
||||||
|
<MkButton @click="close">{{ i18n.ts.close }}</MkButton>
|
||||||
|
<MkButton @click="goToMisskey">{{ i18n.ts.goToMisskey }}</MkButton>
|
||||||
|
</div>
|
||||||
|
<div v-else class="_fullInfo">
|
||||||
|
<MkLoading/>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</MkStickyContainer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { computed, ref } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import * as os from '@/os.js';
|
||||||
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
|
import { mainRouter } from '@/router/main.js';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
|
||||||
|
const state = ref<'fetching' | 'done'>('fetching');
|
||||||
|
|
||||||
|
function fetch() {
|
||||||
|
const params = new URL(location.href).searchParams;
|
||||||
|
|
||||||
|
// acctのほうはdeprecated
|
||||||
|
let uri = params.get('uri') ?? params.get('acct');
|
||||||
|
if (uri == null) {
|
||||||
|
state.value = 'done';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise: Promise<any>;
|
||||||
|
|
||||||
|
if (uri.startsWith('https://')) {
|
||||||
|
promise = misskeyApi('ap/show', {
|
||||||
|
uri,
|
||||||
|
});
|
||||||
|
promise.then(res => {
|
||||||
|
if (res.type === 'User') {
|
||||||
|
mainRouter.replace(res.object.host ? `/@${res.object.username}@${res.object.host}` : `/@${res.object.username}`);
|
||||||
|
} else if (res.type === 'Note') {
|
||||||
|
mainRouter.replace(`/notes/${res.object.id}`);
|
||||||
|
} else {
|
||||||
|
os.alert({
|
||||||
|
type: 'error',
|
||||||
|
text: 'Not a user',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (uri.startsWith('acct:')) {
|
||||||
|
uri = uri.slice(5);
|
||||||
|
}
|
||||||
|
promise = misskeyApi('users/show', Misskey.acct.parse(uri));
|
||||||
|
promise.then(user => {
|
||||||
|
mainRouter.replace(user.host ? `/@${user.username}@${user.host}` : `/@${user.username}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
window.close();
|
||||||
|
|
||||||
|
// 閉じなければ100ms後タイムラインに
|
||||||
|
window.setTimeout(() => {
|
||||||
|
location.href = '/';
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToMisskey(): void {
|
||||||
|
location.href = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch();
|
||||||
|
|
||||||
|
const headerActions = computed(() => []);
|
||||||
|
|
||||||
|
const headerTabs = computed(() => []);
|
||||||
|
|
||||||
|
definePageMetadata({
|
||||||
|
title: i18n.ts.lookup,
|
||||||
|
icon: 'ti ti-world-search',
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -32,9 +32,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
|
||||||
<div v-if="$i" class="actions">
|
<div class="actions">
|
||||||
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
<button class="menu _button" @click="menu"><i class="ti ti-dots"></i></button>
|
||||||
<MkFollowButton v-if="$i.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
<MkFollowButton v-if="$i?.id != user.id" v-model:user="user" :inline="true" :transparent="false" :full="true" class="koudoku"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<MkAvatar class="avatar" :user="user" indicator/>
|
<MkAvatar class="avatar" :user="user" indicator/>
|
||||||
|
|
|
@ -237,8 +237,18 @@ const routes: RouteDef[] = [{
|
||||||
origin: 'origin',
|
origin: 'origin',
|
||||||
},
|
},
|
||||||
}, {
|
}, {
|
||||||
|
// Legacy Compatibility
|
||||||
path: '/authorize-follow',
|
path: '/authorize-follow',
|
||||||
component: page(() => import('@/pages/follow.vue')),
|
redirect: '/lookup',
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
// Mastodon Compatibility
|
||||||
|
path: '/authorize_interaction',
|
||||||
|
redirect: '/lookup',
|
||||||
|
loginRequired: true,
|
||||||
|
}, {
|
||||||
|
path: '/lookup',
|
||||||
|
component: page(() => import('@/pages/lookup.vue')),
|
||||||
loginRequired: true,
|
loginRequired: true,
|
||||||
}, {
|
}, {
|
||||||
path: '/share',
|
path: '/share',
|
||||||
|
|
|
@ -186,7 +186,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||||
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
|
const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
|
||||||
copyToClipboard(`${url}/${canonical}`);
|
copyToClipboard(`${url}/${canonical}`);
|
||||||
},
|
},
|
||||||
}, {
|
}, ...($i ? [{
|
||||||
icon: 'ti ti-mail',
|
icon: 'ti ti-mail',
|
||||||
text: i18n.ts.sendMessage,
|
text: i18n.ts.sendMessage,
|
||||||
action: () => {
|
action: () => {
|
||||||
|
@ -259,7 +259,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
}] as any;
|
}] : [])] as any;
|
||||||
|
|
||||||
if ($i && meId !== user.id) {
|
if ($i && meId !== user.id) {
|
||||||
if (iAmModerator) {
|
if (iAmModerator) {
|
||||||
|
|
|
@ -8,12 +8,49 @@ import { $i } from '@/account.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { popup } from '@/os.js';
|
import { popup } from '@/os.js';
|
||||||
|
|
||||||
export function pleaseLogin(path?: string) {
|
export type OpenOnRemoteOptions = {
|
||||||
|
/**
|
||||||
|
* 外部のMisskey Webで特定のパスを開く
|
||||||
|
*/
|
||||||
|
type: 'web';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部パス(例: `/settings`)
|
||||||
|
*/
|
||||||
|
path: string;
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* 外部のMisskey Webで照会する
|
||||||
|
*/
|
||||||
|
type: 'lookup';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 照会したいエンティティのURL
|
||||||
|
*
|
||||||
|
* (例: `https://misskey.example.com/notes/abcdexxxxyz`)
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
} | {
|
||||||
|
/**
|
||||||
|
* 外部のMisskeyでノートする
|
||||||
|
*/
|
||||||
|
type: 'share';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `/share` ページに渡すクエリストリング
|
||||||
|
*
|
||||||
|
* @see https://go.misskey-hub.net/spec/share/
|
||||||
|
*/
|
||||||
|
params: Record<string, string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function pleaseLogin(path?: string, openOnRemote?: OpenOnRemoteOptions) {
|
||||||
if ($i) return;
|
if ($i) return;
|
||||||
|
|
||||||
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
|
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {
|
||||||
autoSet: true,
|
autoSet: true,
|
||||||
message: i18n.ts.signinRequired,
|
message: openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired,
|
||||||
|
openOnRemote,
|
||||||
}, {
|
}, {
|
||||||
cancelled: () => {
|
cancelled: () => {
|
||||||
if (path) {
|
if (path) {
|
||||||
|
|
|
@ -21,3 +21,8 @@ export function query(obj: Record<string, any>): string {
|
||||||
export function appendQuery(url: string, query: string): string {
|
export function appendQuery(url: string, query: string): string {
|
||||||
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
return `${url}${/\?/.test(url) ? url.endsWith('?') ? '' : '&' : '?'}${query}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function extractDomain(url: string) {
|
||||||
|
const match = url.match(/^(?:https?:)?(?:\/\/)?(?:[^@\n]+@)?([^:\/\n]+)/im);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
|
@ -85,29 +85,29 @@ export function openInstanceMenu(ev: MouseEvent) {
|
||||||
icon: 'ti ti-help-circle',
|
icon: 'ti ti-help-circle',
|
||||||
to: '/contact',
|
to: '/contact',
|
||||||
}, (instance.impressumUrl) ? {
|
}, (instance.impressumUrl) ? {
|
||||||
|
type: 'a',
|
||||||
text: i18n.ts.impressum,
|
text: i18n.ts.impressum,
|
||||||
icon: 'ti ti-file-invoice',
|
icon: 'ti ti-file-invoice',
|
||||||
action: () => {
|
href: instance.impressumUrl,
|
||||||
window.open(instance.impressumUrl, '_blank', 'noopener');
|
target: '_blank',
|
||||||
},
|
|
||||||
} : undefined, (instance.tosUrl) ? {
|
} : undefined, (instance.tosUrl) ? {
|
||||||
|
type: 'a',
|
||||||
text: i18n.ts.termsOfService,
|
text: i18n.ts.termsOfService,
|
||||||
icon: 'ti ti-notebook',
|
icon: 'ti ti-notebook',
|
||||||
action: () => {
|
href: instance.tosUrl,
|
||||||
window.open(instance.tosUrl, '_blank', 'noopener');
|
target: '_blank',
|
||||||
},
|
|
||||||
} : undefined, (instance.privacyPolicyUrl) ? {
|
} : undefined, (instance.privacyPolicyUrl) ? {
|
||||||
|
type: 'a',
|
||||||
text: i18n.ts.privacyPolicy,
|
text: i18n.ts.privacyPolicy,
|
||||||
icon: 'ti ti-shield-lock',
|
icon: 'ti ti-shield-lock',
|
||||||
action: () => {
|
href: instance.privacyPolicyUrl,
|
||||||
window.open(instance.privacyPolicyUrl, '_blank', 'noopener');
|
target: '_blank',
|
||||||
},
|
|
||||||
} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
|
} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
|
||||||
|
type: 'a',
|
||||||
text: i18n.ts.document,
|
text: i18n.ts.document,
|
||||||
icon: 'ti ti-bulb',
|
icon: 'ti ti-bulb',
|
||||||
action: () => {
|
href: 'https://misskey-hub.net/docs/for-users/',
|
||||||
window.open('https://misskey-hub.net/docs/for-users/', '_blank', 'noopener');
|
target: '_blank',
|
||||||
},
|
|
||||||
}, ($i) ? {
|
}, ($i) ? {
|
||||||
text: i18n.ts._initialTutorial.launchTutorial,
|
text: i18n.ts._initialTutorial.launchTutorial,
|
||||||
icon: 'ti ti-presentation',
|
icon: 'ti ti-presentation',
|
||||||
|
|
|
@ -95,7 +95,6 @@ async function watchSrc() {
|
||||||
process.on('SIGHUP', resolve);
|
process.on('SIGHUP', resolve);
|
||||||
process.on('SIGINT', resolve);
|
process.on('SIGINT', resolve);
|
||||||
process.on('SIGTERM', resolve);
|
process.on('SIGTERM', resolve);
|
||||||
process.on('SIGKILL', resolve);
|
|
||||||
process.on('uncaughtException', reject);
|
process.on('uncaughtException', reject);
|
||||||
process.on('exit', resolve);
|
process.on('exit', resolve);
|
||||||
}).finally(async () => {
|
}).finally(async () => {
|
||||||
|
|
|
@ -95,7 +95,6 @@ async function watchSrc() {
|
||||||
process.on('SIGHUP', resolve);
|
process.on('SIGHUP', resolve);
|
||||||
process.on('SIGINT', resolve);
|
process.on('SIGINT', resolve);
|
||||||
process.on('SIGTERM', resolve);
|
process.on('SIGTERM', resolve);
|
||||||
process.on('SIGKILL', resolve);
|
|
||||||
process.on('uncaughtException', reject);
|
process.on('uncaughtException', reject);
|
||||||
process.on('exit', resolve);
|
process.on('exit', resolve);
|
||||||
}).finally(async () => {
|
}).finally(async () => {
|
||||||
|
|
|
@ -291,7 +291,9 @@ export abstract class Connection<Channel extends AnyOf<Channels> = any> extends
|
||||||
|
|
||||||
this.stream = stream;
|
this.stream = stream;
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.name = name;
|
if (name !== undefined) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void {
|
public send<T extends keyof Channel['receives']>(type: T, body: Channel['receives'][T]): void {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./node_modules/@types"
|
"./node_modules/@types"
|
||||||
],
|
],
|
||||||
|
|
|
@ -95,7 +95,6 @@ async function watchSrc() {
|
||||||
process.on('SIGHUP', resolve);
|
process.on('SIGHUP', resolve);
|
||||||
process.on('SIGINT', resolve);
|
process.on('SIGINT', resolve);
|
||||||
process.on('SIGTERM', resolve);
|
process.on('SIGTERM', resolve);
|
||||||
process.on('SIGKILL', resolve);
|
|
||||||
process.on('uncaughtException', reject);
|
process.on('uncaughtException', reject);
|
||||||
process.on('exit', resolve);
|
process.on('exit', resolve);
|
||||||
}).finally(async () => {
|
}).finally(async () => {
|
||||||
|
|
Loading…
Reference in New Issue