Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
263d476ffe | ||
|
|
3ab64ac688 |
@@ -5,7 +5,7 @@
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "24.10.0"
|
||||
"version": "22.15.0"
|
||||
},
|
||||
"ghcr.io/devcontainers-extra/features/pnpm:2": {
|
||||
"version": "10.10.0"
|
||||
|
||||
@@ -11,7 +11,6 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Reply
|
||||
uses: actions/github-script@v6
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
24.10.0
|
||||
22.15.0
|
||||
|
||||
+6
-16
@@ -1,24 +1,14 @@
|
||||
## 2025.11.0
|
||||
## Unreleased
|
||||
|
||||
### General
|
||||
- Enhance: Node.js 24.10.0をサポートするようになりました
|
||||
- Enhance: DockerのNode.jsが24.10.0に更新されました
|
||||
|
||||
### Client
|
||||
- Enhance: 管理しているチャンネルの見分けがつきやすくなるように
|
||||
- Enhance: プッシュ通知を行うための権限確認をより確実に行うように
|
||||
- Fix: 紙吹雪エフェクトがアニメーション設定を考慮せず常に表示される問題を修正
|
||||
- Fix: ナビゲーションバーのリアルタイムモード切替ボタンの状態をよりわかりやすく表示するように
|
||||
- Fix: ページのタイトルが長いとき、はみ出る問題を修正
|
||||
|
||||
### Server
|
||||
- Enhance: Remote Notes Cleaningが複雑度が高いノートの処理を中断せずに次のノートから再開するように
|
||||
|
||||
## 2025.10.2
|
||||
-
|
||||
|
||||
### Client
|
||||
- Fix: アプリ内からキャッシュをクリアするとテーマ再適用するまでレンダリングが正しく行われない問題を修正
|
||||
- Fix: 期限が無期限のアンケートに投票できない問題を修正
|
||||
|
||||
### Server
|
||||
-
|
||||
|
||||
|
||||
## 2025.10.1
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=24.10.0-bookworm
|
||||
ARG NODE_VERSION=22.15.0-bookworm
|
||||
|
||||
# build assets & compile TypeScript
|
||||
|
||||
|
||||
Vendored
+1
-37
@@ -747,7 +747,7 @@ export interface Locale extends ILocale {
|
||||
*/
|
||||
"flagShowTimelineRepliesDescription": string;
|
||||
/**
|
||||
* フォロー中ユーザーからのフォロー申請を自動承認
|
||||
* フォロー中ユーザーからのフォロリクを自動承認
|
||||
*/
|
||||
"autoAcceptFollowed": string;
|
||||
/**
|
||||
@@ -1226,10 +1226,6 @@ export interface Locale extends ILocale {
|
||||
* 既読
|
||||
*/
|
||||
"messageRead": string;
|
||||
/**
|
||||
* すべてのメッセージを既読にする
|
||||
*/
|
||||
"readAllChatMessages": string;
|
||||
/**
|
||||
* これより過去の履歴はありません
|
||||
*/
|
||||
@@ -4110,18 +4106,6 @@ export interface Locale extends ILocale {
|
||||
* 端末の電池消費量が増加する可能性があります。
|
||||
*/
|
||||
"sendPushNotificationReadMessageCaption": string;
|
||||
/**
|
||||
* ブラウザの通知設定を許可してください
|
||||
*/
|
||||
"pleaseAllowPushNotification": string;
|
||||
/**
|
||||
* 通知の送信権限の取得に失敗しました
|
||||
*/
|
||||
"browserPushNotificationDisabled": string;
|
||||
/**
|
||||
* {serverName}から通知を送信する権限がありません。ブラウザの設定から通知を許可して再度お試しください。
|
||||
*/
|
||||
"browserPushNotificationDisabledDescription": ParameterizedString<"serverName">;
|
||||
/**
|
||||
* 最大化
|
||||
*/
|
||||
@@ -5621,10 +5605,6 @@ export interface Locale extends ILocale {
|
||||
* 技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。
|
||||
*/
|
||||
"deviceInfoDescription": string;
|
||||
/**
|
||||
* あなたは管理者です
|
||||
*/
|
||||
"youAreAdmin": string;
|
||||
"_compression": {
|
||||
"_quality": {
|
||||
/**
|
||||
@@ -6017,10 +5997,6 @@ export interface Locale extends ILocale {
|
||||
* ページのタブバーを下部に表示
|
||||
*/
|
||||
"showPageTabBarBottom": string;
|
||||
/**
|
||||
* 絵文字ピッカーに固定表示するプリセットをパレットとして登録したり、ピッカーの表示方法をカスタマイズしたりできます。
|
||||
*/
|
||||
"emojiPaletteBanner": string;
|
||||
"_chat": {
|
||||
/**
|
||||
* 送信者の名前を表示
|
||||
@@ -6083,10 +6059,6 @@ export interface Locale extends ILocale {
|
||||
* 設定のバックアップが見つかりました
|
||||
*/
|
||||
"backupFound": string;
|
||||
/**
|
||||
* 設定の強制バックアップ
|
||||
*/
|
||||
"forceBackup": string;
|
||||
};
|
||||
"_accountSettings": {
|
||||
/**
|
||||
@@ -10918,14 +10890,6 @@ export interface Locale extends ILocale {
|
||||
};
|
||||
};
|
||||
"_moderationLogTypes": {
|
||||
/**
|
||||
* ジョブキューをクリア
|
||||
*/
|
||||
"clearQueue": string;
|
||||
/**
|
||||
* キューのジョブを再試行
|
||||
*/
|
||||
"promoteQueue": string;
|
||||
/**
|
||||
* ロールを作成
|
||||
*/
|
||||
|
||||
+1
-10
@@ -182,7 +182,7 @@ flagAsCat: "にゃああああああああああああああ!!!!!!
|
||||
flagAsCatDescription: "にゃにゃにゃ??"
|
||||
flagShowTimelineReplies: "タイムラインにノートへの返信を表示する"
|
||||
flagShowTimelineRepliesDescription: "オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。"
|
||||
autoAcceptFollowed: "フォロー中ユーザーからのフォロー申請を自動承認"
|
||||
autoAcceptFollowed: "フォロー中ユーザーからのフォロリクを自動承認"
|
||||
addAccount: "アカウントを追加"
|
||||
reloadAccountsList: "アカウントリストの情報を更新"
|
||||
loginFailed: "ログインに失敗しました"
|
||||
@@ -302,7 +302,6 @@ uploadFromUrlMayTakeTime: "アップロードが完了するまで時間がか
|
||||
uploadNFiles: "{n}個のファイルをアップロード"
|
||||
explore: "みつける"
|
||||
messageRead: "既読"
|
||||
readAllChatMessages: "すべてのメッセージを既読にする"
|
||||
noMoreHistory: "これより過去の履歴はありません"
|
||||
startChat: "メッセージを送る"
|
||||
nUsersRead: "{n}人が読みました"
|
||||
@@ -1023,9 +1022,6 @@ pushNotificationAlreadySubscribed: "プッシュ通知は有効です"
|
||||
pushNotificationNotSupported: "ブラウザかサーバーがプッシュ通知に非対応"
|
||||
sendPushNotificationReadMessage: "通知が既読になったらプッシュ通知を削除する"
|
||||
sendPushNotificationReadMessageCaption: "端末の電池消費量が増加する可能性があります。"
|
||||
pleaseAllowPushNotification: "ブラウザの通知設定を許可してください"
|
||||
browserPushNotificationDisabled: "通知の送信権限の取得に失敗しました"
|
||||
browserPushNotificationDisabledDescription: "{serverName}から通知を送信する権限がありません。ブラウザの設定から通知を許可して再度お試しください。"
|
||||
windowMaximize: "最大化"
|
||||
windowMinimize: "最小化"
|
||||
windowRestore: "元に戻す"
|
||||
@@ -1400,7 +1396,6 @@ scheduled: "予約"
|
||||
widgets: "ウィジェット"
|
||||
deviceInfo: "デバイス情報"
|
||||
deviceInfoDescription: "技術的なお問い合わせの際に、以下の情報を併記すると問題の解決に役立つことがあります。"
|
||||
youAreAdmin: "あなたは管理者です"
|
||||
|
||||
_compression:
|
||||
_quality:
|
||||
@@ -1508,7 +1503,6 @@ _settings:
|
||||
showUrlPreview: "URLプレビューを表示する"
|
||||
showAvailableReactionsFirstInNote: "利用できるリアクションを先頭に表示"
|
||||
showPageTabBarBottom: "ページのタブバーを下部に表示"
|
||||
emojiPaletteBanner: "絵文字ピッカーに固定表示するプリセットをパレットとして登録したり、ピッカーの表示方法をカスタマイズしたりできます。"
|
||||
|
||||
_chat:
|
||||
showSenderName: "送信者の名前を表示"
|
||||
@@ -1529,7 +1523,6 @@ _preferencesBackup:
|
||||
youNeedToNameYourProfileToEnableAutoBackup: "自動バックアップを有効にするにはプロファイル名の設定が必要です。"
|
||||
autoPreferencesBackupIsNotEnabledForThisDevice: "このデバイスで設定の自動バックアップは有効になっていません。"
|
||||
backupFound: "設定のバックアップが見つかりました"
|
||||
forceBackup: "設定の強制バックアップ"
|
||||
|
||||
_accountSettings:
|
||||
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
|
||||
@@ -2893,8 +2886,6 @@ _abuseReport:
|
||||
deleteConfirm: "通知先を削除しますか?"
|
||||
|
||||
_moderationLogTypes:
|
||||
clearQueue: "ジョブキューをクリア"
|
||||
promoteQueue: "キューのジョブを再試行"
|
||||
createRole: "ロールを作成"
|
||||
deleteRole: "ロールを削除"
|
||||
updateRole: "ロールを更新"
|
||||
|
||||
+3
-6
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"version": "2025.11.0-alpha.0",
|
||||
"version": "2025.10.1",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -68,7 +68,7 @@
|
||||
"devDependencies": {
|
||||
"@misskey-dev/eslint-plugin": "2.1.0",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"cross-env": "10.1.0",
|
||||
@@ -85,9 +85,6 @@
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@aiscript-dev/aiscript-languageserver": "-"
|
||||
},
|
||||
"ignoredBuiltDependencies": [
|
||||
"@sentry-internal/node-cpu-profiler"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ const __dirname = path.dirname(__filename);
|
||||
|
||||
const args = [];
|
||||
args.push(...[
|
||||
...semver.satisfies(process.version, '^20.17.0 || ^22.0.0 || ^24.10.0') ? ['--no-experimental-require-module'] : [],
|
||||
...semver.satisfies(process.version, '^20.17.0 || ^22.0.0') ? ['--no-experimental-require-module'] : [],
|
||||
'--experimental-vm-modules',
|
||||
'--experimental-import-meta-resolve',
|
||||
path.join(__dirname, 'node_modules/jest/bin/jest.js'),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": "^22.15.0 || ^24.10.0"
|
||||
"node": "^22.15.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ./built/boot/entry.js",
|
||||
@@ -87,8 +87,8 @@
|
||||
"@nestjs/core": "11.1.6",
|
||||
"@nestjs/testing": "11.1.6",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "10.20.0",
|
||||
"@sentry/profiling-node": "10.20.0",
|
||||
"@sentry/node": "8.55.0",
|
||||
"@sentry/profiling-node": "8.55.0",
|
||||
"@simplewebauthn/server": "12.0.0",
|
||||
"@sinonjs/fake-timers": "11.3.1",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
@@ -117,7 +117,7 @@
|
||||
"fastify": "5.6.1",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "21.0.0",
|
||||
"file-type": "19.6.0",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.4",
|
||||
"got": "14.5.0",
|
||||
@@ -191,7 +191,7 @@
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@nestjs/platform-express": "10.4.20",
|
||||
"@sentry/vue": "10.20.0",
|
||||
"@sentry/vue": "9.46.0",
|
||||
"@simplewebauthn/types": "12.0.0",
|
||||
"@swc/jest": "0.2.39",
|
||||
"@types/accepts": "1.3.7",
|
||||
@@ -210,7 +210,7 @@
|
||||
"@types/jsrsasign": "10.5.15",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/nodemailer": "6.4.20",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
|
||||
@@ -20,8 +20,8 @@ import { AiService } from '@/core/AiService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
import type { PredictionType } from 'nsfwjs';
|
||||
import { isMimeImage } from '@/misc/is-mime-image.js';
|
||||
|
||||
export type FileInfo = {
|
||||
size: number;
|
||||
@@ -339,7 +339,7 @@ export class FileInfoService {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public fixMime(mime: string): string {
|
||||
public fixMime(mime: string | fileType.MimeType): string {
|
||||
// see https://github.com/misskey-dev/misskey/pull/10686
|
||||
if (mime === 'audio/x-flac') {
|
||||
return 'audio/flac';
|
||||
|
||||
@@ -51,17 +51,6 @@ export class CleanRemoteNotesProcessorService {
|
||||
skipped: boolean;
|
||||
transientErrors: number;
|
||||
}> {
|
||||
const getConfig = () => {
|
||||
return {
|
||||
enabled: this.meta.enableRemoteNotesCleaning,
|
||||
maxDuration: this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000, // Convert minutes to milliseconds
|
||||
// The date limit for the newest note to be considered for deletion.
|
||||
// All notes newer than this limit will always be retained.
|
||||
newestLimit: this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes)),
|
||||
};
|
||||
};
|
||||
|
||||
const initialConfig = getConfig();
|
||||
if (!this.meta.enableRemoteNotesCleaning) {
|
||||
this.logger.info('Remote notes cleaning is disabled, skipping...');
|
||||
return {
|
||||
@@ -75,9 +64,13 @@ export class CleanRemoteNotesProcessorService {
|
||||
|
||||
this.logger.info('cleaning remote notes...');
|
||||
|
||||
const maxDuration = this.meta.remoteNotesCleaningMaxProcessingDurationInMinutes * 60 * 1000; // Convert minutes to milliseconds
|
||||
const startAt = Date.now();
|
||||
|
||||
//#region queries
|
||||
// The date limit for the newest note to be considered for deletion.
|
||||
// All notes newer than this limit will always be retained.
|
||||
const newestLimit = this.idService.gen(Date.now() - (1000 * 60 * 60 * 24 * this.meta.remoteNotesCleaningExpiryDaysForEachNotes));
|
||||
|
||||
// The condition for removing the notes.
|
||||
// The note must be:
|
||||
@@ -99,7 +92,7 @@ export class CleanRemoteNotesProcessorService {
|
||||
const minId = (await this.notesRepository.createQueryBuilder('note')
|
||||
.select('MIN(note.id)', 'minId')
|
||||
.where({
|
||||
id: LessThan(initialConfig.newestLimit),
|
||||
id: LessThan(newestLimit),
|
||||
userHost: Not(IsNull()),
|
||||
replyId: IsNull(),
|
||||
renoteId: IsNull(),
|
||||
@@ -162,12 +155,12 @@ export class CleanRemoteNotesProcessorService {
|
||||
// | fff | fff | TRUE |
|
||||
// | ggg | ggg | FALSE |
|
||||
//
|
||||
const candidateNotesQuery = ({ limit }: { limit: number }) => this.db.createQueryBuilder()
|
||||
const candidateNotesQuery = this.db.createQueryBuilder()
|
||||
.select(`"${candidateNotesCteName}"."id"`, 'id')
|
||||
.addSelect('unremovable."id" IS NULL', 'isRemovable')
|
||||
.addSelect(`BOOL_OR("${candidateNotesCteName}"."isBase")`, 'isBase')
|
||||
.addCommonTableExpression(
|
||||
`((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(limit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`,
|
||||
`((SELECT "base".* FROM (${candidateNotesQueryBase.orderBy('note.id', 'ASC').limit(currentLimit).getQuery()}) AS "base") UNION ${candidateNotesQueryInductive.getQuery()})`,
|
||||
candidateNotesCteName,
|
||||
{ recursive: true },
|
||||
)
|
||||
@@ -185,11 +178,6 @@ export class CleanRemoteNotesProcessorService {
|
||||
let lowThroughputWarned = false;
|
||||
let transientErrors = 0;
|
||||
for (;;) {
|
||||
const { enabled, maxDuration, newestLimit } = getConfig();
|
||||
if (!enabled) {
|
||||
this.logger.info('Remote notes cleaning is disabled, processing stopped...');
|
||||
break;
|
||||
}
|
||||
//#region check time
|
||||
const batchBeginAt = Date.now();
|
||||
|
||||
@@ -217,38 +205,13 @@ export class CleanRemoteNotesProcessorService {
|
||||
let noteIds = null;
|
||||
|
||||
try {
|
||||
noteIds = await candidateNotesQuery({ limit: currentLimit }).setParameters(
|
||||
noteIds = await candidateNotesQuery.setParameters(
|
||||
{ newestLimit, cursorLeft },
|
||||
).getRawMany<{ id: MiNote['id'], isRemovable: boolean, isBase: boolean }>();
|
||||
} catch (e) {
|
||||
if (e instanceof QueryFailedError && e.driverError?.code === '57014') {
|
||||
// Statement timeout (maybe suddenly hit a large note tree), if possible, reduce the limit and try again
|
||||
// if not possible, skip the current batch of notes and find the next root note
|
||||
if (currentLimit <= minimumLimit) {
|
||||
job.log('Local note tree complexity is too high, finding next root note...');
|
||||
|
||||
const idWindow = await this.notesRepository.createQueryBuilder('note')
|
||||
.select('id')
|
||||
.where('note.id > :cursorLeft')
|
||||
.andWhere(removalCriteria)
|
||||
.andWhere({ replyId: IsNull(), renoteId: IsNull() })
|
||||
.orderBy('note.id', 'ASC')
|
||||
.limit(minimumLimit + 1)
|
||||
.setParameters({ cursorLeft, newestLimit })
|
||||
.getRawMany<{ id?: MiNote['id'] }>();
|
||||
|
||||
job.log(`Skipped note IDs: ${idWindow.slice(0, minimumLimit).map(id => id.id).join(', ')}`);
|
||||
|
||||
const lastId = idWindow.at(minimumLimit)?.id;
|
||||
|
||||
if (!lastId) {
|
||||
job.log('No more notes to clean.');
|
||||
break;
|
||||
}
|
||||
|
||||
cursorLeft = lastId;
|
||||
continue;
|
||||
}
|
||||
if (currentLimit > minimumLimit && e instanceof QueryFailedError && e.driverError?.code === '57014') {
|
||||
// Statement timeout (maybe suddenly hit a large note tree), reduce the limit and try again
|
||||
// continuous failures will eventually converge to currentLimit == minimumLimit and then throw
|
||||
currentLimit = Math.max(minimumLimit, Math.floor(currentLimit * 0.25));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -316,12 +316,8 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
|
||||
: new URL(path, new URL('resources/', import.meta.url));
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append(
|
||||
'file',
|
||||
blob ?? new Blob([new Uint8Array(await readFile(absPath))]),
|
||||
basename(absPath.toString()),
|
||||
);
|
||||
|
||||
formData.append('file', blob ??
|
||||
new File([new Uint8Array(await readFile(absPath))], basename(absPath.toString())));
|
||||
formData.append('force', 'true');
|
||||
if (name) {
|
||||
formData.append('name', name);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"rollup": "4.52.4",
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"@testing-library/vue": "8.1.0",
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/tinycolor2": "1.4.6",
|
||||
"@types/ws": "8.18.1",
|
||||
|
||||
@@ -20,4 +20,3 @@ export const instanceName = (siteName === 'Misskey' || siteName == null) ? host
|
||||
export const ui = localStorage.getItem('ui');
|
||||
export const debug = localStorage.getItem('debug') === 'true';
|
||||
export const isSafeMode = localStorage.getItem('isSafeMode') === 'true';
|
||||
export const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion)').matches;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"esbuild": "0.25.10",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
@@ -24,7 +24,7 @@
|
||||
"@rollup/plugin-json": "6.1.0",
|
||||
"@rollup/plugin-replace": "6.0.2",
|
||||
"@rollup/pluginutils": "5.3.0",
|
||||
"@sentry/vue": "10.20.0",
|
||||
"@sentry/vue": "10.19.0",
|
||||
"@syuilo/aiscript": "1.1.2",
|
||||
"@syuilo/aiscript-0-19-0": "npm:@syuilo/aiscript@^0.19.0",
|
||||
"@twemoji/parser": "16.0.0",
|
||||
@@ -109,7 +109,7 @@
|
||||
"@types/estree": "1.0.8",
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/micromatch": "4.0.9",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/punycode.js": "npm:@types/punycode@2.1.4",
|
||||
"@types/sanitize-html": "2.16.0",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
|
||||
@@ -303,6 +303,13 @@ export async function mainBoot() {
|
||||
});
|
||||
}
|
||||
|
||||
if ('Notification' in window) {
|
||||
// 許可を得ていなかったらリクエスト
|
||||
if (Notification.permission === 'default') {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
}
|
||||
|
||||
if (store.s.realtimeMode) {
|
||||
const stream = useStream();
|
||||
|
||||
|
||||
@@ -27,10 +27,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</template>
|
||||
</I18n>
|
||||
</div>
|
||||
<div v-if="$i != null && $i.id === channel.userId" style="color: var(--MI_THEME-warn)">
|
||||
<i class="ti ti-user-star ti-fw"></i>
|
||||
<span style="margin-left: 4px;">{{ i18n.ts.youAreAdmin }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<article v-if="channel.description">
|
||||
@@ -52,7 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { $i } from '@/i.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { miLocalStorage } from '@/local-storage.js';
|
||||
|
||||
|
||||
@@ -60,9 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
@drop.prevent.stop="onDrop"
|
||||
@contextmenu.stop="onContextmenu"
|
||||
>
|
||||
<div :class="$style.tipContainer">
|
||||
<MkTip k="drive"><div v-html="i18n.ts.driveAboutTip"></div></MkTip>
|
||||
</div>
|
||||
<MkTip k="drive"><div v-html="i18n.ts.driveAboutTip"></div></MkTip>
|
||||
|
||||
<div :class="$style.folders">
|
||||
<XFolder
|
||||
@@ -804,10 +802,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.tipContainer:not(:empty) {
|
||||
padding: 16px 32px;
|
||||
}
|
||||
|
||||
.folders,
|
||||
.files {
|
||||
display: grid;
|
||||
@@ -817,10 +811,6 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
@container (max-width: 600px) {
|
||||
.tipContainer:not(:empty) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.folders,
|
||||
.files {
|
||||
padding: 16px;
|
||||
|
||||
@@ -539,44 +539,46 @@ defineExpose({
|
||||
}
|
||||
|
||||
&.w1 {
|
||||
--columns: 5;
|
||||
width: calc((var(--eachSize) * 5) + (#{$pad} * 2));
|
||||
--columns: 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
&.w2 {
|
||||
--columns: 6;
|
||||
width: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||
--columns: 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
&.w3 {
|
||||
--columns: 7;
|
||||
width: calc((var(--eachSize) * 7) + (#{$pad} * 2));
|
||||
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
&.w4 {
|
||||
--columns: 8;
|
||||
width: calc((var(--eachSize) * 8) + (#{$pad} * 2));
|
||||
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
&.w5 {
|
||||
--columns: 9;
|
||||
width: calc((var(--eachSize) * 9) + (#{$pad} * 2));
|
||||
--columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
&.h1 {
|
||||
--rows: 4;
|
||||
height: calc((var(--eachSize) * 4) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h2 {
|
||||
--rows: 6;
|
||||
height: calc((var(--eachSize) * 6) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h3 {
|
||||
--rows: 8;
|
||||
height: calc((var(--eachSize) * 8) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
&.h4 {
|
||||
--rows: 10;
|
||||
height: calc((var(--eachSize) * 10) + (#{$pad} * 2));
|
||||
}
|
||||
|
||||
width: calc((var(--eachSize) * var(--columns)) + (#{$pad} * 2));
|
||||
height: calc((var(--eachSize) * var(--rows)) + (#{$pad} * 2));
|
||||
|
||||
&.asDrawer {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -591,7 +593,7 @@ defineExpose({
|
||||
|
||||
> .body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), 1fr);
|
||||
grid-template-columns: var(--columns);
|
||||
font-size: 30px;
|
||||
|
||||
> .config {
|
||||
@@ -633,7 +635,7 @@ defineExpose({
|
||||
::v-deep(section) {
|
||||
> .body {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--columns), 1fr);
|
||||
grid-template-columns: var(--columns);
|
||||
font-size: 30px;
|
||||
|
||||
> .item {
|
||||
|
||||
@@ -242,31 +242,27 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
|
||||
const AW = canvasEl.value.clientWidth;
|
||||
const AH = canvasEl.value.clientHeight;
|
||||
const BW = imageBitmap.width;
|
||||
const BH = imageBitmap.height;
|
||||
const BW = canvasEl.value.width; // Canvas intrinsic width (render width)
|
||||
const BH = canvasEl.value.height; // Canvas intrinsic height (render height)
|
||||
|
||||
let xOffset = 0;
|
||||
let yOffset = 0;
|
||||
// Calculate the scale factor for object-fit: contain
|
||||
const scale = Math.min(AW / BW, AH / BH);
|
||||
|
||||
// Calculate the actual rendered size of the canvas content
|
||||
const renderW = BW * scale;
|
||||
const renderH = BH * scale;
|
||||
|
||||
// Calculate the offset to center the image
|
||||
const xOffset = (AW - renderW) / 2;
|
||||
const yOffset = (AH - renderH) / 2;
|
||||
|
||||
if (AW / AH < BW / BH) { // 横長
|
||||
yOffset = AH - BH * (AW / BW);
|
||||
} else { // 縦長
|
||||
xOffset = AW - BW * (AH / BH);
|
||||
}
|
||||
const rect = canvasEl.value.getBoundingClientRect();
|
||||
const pointerOffsetX = ev.clientX - rect.left;
|
||||
const pointerOffsetY = ev.clientY - rect.top;
|
||||
|
||||
xOffset /= 2;
|
||||
yOffset /= 2;
|
||||
|
||||
let startX = ev.offsetX - xOffset;
|
||||
let startY = ev.offsetY - yOffset;
|
||||
|
||||
if (AW / AH < BW / BH) { // 横長
|
||||
startX = startX / (Math.max(AW, AH) / Math.max(BH / BW, 1));
|
||||
startY = startY / (Math.max(AW, AH) / Math.max(BW / BH, 1));
|
||||
} else { // 縦長
|
||||
startX = startX / (Math.min(AW, AH) / Math.max(BH / BW, 1));
|
||||
startY = startY / (Math.min(AW, AH) / Math.max(BW / BH, 1));
|
||||
}
|
||||
// Convert from client coordinates to canvas coordinates
|
||||
let startX = (pointerOffsetX - xOffset) / scale;
|
||||
let startY = (pointerOffsetY - yOffset) / scale;
|
||||
|
||||
const id = genId();
|
||||
if (penMode.value === 'fill') {
|
||||
@@ -311,19 +307,15 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
});
|
||||
}
|
||||
|
||||
_move(ev.offsetX, ev.offsetY);
|
||||
_move(ev.clientX, ev.clientY);
|
||||
|
||||
function _move(pointerX: number, pointerY: number) {
|
||||
let x = pointerX - xOffset;
|
||||
let y = pointerY - yOffset;
|
||||
|
||||
if (AW / AH < BW / BH) { // 横長
|
||||
x = x / (Math.max(AW, AH) / Math.max(BH / BW, 1));
|
||||
y = y / (Math.max(AW, AH) / Math.max(BW / BH, 1));
|
||||
} else { // 縦長
|
||||
x = x / (Math.min(AW, AH) / Math.max(BH / BW, 1));
|
||||
y = y / (Math.min(AW, AH) / Math.max(BW / BH, 1));
|
||||
}
|
||||
function _move(pointerClientX: number, pointerClientY: number) {
|
||||
const pointerX = pointerClientX - rect.left;
|
||||
const pointerY = pointerClientY - rect.top;
|
||||
|
||||
// Convert from client coordinates to canvas coordinates
|
||||
let x = (pointerX - xOffset) / scale;
|
||||
let y = (pointerY - yOffset) / scale;
|
||||
|
||||
const scaleX = Math.abs(x - startX);
|
||||
const scaleY = Math.abs(y - startY);
|
||||
@@ -340,7 +332,7 @@ function onImagePointerdown(ev: PointerEvent) {
|
||||
}
|
||||
|
||||
function move(ev: PointerEvent) {
|
||||
_move(ev.offsetX, ev.offsetY);
|
||||
_move(ev.clientX, ev.clientY);
|
||||
}
|
||||
|
||||
function up() {
|
||||
|
||||
@@ -58,7 +58,7 @@ const remaining = computed(() => {
|
||||
});
|
||||
|
||||
const total = computed(() => sum(props.choices.map(x => x.votes)));
|
||||
const closed = computed(() => props.expiresAt != null && remaining.value <= 0);
|
||||
const closed = computed(() => remaining.value <= 0);
|
||||
const isVoted = computed(() => !props.multiple && props.choices.some(c => c.isVoted));
|
||||
const timer = computed(() => i18n.tsx._poll[
|
||||
remaining.value >= 86400 ? 'remainingDays' :
|
||||
|
||||
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<header :class="$style.header">
|
||||
<div :class="$style.headerLeft">
|
||||
<button v-if="!fixed" :class="$style.cancel" class="_button" @click="cancel"><i class="ti ti-x"></i></button>
|
||||
<button v-click-anime v-tooltip="i18n.ts.account" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<button v-click-anime v-tooltip="i18n.ts.switchAccount" :class="$style.account" class="_button" @click="openAccountMenu">
|
||||
<img :class="$style.avatar" :src="(postAccount ?? $i).avatarUrl" style="border-radius: 100%;"/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -42,11 +42,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { instanceName } from '@@/js/config.js';
|
||||
import { $i } from '@/i.js';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import { instance } from '@/instance.js';
|
||||
import { apiWithDialog, promiseDialog, alert } from '@/os.js';
|
||||
import { apiWithDialog, promiseDialog } from '@/os.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { getAccounts } from '@/accounts.js';
|
||||
@@ -73,28 +72,11 @@ const supported = ref(false);
|
||||
const pushSubscription = ref<PushSubscription | null>(null);
|
||||
const pushRegistrationInServer = ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
|
||||
|
||||
async function subscribe() {
|
||||
function subscribe() {
|
||||
if (!registration.value || !supported.value || !instance.swPublickey) return;
|
||||
|
||||
if ('Notification' in window) {
|
||||
let permission = Notification.permission;
|
||||
|
||||
if (Notification.permission === 'default') {
|
||||
permission = await promiseDialog(Notification.requestPermission(), null, null, i18n.ts.pleaseAllowPushNotification);
|
||||
}
|
||||
|
||||
if (permission !== 'granted') {
|
||||
alert({
|
||||
type: 'error',
|
||||
title: i18n.ts.browserPushNotificationDisabled,
|
||||
text: i18n.tsx.browserPushNotificationDisabledDescription({ serverName: instanceName }),
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
|
||||
await promiseDialog(registration.value.pushManager.subscribe({
|
||||
return promiseDialog(registration.value.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
|
||||
})
|
||||
|
||||
@@ -47,11 +47,9 @@ defineProps<{
|
||||
<style lang="scss" module>
|
||||
.root {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
||||
&.inline {
|
||||
display: inline-block;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div v-if="!thin_ && narrow && props.displayMyAvatar && $i" class="_button" :class="$style.buttonsLeft" @click="openAccountMenu">
|
||||
<MkAvatar :class="$style.avatar" :user="$i"/>
|
||||
</div>
|
||||
<div v-else-if="!thin_ && narrow && !hideTitle" :class="[$style.buttons, $style.buttonsLeft]"></div>
|
||||
<div v-else-if="!thin_ && narrow && !hideTitle" :class="$style.buttonsLeft"/>
|
||||
|
||||
<template v-if="pageMetadata">
|
||||
<div v-if="!hideTitle" :class="$style.titleContainer" @click="top">
|
||||
@@ -18,9 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<i v-else-if="pageMetadata.icon" :class="[$style.titleIcon, pageMetadata.icon]"></i>
|
||||
|
||||
<div class="_nowrap" :class="$style.title">
|
||||
<div :class="$style.title">
|
||||
<MkUserName v-if="pageMetadata.userName" :user="pageMetadata.userName" :nowrap="true"/>
|
||||
<div v-else-if="pageMetadata.title" class="_nowrap">{{ pageMetadata.title }}</div>
|
||||
<div v-else-if="pageMetadata.title">{{ pageMetadata.title }}</div>
|
||||
<div v-if="pageMetadata.subtitle" :class="$style.subtitle">
|
||||
{{ pageMetadata.subtitle }}
|
||||
</div>
|
||||
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
<XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/>
|
||||
</template>
|
||||
<div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="[$style.buttons, $style.buttonsRight]">
|
||||
<div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight">
|
||||
<template v-for="action in actions">
|
||||
<button v-tooltip.noDelay="action.text" class="_button" :class="[$style.button, { [$style.highlighted]: action.highlighted }]" @click.stop="action.handler" @touchstart="preventDrag"><i :class="action.icon"></i></button>
|
||||
</template>
|
||||
@@ -155,10 +155,8 @@ onUnmounted(() => {
|
||||
|
||||
.upper {
|
||||
--height: 50px;
|
||||
--margin: var(--MI-margin);
|
||||
display: flex;
|
||||
gap: var(--margin);
|
||||
align-items: center;
|
||||
gap: var(--MI-margin);
|
||||
height: var(--height);
|
||||
|
||||
.tabs:first-child {
|
||||
@@ -171,7 +169,6 @@ onUnmounted(() => {
|
||||
|
||||
&.thin {
|
||||
--height: 40px;
|
||||
--margin: 8px;
|
||||
|
||||
> .buttons {
|
||||
> .button {
|
||||
@@ -182,8 +179,12 @@ onUnmounted(() => {
|
||||
|
||||
&.slim {
|
||||
text-align: center;
|
||||
gap: 0;
|
||||
|
||||
.titleContainer {
|
||||
.tabs:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
> .titleContainer {
|
||||
margin: 0 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -196,7 +197,7 @@ onUnmounted(() => {
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-shrink: 0;
|
||||
--margin: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: var(--height);
|
||||
@@ -206,6 +207,16 @@ onUnmounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.buttonsLeft {
|
||||
composes: buttons;
|
||||
margin: 0 var(--margin) 0 0;
|
||||
}
|
||||
|
||||
.buttonsRight {
|
||||
composes: buttons;
|
||||
margin: 0 0 0 var(--margin);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
$size: 32px;
|
||||
display: inline-block;
|
||||
@@ -220,7 +231,7 @@ onUnmounted(() => {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: var(--height);
|
||||
width: calc(var(--height) - 8px);
|
||||
width: calc(var(--height) - (var(--margin)));
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
@@ -243,7 +254,6 @@ onUnmounted(() => {
|
||||
.titleContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
max-width: min(30vw, 400px);
|
||||
overflow: clip;
|
||||
white-space: nowrap;
|
||||
@@ -277,6 +287,9 @@ onUnmounted(() => {
|
||||
|
||||
.title {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,6 @@ import FormSplit from '@/components/form/split.vue';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||
import MkLink from '@/components/MkLink.vue';
|
||||
import MkInfo from '@/components/MkInfo.vue';
|
||||
|
||||
const initStats = () => misskeyApi('stats', {});
|
||||
</script>
|
||||
|
||||
@@ -6,10 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<PageWithHeader>
|
||||
<div class="_spacer" style="--MI_SPACER-w: 500px;">
|
||||
<div v-if="instance.ads.length > 0" class="_gaps">
|
||||
<div class="_gaps">
|
||||
<MkAd v-for="ad in instance.ads" :key="ad.id" :specify="ad"/>
|
||||
</div>
|
||||
<MkResult v-else type="empty"/>
|
||||
</div>
|
||||
</PageWithHeader>
|
||||
</template>
|
||||
|
||||
@@ -15,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<div :class="$style.bannerStatus">
|
||||
<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
|
||||
<div><i class="ti ti-pencil ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
|
||||
<div v-if="$i != null && channel != null && $i.id === channel.userId" style="color: var(--MI_THEME-warn)"><i class="ti ti-user-star ti-fw"></i><span style="margin-left: 4px;">{{ i18n.ts.youAreAdmin }}</span></div>
|
||||
</div>
|
||||
<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
|
||||
<div :class="$style.bannerFade"></div>
|
||||
|
||||
@@ -44,8 +44,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
</div>
|
||||
</MkPagination>
|
||||
</div>
|
||||
<div v-else-if="tab === 'owned'" class="_gaps">
|
||||
<MkButton link primary rounded to="/channels/new"><i class="ti ti-plus"></i> {{ i18n.ts.createNew }}</MkButton>
|
||||
<div v-else-if="tab === 'owned'">
|
||||
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
|
||||
<MkPagination v-slot="{items}" :paginator="ownedPaginator">
|
||||
<div :class="$style.root">
|
||||
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
|
||||
@@ -126,7 +126,15 @@ async function search() {
|
||||
key.value = query + type;
|
||||
}
|
||||
|
||||
const headerActions = computed(() => []);
|
||||
function create() {
|
||||
router.push('/channels/new');
|
||||
}
|
||||
|
||||
const headerActions = computed(() => [{
|
||||
icon: 'ti ti-plus',
|
||||
text: i18n.ts.create,
|
||||
handler: create,
|
||||
}]);
|
||||
|
||||
const headerTabs = computed(() => [{
|
||||
key: 'search',
|
||||
|
||||
@@ -5,11 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<template>
|
||||
<div class="_spacer" style="--MI_SPACER-w: 700px;">
|
||||
<div v-if="roles != null && roles.length > 0" class="_gaps_s">
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :forModeration="false"/>
|
||||
</div>
|
||||
<MkLoading v-else-if="loading" />
|
||||
<MkResult v-else type="empty" :text="i18n.ts.noRole"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -17,16 +15,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
import { ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import MkRolePreview from '@/components/MkRolePreview.vue';
|
||||
import { i18n } from '@/i18n.js';
|
||||
import { misskeyApi } from '@/utility/misskey-api.js';
|
||||
|
||||
const roles = ref<Misskey.entities.Role[] | null>(null);
|
||||
const loading = ref(true);
|
||||
|
||||
misskeyApi('roles/list').then(res => {
|
||||
roles.value = res.filter(x => x.target === 'manual').sort((a, b) => b.displayOrder - a.displayOrder);
|
||||
}).finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -6,10 +6,6 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template>
|
||||
<SearchMarker path="/settings/emoji-palette" :label="i18n.ts.emojiPalette" :keywords="['emoji', 'palette']" icon="ti ti-mood-happy">
|
||||
<div class="_gaps_m">
|
||||
<MkFeatureBanner icon="/client-assets/artist_palette_3d.png" color="#ff9100">
|
||||
<SearchText>{{ i18n.ts._settings.emojiPaletteBanner }}</SearchText>
|
||||
</MkFeatureBanner>
|
||||
|
||||
<FormSection first>
|
||||
<template #label>{{ i18n.ts._emojiPalette.palettes }}</template>
|
||||
|
||||
@@ -127,7 +123,6 @@ import { computed, ref, watch } from 'vue';
|
||||
import XPalette from './emoji-palette.palette.vue';
|
||||
import type { MkSelectItem } from '@/components/MkSelect.vue';
|
||||
import { genId } from '@/utility/id.js';
|
||||
import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
|
||||
import MkRadios from '@/components/MkRadios.vue';
|
||||
import MkButton from '@/components/MkButton.vue';
|
||||
import FormSection from '@/components/form/section.vue';
|
||||
|
||||
@@ -54,9 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<template #icon><SearchIcon><i class="ti ti-badges"></i></SearchIcon></template>
|
||||
<template #label><SearchLabel>{{ i18n.ts.rolesAssignedToMe }}</SearchLabel></template>
|
||||
|
||||
<div class="_gaps_s">
|
||||
<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
|
||||
</div>
|
||||
<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
|
||||
</MkFolder>
|
||||
</SearchMarker>
|
||||
|
||||
@@ -136,14 +134,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
<hr>
|
||||
|
||||
<MkButton @click="forceCloudBackup">Force cloud backup</MkButton>
|
||||
|
||||
<hr>
|
||||
|
||||
<template v-if="$i.policies.chatAvailability !== 'unavailable'">
|
||||
<MkButton @click="readAllChatMessages">{{ i18n.ts.readAllChatMessages }}</MkButton>
|
||||
<MkButton @click="readAllChatMessages">Read all chat messages</MkButton>
|
||||
|
||||
<hr>
|
||||
</template>
|
||||
|
||||
<MkButton @click="forceCloudBackup">{{ i18n.ts._preferencesBackup.forceBackup }}</MkButton>
|
||||
|
||||
<FormSlot>
|
||||
<MkButton danger @click="migrate"><i class="ti ti-refresh"></i> {{ i18n.ts.migrateOldSettings }}</MkButton>
|
||||
<template #caption>{{ i18n.ts.migrateOldSettings_description }}</template>
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import * as Misskey from 'misskey-js';
|
||||
import { hemisphere } from '@@/js/intl-const.js';
|
||||
import { prefersReducedMotion } from '@@/js/config.js';
|
||||
import { definePreferences } from './manager.js';
|
||||
import type { Theme } from '@/theme.js';
|
||||
import type { SoundType } from '@/utility/sound.js';
|
||||
@@ -212,10 +211,10 @@ export const PREF_DEF = definePreferences({
|
||||
default: false,
|
||||
},
|
||||
animation: {
|
||||
default: !prefersReducedMotion,
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
animatedMfm: {
|
||||
default: !prefersReducedMotion,
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
advancedMfm: {
|
||||
default: true,
|
||||
@@ -233,7 +232,7 @@ export const PREF_DEF = definePreferences({
|
||||
default: false,
|
||||
},
|
||||
disableShowingAnimatedImages: {
|
||||
default: prefersReducedMotion,
|
||||
default: window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
emojiStyle: {
|
||||
default: 'twemoji', // twemoji / fluentEmoji / native
|
||||
|
||||
@@ -7,7 +7,6 @@ import { markRaw, ref } from 'vue';
|
||||
import * as Misskey from 'misskey-js';
|
||||
import lightTheme from '@@/themes/l-light.json5';
|
||||
import darkTheme from '@@/themes/d-green-lime.json5';
|
||||
import { prefersReducedMotion } from '@@/js/config.js';
|
||||
import { hemisphere } from '@@/js/intl-const.js';
|
||||
import type { DeviceKind } from '@/utility/device-kind.js';
|
||||
import type { Plugin } from '@/plugin.js';
|
||||
@@ -221,11 +220,11 @@ export const store = markRaw(new Pizzax('base', {
|
||||
},
|
||||
animation: {
|
||||
where: 'device',
|
||||
default: !prefersReducedMotion,
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
animatedMfm: {
|
||||
where: 'device',
|
||||
default: !prefersReducedMotion,
|
||||
default: !window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
advancedMfm: {
|
||||
where: 'device',
|
||||
@@ -249,7 +248,7 @@ export const store = markRaw(new Pizzax('base', {
|
||||
},
|
||||
disableShowingAnimatedImages: {
|
||||
where: 'device',
|
||||
default: prefersReducedMotion,
|
||||
default: window.matchMedia('(prefers-reduced-motion)').matches,
|
||||
},
|
||||
emojiStyle: {
|
||||
where: 'device',
|
||||
|
||||
@@ -11,8 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<img :src="instance.iconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
|
||||
</button>
|
||||
<button v-if="!iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
|
||||
<i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i>
|
||||
<i v-else class="ti ti-bolt-off ti-fw"></i>
|
||||
<i class="ti ti-bolt ti-fw"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div :class="$style.middle">
|
||||
@@ -55,8 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||
<i class="ti ti-apps ti-fw"></i>
|
||||
</button>
|
||||
<button v-if="iconOnly" v-tooltip.noDelay.right="i18n.ts.realtimeMode" class="_button" :class="[$style.realtimeMode, store.r.realtimeMode.value ? $style.on : null]" @click="toggleRealtimeMode">
|
||||
<i v-if="store.r.realtimeMode.value" class="ti ti-bolt ti-fw"></i>
|
||||
<i v-else class="ti ti-bolt-off ti-fw"></i>
|
||||
<i class="ti ti-bolt ti-fw"></i>
|
||||
</button>
|
||||
<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="() => { os.post(); }">
|
||||
<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
|
||||
|
||||
@@ -5,21 +5,13 @@
|
||||
|
||||
import _confetti from 'canvas-confetti';
|
||||
import * as os from '@/os.js';
|
||||
import { prefer } from '@/preferences.js';
|
||||
|
||||
export function confetti(options: { duration?: number; } = {}) {
|
||||
if (!prefer.s.animation) return;
|
||||
|
||||
const duration = options.duration ?? 1000 * 4;
|
||||
const animationEnd = Date.now() + duration;
|
||||
const defaults = {
|
||||
startVelocity: 30,
|
||||
spread: 360,
|
||||
ticks: 60,
|
||||
zIndex: os.claimZIndex('high'),
|
||||
} satisfies _confetti.Options;
|
||||
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: os.claimZIndex('high') };
|
||||
|
||||
function randomInRange(min: number, max: number) {
|
||||
function randomInRange(min, max) {
|
||||
return Math.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/wawoff2": "1.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1"
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./built/index.js",
|
||||
"types": "./built/index.d.ts",
|
||||
"default": "./built/index.js"
|
||||
"types": "./built/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./built/*",
|
||||
"types": "./built/*",
|
||||
"default": "./built/*"
|
||||
"types": "./built/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -25,7 +23,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/matter-js": "0.20.2",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@types/seedrandom": "3.0.8",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@readme/openapi-parser": "5.0.2",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"openapi-types": "12.1.3",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "misskey-js",
|
||||
"version": "2025.11.0-alpha.0",
|
||||
"version": "2025.10.1",
|
||||
"description": "Misskey SDK for JavaScript",
|
||||
"license": "MIT",
|
||||
"main": "./built/index.js",
|
||||
@@ -9,13 +9,11 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./built/index.js",
|
||||
"types": "./built/index.d.ts",
|
||||
"default": "./built/index.js"
|
||||
"types": "./built/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./built/*",
|
||||
"types": "./built/*",
|
||||
"default": "./built/*"
|
||||
"types": "./built/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -38,7 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@microsoft/api-extractor": "7.53.1",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
|
||||
@@ -7,13 +7,11 @@
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./built/index.js",
|
||||
"types": "./built/index.d.ts",
|
||||
"default": "./built/index.js"
|
||||
"types": "./built/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./built/*",
|
||||
"types": "./built/*",
|
||||
"default": "./built/*"
|
||||
"types": "./built/*"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -24,7 +22,7 @@
|
||||
"lint": "pnpm typecheck && pnpm eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.46.1",
|
||||
"@typescript-eslint/parser": "8.46.1",
|
||||
"esbuild": "0.25.10",
|
||||
|
||||
Generated
+747
-655
File diff suppressed because it is too large
Load Diff
+18
-19
@@ -9,14 +9,14 @@
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
"remark-parse": "11.0.0",
|
||||
"typescript": "5.9.3",
|
||||
"unified": "11.0.5",
|
||||
"vite": "6.4.1",
|
||||
"vite": "6.3.6",
|
||||
"vite-node": "3.2.4",
|
||||
"vitest": "3.2.4"
|
||||
}
|
||||
@@ -940,14 +940,14 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz",
|
||||
"integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==",
|
||||
"version": "22.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.10.tgz",
|
||||
"integrity": "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
@@ -1171,9 +1171,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -1247,11 +1247,10 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -2729,9 +2728,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
@@ -2840,9 +2839,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz",
|
||||
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
|
||||
"version": "6.3.6",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.6.tgz",
|
||||
"integrity": "sha512-0msEVHJEScQbhkbVTb/4iHZdJ6SXp/AvxL2sjwYQFfBqleHtnCqv1J3sa9zbWz/6kW1m9Tfzn92vW+kZ1WV6QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mdast": "4.0.4",
|
||||
"@types/node": "24.9.1",
|
||||
"@types/node": "22.18.10",
|
||||
"@vitest/coverage-v8": "3.2.4",
|
||||
"mdast-util-to-string": "4.0.0",
|
||||
"remark": "15.0.1",
|
||||
"remark-parse": "11.0.0",
|
||||
"typescript": "5.9.3",
|
||||
"unified": "11.0.5",
|
||||
"vite": "6.4.1",
|
||||
"vite": "6.3.6",
|
||||
"vite-node": "3.2.4",
|
||||
"vitest": "3.2.4"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user