Merge remote-branch 'misskey/develop'

This commit is contained in:
NoriDev 2023-12-13 13:28:20 +09:00
parent 5d2f2acfc9
commit ac8d0f133e
357 changed files with 12654 additions and 5689 deletions

29
.github/labeler.yml vendored
View file

@ -1,21 +1,34 @@
'packages/backend':
- packages/backend/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/**/*']
'packages/backend:test':
- packages/backend/test/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/backend/test/**/*']
'packages/frontend':
- packages/frontend/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/frontend/**/*']
'packages/frontend:test':
- cypress/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['cypress/**/*']
'packages/sw':
- packages/sw/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/sw/**/*']
'packages/cherrypick-js':
- packages/cherrypick-js/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/cherrypick-js/**/*']
'packages/cherrypick-js:test':
- packages/cherrypick-js/test/**/*
- packages/cherrypick-js/test-d/**/*
- any:
- changed-files:
- any-glob-to-any-file: ['packages/cherrypick-js/test/**/*', 'packages/cherrypick-js/test-d/**/*']

View file

@ -11,6 +11,6 @@ jobs:
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"

View file

@ -46,9 +46,11 @@
- Fix: 共有機能をサポートしていないブラウザの場合は共有ボタンを非表示にする #11305
- Fix: 通知のグルーピング設定を変更してもリロードされるまで表示が変わらない問題を修正 #12470
- Fix: 長い名前のチャンネルにおける投稿フォームの表示が崩れる問題を修正
- Fix: セキュリティ向上のためAiScriptの`Mk:apiExternal`を無効化
### Server
- Enhance: MFM `$[ruby ]` が他ソフトウェアと連合されるように
- Enhance: Meilisearchを有効にした検索で、ユーザーのミュートやブロックを考慮するように
- Fix: 時間経過により無効化されたアンテナを再有効化したとき、サーバ再起動までその状況が反映されないのを修正 #12303
- Fix: ロールタイムラインが保存されない問題を修正
- Fix: api.jsonの生成ロジックを改善 #12402
@ -61,6 +63,7 @@
- Fix: ユーザのノート一覧にてインスタンスミュートが効かない問題
- Fix: チャンネルのノート一覧にてインスタンスミュートが効かない問題
- Fix: 「みつける」が年越し時に壊れる問題を修正
- Fix: アカウントをブロックした際に、自身のユーザーのページでノートが相手に表示される問題を修正
## 2023.11.1

View file

@ -117,6 +117,10 @@ command.
- Server-side source files and automatically builds them if they are modified. Automatically start the server process(es).
- Vite HMR (just the `vite` command) is available. The behavior may be different from production.
- Service Worker is watched by esbuild.
- The front end can be viewed by accessing `http://localhost:5173`.
- The backend listens on the port configured with `port` in .config/default.yml.
If you have not changed it from the default, it will be "http://localhost:3000".
If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
### Dev Container
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.

View file

@ -0,0 +1,42 @@
version: "3"
# このconfigは、 dockerでMisskey本体を起動せず、 redisとpostgresql などだけを起動します
services:
redis:
restart: always
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- ./redis:/data
healthcheck:
test: "redis-cli ping"
interval: 5s
retries: 20
db:
restart: always
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
- .config/docker.env
volumes:
- ./db:/var/lib/postgresql/data
healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s
retries: 20
# meilisearch:
# restart: always
# image: getmeili/meilisearch:v1.3.4
# environment:
# - MEILI_NO_ANALYTICS=true
# - MEILI_ENV=production
# env_file:
# - .config/meilisearch.env
# volumes:
# - ./meili_data:/meili_data

View file

@ -1,7 +1,7 @@
{
"name": "cherrypick",
"version": "4.6.0-beta.2",
"basedMisskeyVersion": "2023.12.0-beta.2",
"basedMisskeyVersion": "2023.12.0-beta.3",
"codename": "nasubi",
"repository": {
"type": "git",
@ -30,7 +30,7 @@
"migrateandstart": "pnpm migrate && pnpm start",
"migrateandstart:docker": "pnpm migrate && exec pnpm start:docker",
"watch": "pnpm dev",
"dev": "node ./scripts/dev.mjs",
"dev": "pnpm -r dev",
"lint": "pnpm -r lint",
"cy:open": "pnpm cypress open --browser --e2e --config-file=cypress.config.ts",
"cy:run": "pnpm cypress run",
@ -54,13 +54,13 @@
"js-yaml": "4.1.0",
"postcss": "8.4.32",
"terser": "5.24.0",
"typescript": "5.3.2"
"typescript": "5.3.3"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"cross-env": "7.0.3",
"cypress": "13.6.0",
"cypress": "13.6.1",
"eslint": "8.55.0",
"start-server-and-test": "2.0.3",
"ncp": "2.0.0"

View file

@ -16,6 +16,7 @@
"watch:swc": "swc src -d built -D -w",
"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs",
"dev": "node ./built/boot/entry.js",
"typecheck": "tsc --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
@ -61,13 +62,13 @@
"dependencies": {
"@aws-sdk/client-s3": "3.412.0",
"@aws-sdk/lib-storage": "3.412.0",
"@bull-board/api": "5.10.1",
"@bull-board/fastify": "5.10.1",
"@bull-board/ui": "5.10.1",
"@bull-board/api": "5.10.2",
"@bull-board/fastify": "5.10.2",
"@bull-board/ui": "5.10.2",
"@discordapp/twemoji": "14.1.2",
"@fastify/accepts": "4.2.0",
"@fastify/accepts": "4.3.0",
"@fastify/cookie": "9.2.0",
"@fastify/cors": "8.4.1",
"@fastify/cors": "8.4.2",
"@fastify/express": "2.3.0",
"@fastify/http-proxy": "9.3.0",
"@fastify/multipart": "8.0.0",
@ -92,7 +93,7 @@
"bcryptjs": "2.4.3",
"blurhash": "2.0.5",
"body-parser": "1.20.2",
"bullmq": "4.14.4",
"bullmq": "4.15.2",
"cacheable-lookup": "7.0.0",
"cbor": "9.0.1",
"chalk": "5.3.0",
@ -122,7 +123,7 @@
"js-yaml": "4.1.0",
"jsdom": "23.0.1",
"json5": "2.2.3",
"jsonld": "8.3.1",
"jsonld": "8.3.2",
"jsrsasign": "10.9.0",
"meilisearch": "0.36.0",
"microformats-parser": "1.5.2",
@ -137,7 +138,7 @@
"oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14",
"otpauth": "9.2.0",
"otpauth": "9.2.1",
"parse5": "7.1.2",
"pg": "8.11.3",
"pkce-challenge": "4.0.1",
@ -151,7 +152,7 @@
"ratelimiter": "3.4.1",
"re2": "1.20.9",
"redis-lock": "0.1.4",
"reflect-metadata": "0.1.13",
"reflect-metadata": "0.1.14",
"rename": "1.0.4",
"rss-parser": "3.13.0",
"rxjs": "7.8.1",
@ -171,7 +172,7 @@
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.17",
"typescript": "5.3.2",
"typescript": "5.3.3",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.6",
@ -191,14 +192,14 @@
"@types/content-disposition": "0.5.8",
"@types/fluent-ffmpeg": "2.1.24",
"@types/http-link-header": "1.0.5",
"@types/jest": "29.5.10",
"@types/jest": "29.5.11",
"@types/js-yaml": "4.0.9",
"@types/jsdom": "21.1.6",
"@types/jsonld": "1.5.13",
"@types/jsrsasign": "10.5.12",
"@types/mime-types": "2.1.4",
"@types/ms": "0.7.34",
"@types/node": "20.10.3",
"@types/node": "20.10.4",
"@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.14",
"@types/oauth": "0.9.4",
@ -221,8 +222,8 @@
"@types/vary": "1.1.3",
"@types/web-push": "3.6.3",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"aws-sdk-client-mock": "3.0.0",
"cross-env": "7.0.3",
"eslint": "8.55.0",

View file

@ -28,6 +28,7 @@ type TimelineOptions = {
redisTimelines: FanoutTimelineName[],
noteFilter?: (note: MiNote) => boolean,
alwaysIncludeMyNotes?: boolean;
ignoreAuthorFromBlock?: boolean;
ignoreAuthorFromMute?: boolean;
excludeNoFiles?: boolean;
excludeReplies?: boolean;
@ -61,11 +62,15 @@ export class FanoutTimelineEndpointService {
// 呼び出し元と以下の処理をシンプルにするためにdbFallbackを置き換える
if (!ps.useDbFallback) ps.dbFallback = () => Promise.resolve([]);
const shouldPrepend = ps.sinceId && !ps.untilId;
const idCompare: (a: string, b: string) => number = shouldPrepend ? (a, b) => a < b ? -1 : 1 : (a, b) => a > b ? -1 : 1;
const redisResult = await this.fanoutTimelineService.getMulti(ps.redisTimelines, ps.untilId, ps.sinceId);
// TODO: いい感じにgetMulti内でソート済だからuniqするときにredisResultが全てソート済なのを利用して再ソートを避けたい
const redisResultIds = Array.from(new Set(redisResult.flat(1)));
redisResultIds.sort((a, b) => a > b ? -1 : 1);
redisResultIds.sort(idCompare);
noteIds = redisResultIds.slice(0, ps.limit);
shouldFallbackToDb = shouldFallbackToDb || (noteIds.length === 0);
@ -115,7 +120,7 @@ export class FanoutTimelineEndpointService {
const parentFilter = filter;
filter = (note) => {
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromMute)) return false;
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
if (isInstanceMuted(note, userMutedInstances)) return false;
@ -132,32 +137,43 @@ export class FanoutTimelineEndpointService {
const remainingToRead = ps.limit - redisTimeline.length;
// DBからの取り直しを減らす初回と同じ割合以上で成功すると仮定するが、クエリの長さを考えて三倍まで
const countToGet = remainingToRead * Math.ceil(Math.min(1.1 / lastSuccessfulRate, 3));
const countToGet = Math.ceil(remainingToRead * Math.min(1.1 / lastSuccessfulRate, 3));
noteIds = redisResultIds.slice(readFromRedis, readFromRedis + countToGet);
readFromRedis += noteIds.length;
const gotFromDb = await this.getAndFilterFromDb(noteIds, filter);
const gotFromDb = await this.getAndFilterFromDb(noteIds, filter, idCompare);
redisTimeline.push(...gotFromDb);
lastSuccessfulRate = gotFromDb.length / noteIds.length;
if (ps.allowPartial ? redisTimeline.length !== 0 : redisTimeline.length >= ps.limit) {
// 十分Redisからとれた
return redisTimeline.slice(0, ps.limit);
const result = redisTimeline.slice(0, ps.limit);
if (shouldPrepend) result.reverse();
return result;
}
}
// まだ足りない分はDBにフォールバック
const remainingToRead = ps.limit - redisTimeline.length;
const gotFromDb = await ps.dbFallback(noteIds[noteIds.length - 1], ps.sinceId, remainingToRead);
redisTimeline.push(...gotFromDb);
return redisTimeline;
let dbUntil: string | null;
let dbSince: string | null;
if (shouldPrepend) {
redisTimeline.reverse();
dbUntil = ps.untilId;
dbSince = noteIds[noteIds.length - 1];
} else {
dbUntil = noteIds[noteIds.length - 1];
dbSince = ps.sinceId;
}
const gotFromDb = await ps.dbFallback(dbUntil, dbSince, remainingToRead);
return shouldPrepend ? [...gotFromDb, ...redisTimeline] : [...redisTimeline, ...gotFromDb];
}
return await ps.dbFallback(ps.untilId, ps.sinceId, ps.limit);
}
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean): Promise<MiNote[]> {
private async getAndFilterFromDb(noteIds: string[], noteFilter: (note: MiNote) => boolean, idCompare: (a: string, b: string) => number): Promise<MiNote[]> {
const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')
@ -169,7 +185,7 @@ export class FanoutTimelineEndpointService {
const notes = (await query.getMany()).filter(noteFilter);
notes.sort((a, b) => a.id > b.id ? -1 : 1);
notes.sort((a, b) => idCompare(a.id, b.id));
return notes;
}

View file

@ -12,6 +12,8 @@ import { MiNote } from '@/models/Note.js';
import { MiUser } from '@/models/_.js';
import type { NotesRepository } from '@/models/_.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { CacheService } from '@/core/CacheService.js';
import { QueryService } from '@/core/QueryService.js';
import { IdService } from '@/core/IdService.js';
import type { Index, MeiliSearch } from 'meilisearch';
@ -74,6 +76,7 @@ export class SearchService {
@Inject(DI.notesRepository)
private notesRepository: NotesRepository,
private cacheService: CacheService,
private queryService: QueryService,
private idService: IdService,
) {
@ -193,8 +196,19 @@ export class SearchService {
limit: pagination.limit,
});
if (res.hits.length === 0) return [];
const notes = await this.notesRepository.findBy({
const [
userIdsWhoMeMuting,
userIdsWhoBlockingMe,
] = me ? await Promise.all([
this.cacheService.userMutingsCache.fetch(me.id),
this.cacheService.userBlockedCache.fetch(me.id),
]) : [new Set<string>(), new Set<string>()];
const notes = (await this.notesRepository.findBy({
id: In(res.hits.map(x => x.id)),
})).filter(note => {
if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
return true;
});
return notes.sort((a, b) => a.id > b.id ? -1 : 1);
} else {

View file

@ -9,6 +9,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { FeaturedService } from '@/core/FeaturedService.js';
import { CacheService } from '@/core/CacheService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
export const meta = {
tags: ['notes'],
@ -46,6 +48,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private noteEntityService: NoteEntityService,
private featuredService: FeaturedService,
private cacheService: CacheService,
) {
super(meta, paramDef, async (ps, me) => {
let noteIds = await this.featuredService.getPerUserNotesRanking(ps.userId, 50);
@ -60,6 +63,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return [];
}
const [
userIdsWhoMeMuting,
userIdsWhoBlockingMe,
] = me ? await Promise.all([
this.cacheService.userMutingsCache.fetch(me.id),
this.cacheService.userBlockedCache.fetch(me.id),
]) : [new Set<string>(), new Set<string>()];
const query = this.notesRepository.createQueryBuilder('note')
.where('note.id IN (:...noteIds)', { noteIds: noteIds })
.innerJoinAndSelect('note.user', 'user')
@ -69,10 +80,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.leftJoinAndSelect('renote.user', 'renoteUser')
.leftJoinAndSelect('note.channel', 'channel');
const notes = await query.getMany();
notes.sort((a, b) => a.id > b.id ? -1 : 1);
const notes = (await query.getMany()).filter(note => {
if (me && isUserRelated(note, userIdsWhoBlockingMe, false)) return false;
if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false;
// TODO: ミュート等考慮
return true;
});
notes.sort((a, b) => a.id > b.id ? -1 : 1);
return await this.noteEntityService.packMany(notes, me);
});

View file

@ -44,7 +44,7 @@ export function genOpenapiSpec(config: Config) {
// 書き換えたりするのでディープコピーしておく。そのまま編集するとメモリ上の値が汚れて次回以降の出力に影響する
const copiedEndpoints = JSON.parse(JSON.stringify(endpoints)) as IEndpoint[];
for (const endpoint of copiedEndpoints.filter(ep => !ep.meta.secure)) {
for (const endpoint of copiedEndpoints) {
const errors = {} as any;
if (endpoint.meta.errors) {
@ -60,6 +60,11 @@ export function genOpenapiSpec(config: Config) {
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
if (endpoint.meta.secure) {
desc += '**Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.\n';
}
desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
if (endpoint.meta.kind) {
const kind = endpoint.meta.kind;

View file

@ -146,6 +146,9 @@ type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['reques
// @public (undocumented)
type AdminEmojiDeleteRequest = operations['admin/emoji/delete']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminEmojiImportZipRequest = operations['admin/emoji/import-zip']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminEmojiListRemoteRequest = operations['admin/emoji/list-remote']['requestBody']['content']['application/json'];
@ -402,8 +405,6 @@ class APIClient {
fetch: FetchLike;
// (undocumented)
origin: string;
// (undocumented)
request<E extends keyof Endpoints, P extends Endpoints[E]['req']>(endpoint: E, params?: P, credential?: string | null): Promise<SwitchCaseResponseType<E, P>>;
}
// @public (undocumented)
@ -436,6 +437,9 @@ type ApShowRequest = operations['ap/show']['requestBody']['content']['applicatio
// @public (undocumented)
type ApShowResponse = operations['ap/show']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AuthAcceptRequest = operations['auth/accept']['requestBody']['content']['application/json'];
// @public (undocumented)
type AuthSessionGenerateRequest = operations['auth/session/generate']['requestBody']['content']['application/json'];
@ -1121,6 +1125,7 @@ declare namespace entities {
AdminEmojiCopyResponse,
AdminEmojiDeleteBulkRequest,
AdminEmojiDeleteRequest,
AdminEmojiImportZipRequest,
AdminEmojiListRemoteRequest,
AdminEmojiListRemoteResponse,
AdminEmojiListRequest,
@ -1199,6 +1204,7 @@ declare namespace entities {
AppCreateResponse,
AppShowRequest,
AppShowResponse,
AuthAcceptRequest,
AuthSessionGenerateRequest,
AuthSessionGenerateResponse,
AuthSessionShowRequest,
@ -1358,13 +1364,31 @@ declare namespace entities {
HashtagsUsersRequest,
HashtagsUsersResponse,
IResponse,
I2faDoneRequest,
I2faKeyDoneRequest,
I2faPasswordLessRequest,
I2faRegisterKeyRequest,
I2faRegisterRequest,
I2faUpdateKeyRequest,
I2faRemoveKeyRequest,
I2faUnregisterRequest,
IAppsRequest,
IAuthorizedAppsRequest,
IClaimAchievementRequest,
IChangePasswordRequest,
IDeleteAccountRequest,
IExportFollowingRequest,
IFavoritesRequest,
IFavoritesResponse,
IGalleryLikesRequest,
IGalleryLikesResponse,
IGalleryPostsRequest,
IGalleryPostsResponse,
IImportBlockingRequest,
IImportFollowingRequest,
IImportMutingRequest,
IImportUserListsRequest,
IImportAntennasRequest,
INotificationsRequest,
INotificationsResponse,
INotificationsGroupedRequest,
@ -1376,6 +1400,7 @@ declare namespace entities {
IPinRequest,
IPinResponse,
IReadAnnouncementRequest,
IRegenerateTokenRequest,
IRegistryGetAllRequest,
IRegistryGetDetailRequest,
IRegistryGetRequest,
@ -1383,12 +1408,17 @@ declare namespace entities {
IRegistryKeysRequest,
IRegistryRemoveRequest,
IRegistrySetRequest,
IRevokeTokenRequest,
ISigninHistoryRequest,
ISigninHistoryResponse,
IUnpinRequest,
IUnpinResponse,
IUpdateEmailRequest,
IUpdateRequest,
IUpdateResponse,
IUserGroupInvitesRequest,
IUserGroupInvitesResponse,
IMoveRequest,
IWebhooksCreateRequest,
IWebhooksShowRequest,
IWebhooksUpdateRequest,
@ -1411,6 +1441,8 @@ declare namespace entities {
EmojisResponse,
EmojiRequest,
EmojiResponse,
MiauthGenTokenRequest,
MiauthGenTokenResponse,
MuteCreateRequest,
MuteDeleteRequest,
MuteListRequest,
@ -1476,6 +1508,7 @@ declare namespace entities {
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotificationsCreateRequest,
PagePushRequest,
PagesCreateRequest,
PagesCreateResponse,
PagesDeleteRequest,
@ -1488,6 +1521,8 @@ declare namespace entities {
FlashCreateRequest,
FlashDeleteRequest,
FlashFeaturedResponse,
FlashGenTokenRequest,
FlashGenTokenResponse,
FlashLikeRequest,
FlashShowRequest,
FlashShowResponse,
@ -1713,6 +1748,12 @@ type FlashDeleteRequest = operations['flash/delete']['requestBody']['content']['
// @public (undocumented)
type FlashFeaturedResponse = operations['flash/featured']['responses']['200']['content']['application/json'];
// @public (undocumented)
type FlashGenTokenRequest = operations['flash/gen-token']['requestBody']['content']['application/json'];
// @public (undocumented)
type FlashGenTokenResponse = operations['flash/gen-token']['responses']['200']['content']['application/json'];
// @public (undocumented)
type FlashLikeRequest = operations['flash/like']['requestBody']['content']['application/json'];
@ -1866,12 +1907,51 @@ type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['content
// @public (undocumented)
type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json'];
// @public (undocumented)
type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faRegisterKeyRequest = operations['i/2fa/register-key']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faRegisterRequest = operations['i/2fa/register']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faRemoveKeyRequest = operations['i/2fa/remove-key']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faUnregisterRequest = operations['i/2fa/unregister']['requestBody']['content']['application/json'];
// @public (undocumented)
type I2faUpdateKeyRequest = operations['i/2fa/update-key']['requestBody']['content']['application/json'];
// @public (undocumented)
type IAppsRequest = operations['i/apps']['requestBody']['content']['application/json'];
// @public (undocumented)
type IAuthorizedAppsRequest = operations['i/authorized-apps']['requestBody']['content']['application/json'];
// @public (undocumented)
type IChangePasswordRequest = operations['i/change-password']['requestBody']['content']['application/json'];
// @public (undocumented)
type IClaimAchievementRequest = operations['i/claim-achievement']['requestBody']['content']['application/json'];
// @public (undocumented)
type ID = string;
// @public (undocumented)
type IDeleteAccountRequest = operations['i/delete-account']['requestBody']['content']['application/json'];
// @public (undocumented)
type IExportFollowingRequest = operations['i/export-following']['requestBody']['content']['application/json'];
// @public (undocumented)
type IFavoritesRequest = operations['i/favorites']['requestBody']['content']['application/json'];
@ -1890,6 +1970,24 @@ type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody']['conten
// @public (undocumented)
type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json'];
// @public (undocumented)
type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
// @public (undocumented)
type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
// @public (undocumented)
type IMoveRequest = operations['i/move']['requestBody']['content']['application/json'];
// @public (undocumented)
type INotificationsGroupedRequest = operations['i/notifications-grouped']['requestBody']['content']['application/json'];
@ -1941,6 +2039,9 @@ type IPinResponse = operations['i/pin']['responses']['200']['content']['applicat
// @public (undocumented)
type IReadAnnouncementRequest = operations['i/read-announcement']['requestBody']['content']['application/json'];
// @public (undocumented)
type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json'];
// @public (undocumented)
type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json'];
@ -1965,15 +2066,27 @@ type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content'
// @public (undocumented)
type IResponse = operations['i']['responses']['200']['content']['application/json'];
// @public (undocumented)
type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json'];
// @public (undocumented)
function isAPIError(reason: any): reason is APIError;
// @public (undocumented)
type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json'];
// @public (undocumented)
type ISigninHistoryResponse = operations['i/signin-history']['responses']['200']['content']['application/json'];
// @public (undocumented)
type IUnpinRequest = operations['i/unpin']['requestBody']['content']['application/json'];
// @public (undocumented)
type IUnpinResponse = operations['i/unpin']['responses']['200']['content']['application/json'];
// @public (undocumented)
type IUpdateEmailRequest = operations['i/update-email']['requestBody']['content']['application/json'];
// @public (undocumented)
type IUpdateRequest = operations['i/update']['requestBody']['content']['application/json'];
@ -2037,6 +2150,12 @@ type MetaRequest = operations['meta']['requestBody']['content']['application/jso
// @public (undocumented)
type MetaResponse = operations['meta']['responses']['200']['content']['application/json'];
// @public (undocumented)
type MiauthGenTokenRequest = operations['miauth/gen-token']['requestBody']['content']['application/json'];
// @public (undocumented)
type MiauthGenTokenResponse = operations['miauth/gen-token']['responses']['200']['content']['application/json'];
// @public (undocumented)
type ModerationLog = {
id: ID;
@ -2378,6 +2497,9 @@ type PageEvent = {
user: User;
};
// @public (undocumented)
type PagePushRequest = operations['page-push']['requestBody']['content']['application/json'];
// @public (undocumented)
type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json'];

View file

@ -12,7 +12,7 @@
"@typescript-eslint/eslint-plugin": "6.11.0",
"@typescript-eslint/parser": "6.11.0",
"eslint": "8.53.0",
"typescript": "5.3.2",
"typescript": "5.3.3",
"tsx": "4.4.0",
"ts-case-convert": "2.0.2",
"openapi-types": "12.1.3",

View file

@ -160,6 +160,68 @@ async function generateEndpoints(
await writeFile(endpointOutputPath, endpointOutputLine.join('\n'));
}
async function generateApiClientJSDoc(
openApiDocs: OpenAPIV3.Document,
apiClientFileName: string,
endpointsFileName: string,
warningsOutputPath: string,
) {
const endpoints: { operationId: string; description: string; }[] = [];
// cherrypick-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
const paths = openApiDocs.paths;
const postPathItems = Object.keys(paths)
.map(it => paths[it]?.post)
.filter(filterUndefined);
for (const operation of postPathItems) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const operationId = operation.operationId!;
if (operation.description) {
endpoints.push({
operationId: operationId,
description: operation.description,
});
}
}
const endpointOutputLine: string[] = [];
endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
endpointOutputLine.push('');
endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
endpointOutputLine.push('');
endpointOutputLine.push(`declare module '${toImportPath(apiClientFileName)}' {`);
endpointOutputLine.push(' export interface APIClient {');
for (let i = 0; i < endpoints.length; i++) {
const endpoint = endpoints[i];
endpointOutputLine.push(
' /**',
` * ${endpoint.description.split('\n').join('\n * ')}`,
' */',
` request<E extends '${endpoint.operationId}', P extends Endpoints[E][\'req\']>(`,
' endpoint: E,',
' params: P,',
' credential?: string | null,',
' ): Promise<SwitchCaseResponseType<E, P>>;',
);
if (i < endpoints.length - 1) {
endpointOutputLine.push('\n');
}
}
endpointOutputLine.push(' }');
endpointOutputLine.push('}');
endpointOutputLine.push('');
await writeFile(warningsOutputPath, endpointOutputLine.join('\n'));
}
function isRequestBodyObject(value: unknown): value is OpenAPIV3.RequestBodyObject {
if (!value) {
return false;
@ -280,6 +342,9 @@ async function main() {
const entitiesFileName = `${generatePath}/entities.ts`;
const endpointFileName = `${generatePath}/endpoint.ts`;
await generateEndpoints(openApiDocs, typeFileName, entitiesFileName, endpointFileName);
const apiClientWarningFileName = `${generatePath}/apiClientJSDoc.ts`;
await generateApiClientJSDoc(openApiDocs, '../api.ts', endpointFileName, apiClientWarningFileName);
}
main();

View file

@ -21,19 +21,19 @@
"url": "git+https://github.com/misskey-dev/misskey.js.git"
},
"devDependencies": {
"@microsoft/api-extractor": "7.38.3",
"@microsoft/api-extractor": "7.38.5",
"@swc/jest": "0.2.29",
"@types/jest": "29.5.10",
"@types/node": "20.10.3",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"@types/jest": "29.5.11",
"@types/node": "20.10.4",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"eslint": "8.55.0",
"jest": "29.7.0",
"jest-fetch-mock": "3.0.3",
"jest-websocket-mock": "2.5.0",
"mock-socket": "9.3.1",
"tsd": "0.29.0",
"typescript": "5.3.2",
"typescript": "5.3.3",
"ncp": "2.0.0"
},
"files": [

View file

@ -1,3 +1,5 @@
import './autogen/apiClientJSDoc';
import { SwitchCaseResponseType } from './api.types';
import type { Endpoints } from './api.types';

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
/*
* version: 4.6.0-beta.2
* basedMisskeyVersion: 2023.12.0-beta.2
* generatedAt: 2023-12-06T10:16:11.711Z
* basedMisskeyVersion: 2023.12.0-beta.3
* generatedAt: 2023-12-11T13:00:13.088Z
*/
import type {
@ -49,6 +49,7 @@ import type {
AdminEmojiCopyResponse,
AdminEmojiDeleteBulkRequest,
AdminEmojiDeleteRequest,
AdminEmojiImportZipRequest,
AdminEmojiListRemoteRequest,
AdminEmojiListRemoteResponse,
AdminEmojiListRequest,
@ -127,6 +128,7 @@ import type {
AppCreateResponse,
AppShowRequest,
AppShowResponse,
AuthAcceptRequest,
AuthSessionGenerateRequest,
AuthSessionGenerateResponse,
AuthSessionShowRequest,
@ -286,13 +288,31 @@ import type {
HashtagsUsersRequest,
HashtagsUsersResponse,
IResponse,
I2faDoneRequest,
I2faKeyDoneRequest,
I2faPasswordLessRequest,
I2faRegisterKeyRequest,
I2faRegisterRequest,
I2faUpdateKeyRequest,
I2faRemoveKeyRequest,
I2faUnregisterRequest,
IAppsRequest,
IAuthorizedAppsRequest,
IClaimAchievementRequest,
IChangePasswordRequest,
IDeleteAccountRequest,
IExportFollowingRequest,
IFavoritesRequest,
IFavoritesResponse,
IGalleryLikesRequest,
IGalleryLikesResponse,
IGalleryPostsRequest,
IGalleryPostsResponse,
IImportBlockingRequest,
IImportFollowingRequest,
IImportMutingRequest,
IImportUserListsRequest,
IImportAntennasRequest,
INotificationsRequest,
INotificationsResponse,
INotificationsGroupedRequest,
@ -304,6 +324,7 @@ import type {
IPinRequest,
IPinResponse,
IReadAnnouncementRequest,
IRegenerateTokenRequest,
IRegistryGetAllRequest,
IRegistryGetDetailRequest,
IRegistryGetRequest,
@ -311,12 +332,17 @@ import type {
IRegistryKeysRequest,
IRegistryRemoveRequest,
IRegistrySetRequest,
IRevokeTokenRequest,
ISigninHistoryRequest,
ISigninHistoryResponse,
IUnpinRequest,
IUnpinResponse,
IUpdateEmailRequest,
IUpdateRequest,
IUpdateResponse,
IUserGroupInvitesRequest,
IUserGroupInvitesResponse,
IMoveRequest,
IWebhooksCreateRequest,
IWebhooksShowRequest,
IWebhooksUpdateRequest,
@ -339,6 +365,8 @@ import type {
EmojisResponse,
EmojiRequest,
EmojiResponse,
MiauthGenTokenRequest,
MiauthGenTokenResponse,
MuteCreateRequest,
MuteDeleteRequest,
MuteListRequest,
@ -404,6 +432,7 @@ import type {
NotesUserListTimelineRequest,
NotesUserListTimelineResponse,
NotificationsCreateRequest,
PagePushRequest,
PagesCreateRequest,
PagesCreateResponse,
PagesDeleteRequest,
@ -416,6 +445,8 @@ import type {
FlashCreateRequest,
FlashDeleteRequest,
FlashFeaturedResponse,
FlashGenTokenRequest,
FlashGenTokenResponse,
FlashLikeRequest,
FlashShowRequest,
FlashShowResponse,
@ -559,6 +590,7 @@ export type Endpoints = {
'admin/emoji/copy': { req: AdminEmojiCopyRequest; res: AdminEmojiCopyResponse };
'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse };
'admin/emoji/delete': { req: AdminEmojiDeleteRequest; res: EmptyResponse };
'admin/emoji/import-zip': { req: AdminEmojiImportZipRequest; res: EmptyResponse };
'admin/emoji/list-remote': { req: AdminEmojiListRemoteRequest; res: AdminEmojiListRemoteResponse };
'admin/emoji/list': { req: AdminEmojiListRequest; res: AdminEmojiListResponse };
'admin/emoji/remove-aliases-bulk': { req: AdminEmojiRemoveAliasesBulkRequest; res: EmptyResponse };
@ -618,6 +650,7 @@ export type Endpoints = {
'ap/show': { req: ApShowRequest; res: ApShowResponse };
'app/create': { req: AppCreateRequest; res: AppCreateResponse };
'app/show': { req: AppShowRequest; res: AppShowResponse };
'auth/accept': { req: AuthAcceptRequest; res: EmptyResponse };
'auth/session/generate': { req: AuthSessionGenerateRequest; res: AuthSessionGenerateResponse };
'auth/session/show': { req: AuthSessionShowRequest; res: AuthSessionShowResponse };
'auth/session/userkey': { req: AuthSessionUserkeyRequest; res: AuthSessionUserkeyResponse };
@ -681,6 +714,7 @@ export type Endpoints = {
'email-address/available': { req: EmailAddressAvailableRequest; res: EmailAddressAvailableResponse };
'endpoint': { req: EndpointRequest; res: EmptyResponse };
'endpoints': { req: EmptyRequest; res: EndpointsResponse };
'export-custom-emojis': { req: EmptyRequest; res: EmptyResponse };
'federation/followers': { req: FederationFollowersRequest; res: FederationFollowersResponse };
'federation/following': { req: FederationFollowingRequest; res: FederationFollowingResponse };
'federation/instances': { req: FederationInstancesRequest; res: FederationInstancesResponse };
@ -714,10 +748,34 @@ export type Endpoints = {
'hashtags/trend': { req: EmptyRequest; res: HashtagsTrendResponse };
'hashtags/users': { req: HashtagsUsersRequest; res: HashtagsUsersResponse };
'i': { req: EmptyRequest; res: IResponse };
'i/2fa/done': { req: I2faDoneRequest; res: EmptyResponse };
'i/2fa/key-done': { req: I2faKeyDoneRequest; res: EmptyResponse };
'i/2fa/password-less': { req: I2faPasswordLessRequest; res: EmptyResponse };
'i/2fa/register-key': { req: I2faRegisterKeyRequest; res: EmptyResponse };
'i/2fa/register': { req: I2faRegisterRequest; res: EmptyResponse };
'i/2fa/update-key': { req: I2faUpdateKeyRequest; res: EmptyResponse };
'i/2fa/remove-key': { req: I2faRemoveKeyRequest; res: EmptyResponse };
'i/2fa/unregister': { req: I2faUnregisterRequest; res: EmptyResponse };
'i/apps': { req: IAppsRequest; res: EmptyResponse };
'i/authorized-apps': { req: IAuthorizedAppsRequest; res: EmptyResponse };
'i/claim-achievement': { req: IClaimAchievementRequest; res: EmptyResponse };
'i/change-password': { req: IChangePasswordRequest; res: EmptyResponse };
'i/delete-account': { req: IDeleteAccountRequest; res: EmptyResponse };
'i/export-blocking': { req: EmptyRequest; res: EmptyResponse };
'i/export-following': { req: IExportFollowingRequest; res: EmptyResponse };
'i/export-mute': { req: EmptyRequest; res: EmptyResponse };
'i/export-notes': { req: EmptyRequest; res: EmptyResponse };
'i/export-favorites': { req: EmptyRequest; res: EmptyResponse };
'i/export-user-lists': { req: EmptyRequest; res: EmptyResponse };
'i/export-antennas': { req: EmptyRequest; res: EmptyResponse };
'i/favorites': { req: IFavoritesRequest; res: IFavoritesResponse };
'i/gallery/likes': { req: IGalleryLikesRequest; res: IGalleryLikesResponse };
'i/gallery/posts': { req: IGalleryPostsRequest; res: IGalleryPostsResponse };
'i/import-blocking': { req: IImportBlockingRequest; res: EmptyResponse };
'i/import-following': { req: IImportFollowingRequest; res: EmptyResponse };
'i/import-muting': { req: IImportMutingRequest; res: EmptyResponse };
'i/import-user-lists': { req: IImportUserListsRequest; res: EmptyResponse };
'i/import-antennas': { req: IImportAntennasRequest; res: EmptyResponse };
'i/notifications': { req: INotificationsRequest; res: INotificationsResponse };
'i/notifications-grouped': { req: INotificationsGroupedRequest; res: INotificationsGroupedResponse };
'i/page-likes': { req: IPageLikesRequest; res: IPageLikesResponse };
@ -726,16 +784,22 @@ export type Endpoints = {
'i/read-all-messaging-messages': { req: EmptyRequest; res: EmptyResponse };
'i/read-all-unread-notes': { req: EmptyRequest; res: EmptyResponse };
'i/read-announcement': { req: IReadAnnouncementRequest; res: EmptyResponse };
'i/regenerate-token': { req: IRegenerateTokenRequest; res: EmptyResponse };
'i/registry/get-all': { req: IRegistryGetAllRequest; res: EmptyResponse };
'i/registry/get-detail': { req: IRegistryGetDetailRequest; res: EmptyResponse };
'i/registry/get': { req: IRegistryGetRequest; res: EmptyResponse };
'i/registry/keys-with-type': { req: IRegistryKeysWithTypeRequest; res: EmptyResponse };
'i/registry/keys': { req: IRegistryKeysRequest; res: EmptyResponse };
'i/registry/remove': { req: IRegistryRemoveRequest; res: EmptyResponse };
'i/registry/scopes-with-domain': { req: EmptyRequest; res: EmptyResponse };
'i/registry/set': { req: IRegistrySetRequest; res: EmptyResponse };
'i/revoke-token': { req: IRevokeTokenRequest; res: EmptyResponse };
'i/signin-history': { req: ISigninHistoryRequest; res: ISigninHistoryResponse };
'i/unpin': { req: IUnpinRequest; res: IUnpinResponse };
'i/update-email': { req: IUpdateEmailRequest; res: EmptyResponse };
'i/update': { req: IUpdateRequest; res: IUpdateResponse };
'i/user-group-invites': { req: IUserGroupInvitesRequest; res: IUserGroupInvitesResponse };
'i/move': { req: IMoveRequest; res: EmptyResponse };
'i/webhooks/create': { req: IWebhooksCreateRequest; res: EmptyResponse };
'i/webhooks/list': { req: EmptyRequest; res: EmptyResponse };
'i/webhooks/show': { req: IWebhooksShowRequest; res: EmptyResponse };
@ -753,6 +817,7 @@ export type Endpoints = {
'meta': { req: MetaRequest; res: MetaResponse };
'emojis': { req: EmptyRequest; res: EmojisResponse };
'emoji': { req: EmojiRequest; res: EmojiResponse };
'miauth/gen-token': { req: MiauthGenTokenRequest; res: MiauthGenTokenResponse };
'mute/create': { req: MuteCreateRequest; res: EmptyResponse };
'mute/delete': { req: MuteDeleteRequest; res: EmptyResponse };
'mute/list': { req: MuteListRequest; res: MuteListResponse };
@ -795,6 +860,7 @@ export type Endpoints = {
'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse };
'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse };
'page-push': { req: PagePushRequest; res: EmptyResponse };
'pages/create': { req: PagesCreateRequest; res: PagesCreateResponse };
'pages/delete': { req: PagesDeleteRequest; res: EmptyResponse };
'pages/featured': { req: EmptyRequest; res: PagesFeaturedResponse };
@ -805,6 +871,7 @@ export type Endpoints = {
'flash/create': { req: FlashCreateRequest; res: EmptyResponse };
'flash/delete': { req: FlashDeleteRequest; res: EmptyResponse };
'flash/featured': { req: EmptyRequest; res: FlashFeaturedResponse };
'flash/gen-token': { req: FlashGenTokenRequest; res: FlashGenTokenResponse };
'flash/like': { req: FlashLikeRequest; res: EmptyResponse };
'flash/show': { req: FlashShowRequest; res: FlashShowResponse };
'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse };

View file

@ -1,7 +1,7 @@
/*
* version: 4.6.0-beta.2
* basedMisskeyVersion: 2023.12.0-beta.2
* generatedAt: 2023-12-06T10:16:11.709Z
* basedMisskeyVersion: 2023.12.0-beta.3
* generatedAt: 2023-12-11T13:00:13.087Z
*/
import { operations } from './types.js';
@ -51,6 +51,7 @@ export type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody'
export type AdminEmojiCopyResponse = operations['admin/emoji/copy']['responses']['200']['content']['application/json'];
export type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['requestBody']['content']['application/json'];
export type AdminEmojiDeleteRequest = operations['admin/emoji/delete']['requestBody']['content']['application/json'];
export type AdminEmojiImportZipRequest = operations['admin/emoji/import-zip']['requestBody']['content']['application/json'];
export type AdminEmojiListRemoteRequest = operations['admin/emoji/list-remote']['requestBody']['content']['application/json'];
export type AdminEmojiListRemoteResponse = operations['admin/emoji/list-remote']['responses']['200']['content']['application/json'];
export type AdminEmojiListRequest = operations['admin/emoji/list']['requestBody']['content']['application/json'];
@ -129,6 +130,7 @@ export type AppCreateRequest = operations['app/create']['requestBody']['content'
export type AppCreateResponse = operations['app/create']['responses']['200']['content']['application/json'];
export type AppShowRequest = operations['app/show']['requestBody']['content']['application/json'];
export type AppShowResponse = operations['app/show']['responses']['200']['content']['application/json'];
export type AuthAcceptRequest = operations['auth/accept']['requestBody']['content']['application/json'];
export type AuthSessionGenerateRequest = operations['auth/session/generate']['requestBody']['content']['application/json'];
export type AuthSessionGenerateResponse = operations['auth/session/generate']['responses']['200']['content']['application/json'];
export type AuthSessionShowRequest = operations['auth/session/show']['requestBody']['content']['application/json'];
@ -288,13 +290,31 @@ export type HashtagsTrendResponse = operations['hashtags/trend']['responses']['2
export type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['content']['application/json'];
export type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json'];
export type IResponse = operations['i']['responses']['200']['content']['application/json'];
export type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json'];
export type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json'];
export type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json'];
export type I2faRegisterKeyRequest = operations['i/2fa/register-key']['requestBody']['content']['application/json'];
export type I2faRegisterRequest = operations['i/2fa/register']['requestBody']['content']['application/json'];
export type I2faUpdateKeyRequest = operations['i/2fa/update-key']['requestBody']['content']['application/json'];
export type I2faRemoveKeyRequest = operations['i/2fa/remove-key']['requestBody']['content']['application/json'];
export type I2faUnregisterRequest = operations['i/2fa/unregister']['requestBody']['content']['application/json'];
export type IAppsRequest = operations['i/apps']['requestBody']['content']['application/json'];
export type IAuthorizedAppsRequest = operations['i/authorized-apps']['requestBody']['content']['application/json'];
export type IClaimAchievementRequest = operations['i/claim-achievement']['requestBody']['content']['application/json'];
export type IChangePasswordRequest = operations['i/change-password']['requestBody']['content']['application/json'];
export type IDeleteAccountRequest = operations['i/delete-account']['requestBody']['content']['application/json'];
export type IExportFollowingRequest = operations['i/export-following']['requestBody']['content']['application/json'];
export type IFavoritesRequest = operations['i/favorites']['requestBody']['content']['application/json'];
export type IFavoritesResponse = operations['i/favorites']['responses']['200']['content']['application/json'];
export type IGalleryLikesRequest = operations['i/gallery/likes']['requestBody']['content']['application/json'];
export type IGalleryLikesResponse = operations['i/gallery/likes']['responses']['200']['content']['application/json'];
export type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody']['content']['application/json'];
export type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json'];
export type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json'];
export type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json'];
export type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
export type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
export type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json'];
export type INotificationsRequest = operations['i/notifications']['requestBody']['content']['application/json'];
export type INotificationsResponse = operations['i/notifications']['responses']['200']['content']['application/json'];
export type INotificationsGroupedRequest = operations['i/notifications-grouped']['requestBody']['content']['application/json'];
@ -306,6 +326,7 @@ export type IPagesResponse = operations['i/pages']['responses']['200']['content'
export type IPinRequest = operations['i/pin']['requestBody']['content']['application/json'];
export type IPinResponse = operations['i/pin']['responses']['200']['content']['application/json'];
export type IReadAnnouncementRequest = operations['i/read-announcement']['requestBody']['content']['application/json'];
export type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json'];
export type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json'];
export type IRegistryGetDetailRequest = operations['i/registry/get-detail']['requestBody']['content']['application/json'];
export type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content']['application/json'];
@ -313,12 +334,17 @@ export type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type
export type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
export type IRegistryRemoveRequest = operations['i/registry/remove']['requestBody']['content']['application/json'];
export type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content']['application/json'];
export type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json'];
export type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json'];
export type ISigninHistoryResponse = operations['i/signin-history']['responses']['200']['content']['application/json'];
export type IUnpinRequest = operations['i/unpin']['requestBody']['content']['application/json'];
export type IUnpinResponse = operations['i/unpin']['responses']['200']['content']['application/json'];
export type IUpdateEmailRequest = operations['i/update-email']['requestBody']['content']['application/json'];
export type IUpdateRequest = operations['i/update']['requestBody']['content']['application/json'];
export type IUpdateResponse = operations['i/update']['responses']['200']['content']['application/json'];
export type IUserGroupInvitesRequest = operations['i/user-group-invites']['requestBody']['content']['application/json'];
export type IUserGroupInvitesResponse = operations['i/user-group-invites']['responses']['200']['content']['application/json'];
export type IMoveRequest = operations['i/move']['requestBody']['content']['application/json'];
export type IWebhooksCreateRequest = operations['i/webhooks/create']['requestBody']['content']['application/json'];
export type IWebhooksShowRequest = operations['i/webhooks/show']['requestBody']['content']['application/json'];
export type IWebhooksUpdateRequest = operations['i/webhooks/update']['requestBody']['content']['application/json'];
@ -341,6 +367,8 @@ export type MetaResponse = operations['meta']['responses']['200']['content']['ap
export type EmojisResponse = operations['emojis']['responses']['200']['content']['application/json'];
export type EmojiRequest = operations['emoji']['requestBody']['content']['application/json'];
export type EmojiResponse = operations['emoji']['responses']['200']['content']['application/json'];
export type MiauthGenTokenRequest = operations['miauth/gen-token']['requestBody']['content']['application/json'];
export type MiauthGenTokenResponse = operations['miauth/gen-token']['responses']['200']['content']['application/json'];
export type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
export type MuteDeleteRequest = operations['mute/delete']['requestBody']['content']['application/json'];
export type MuteListRequest = operations['mute/list']['requestBody']['content']['application/json'];
@ -406,6 +434,7 @@ export type NotesUnrenoteRequest = operations['notes/unrenote']['requestBody']['
export type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requestBody']['content']['application/json'];
export type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json'];
export type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json'];
export type PagePushRequest = operations['page-push']['requestBody']['content']['application/json'];
export type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json'];
export type PagesCreateResponse = operations['pages/create']['responses']['200']['content']['application/json'];
export type PagesDeleteRequest = operations['pages/delete']['requestBody']['content']['application/json'];
@ -418,6 +447,8 @@ export type PagesUpdateRequest = operations['pages/update']['requestBody']['cont
export type FlashCreateRequest = operations['flash/create']['requestBody']['content']['application/json'];
export type FlashDeleteRequest = operations['flash/delete']['requestBody']['content']['application/json'];
export type FlashFeaturedResponse = operations['flash/featured']['responses']['200']['content']['application/json'];
export type FlashGenTokenRequest = operations['flash/gen-token']['requestBody']['content']['application/json'];
export type FlashGenTokenResponse = operations['flash/gen-token']['responses']['200']['content']['application/json'];
export type FlashLikeRequest = operations['flash/like']['requestBody']['content']['application/json'];
export type FlashShowRequest = operations['flash/show']['requestBody']['content']['application/json'];
export type FlashShowResponse = operations['flash/show']['responses']['200']['content']['application/json'];

View file

@ -1,7 +1,7 @@
/*
* version: 4.6.0-beta.2
* basedMisskeyVersion: 2023.12.0-beta.2
* generatedAt: 2023-12-06T10:16:11.709Z
* basedMisskeyVersion: 2023.12.0-beta.3
* generatedAt: 2023-12-11T13:00:13.086Z
*/
import { components } from './types.js';

File diff suppressed because it is too large Load diff

View file

@ -70,12 +70,6 @@ module.exports = {
'require': false,
'__dirname': false,
// Vue
'$$': false,
'$ref': false,
'$shallowRef': false,
'$computed': false,
// CherryPick
'_DEV_': false,
'_LANGS_': false,

View file

@ -180,7 +180,7 @@ import './photoswipe-!~{003}~.js';
const _hoisted_1 = createBaseVNode("i", {
class: "ti ti-photo"
}, null, -1);
const _sfc_main = defineComponent({
const index_photos = defineComponent({
__name: "index.photos",
props: {
user: {}
@ -261,7 +261,6 @@ const style0 = {
const cssModules = {
"$style": style0
};
const index_photos = _sfc_main;
export {index_photos as default};
`.slice(1));
});

View file

@ -13,13 +13,13 @@ function isFalsyIdentifier(identifier: estree.Identifier): boolean {
return identifier.name === 'undefined' || identifier.name === 'NaN';
}
function normalizeClassWalker(tree: estree.Node): string | null {
function normalizeClassWalker(tree: estree.Node, stack: string | undefined): string | null {
if (tree.type === 'Identifier') return isFalsyIdentifier(tree) ? '' : null;
if (tree.type === 'Literal') return typeof tree.value === 'string' ? tree.value : '';
if (tree.type === 'BinaryExpression') {
if (tree.operator !== '+') return null;
const left = normalizeClassWalker(tree.left);
const right = normalizeClassWalker(tree.right);
const left = normalizeClassWalker(tree.left, stack);
const right = normalizeClassWalker(tree.right, stack);
if (left === null || right === null) return null;
return `${left}${right}`;
}
@ -33,15 +33,15 @@ function normalizeClassWalker(tree: estree.Node): string | null {
if (tree.type === 'ArrayExpression') {
const values = tree.elements.map((treeNode) => {
if (treeNode === null) return '';
if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument);
return normalizeClassWalker(treeNode);
if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument, stack);
return normalizeClassWalker(treeNode, stack);
});
if (values.some((x) => x === null)) return null;
return values.join(' ');
}
if (tree.type === 'ObjectExpression') {
const values = tree.properties.map((treeNode) => {
if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument);
if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument, stack);
let x = treeNode.value;
let inveted = false;
while (x.type === 'UnaryExpression' && x.operator === '!') {
@ -67,18 +67,26 @@ function normalizeClassWalker(tree: estree.Node): string | null {
if (values.some((x) => x === null)) return null;
return values.join(' ');
}
console.error(`Unexpected node type: ${tree.type}`);
if (
tree.type !== 'CallExpression' &&
tree.type !== 'ChainExpression' &&
tree.type !== 'ConditionalExpression' &&
tree.type !== 'LogicalExpression' &&
tree.type !== 'MemberExpression') {
console.error(stack ? `Unexpected node type: ${tree.type} (in ${stack})` : `Unexpected node type: ${tree.type}`);
}
return null;
}
export function normalizeClass(tree: estree.Node): string | null {
const walked = normalizeClassWalker(tree);
export function normalizeClass(tree: estree.Node, stack?: string): string | null {
const walked = normalizeClassWalker(tree, stack);
return walked && walked.replace(/^\s+|\s+(?=\s)|\s+$/g, '');
}
export function unwindCssModuleClassName(ast: estree.Node): void {
(walk as typeof estreeWalker.walk)(ast, {
enter(node, parent): void {
//#region
if (parent?.type !== 'Program') return;
if (node.type !== 'VariableDeclaration') return;
if (node.declarations.length !== 1) return;
@ -102,6 +110,14 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
return true;
});
if (!~__cssModulesIndex) return;
/* This region assumeed that the entered node looks like the following code.
*
* ```ts
* const SomeComponent = _export_sfc(_sfc_main, [["foo", bar], ["__cssModules", cssModules]]);
* ```
*/
//#endregion
//#region
const cssModuleForestName = ((node.declarations[0].init.arguments[1].elements[__cssModulesIndex] as estree.ArrayExpression).elements[1] as estree.Identifier).name;
const cssModuleForestNode = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
@ -117,6 +133,16 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
if (property.value.type !== 'Identifier') return [];
return [[property.key.value as string, property.value.name as string]];
}));
/* This region collected a VariableDeclaration node in the module that looks like the following code.
*
* ```ts
* const cssModules = {
* "$style": style0,
* };
* ```
*/
//#endregion
//#region
const sfcMain = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
@ -146,7 +172,22 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
if (ctx.type !== 'Identifier') return;
if (ctx.name !== '_ctx') return;
if (render.argument.body.type !== 'BlockStatement') return;
/* This region assumed that `sfcMain` looks like the following code.
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* };
* },
* });
* ```
*/
//#endregion
for (const [key, value] of moduleForest) {
//#region
const cssModuleTreeNode = parent.body.find((x) => {
if (x.type !== 'VariableDeclaration') return false;
if (x.declarations.length !== 1) return false;
@ -172,6 +213,19 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
if (actualValue.declarations[0].init?.type !== 'Literal') return [];
return [[actualKey, actualValue.declarations[0].init.value as string]];
}));
/* This region collected VariableDeclaration nodes in the module that looks like the following code.
*
* ```ts
* const foo = "bar";
* const baz = "qux";
* const style0 = {
* foo: foo,
* baz: baz,
* };
* ```
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
if (childNode.type !== 'MemberExpression') return;
@ -189,6 +243,39 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
});
},
});
/* This region inlined the reference identifier of the class name in the render function into the actual literal, as in the following code.
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: normalizeClass(_ctx.$style.foo),
* }, null);
* };
* },
* });
* ```
*
*
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: normalizeClass("bar"),
* }, null);
* };
* },
* });
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
if (childNode.type !== 'MemberExpression') return;
@ -205,13 +292,47 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
});
},
});
/* This region replaced the reference identifier of missing class names in the render function with `undefined`, as in the following code.
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: normalizeClass(_ctx.$style.hoge),
* }, null);
* };
* },
* });
* ```
*
*
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: normalizeClass(undefined),
* }, null);
* };
* },
* });
* ```
*/
//#endregion
//#region
(walk as typeof estreeWalker.walk)(render.argument.body, {
enter(childNode) {
if (childNode.type !== 'CallExpression') return;
if (childNode.callee.type !== 'Identifier') return;
if (childNode.callee.name !== 'normalizeClass') return;
if (childNode.arguments.length !== 1) return;
const normalized = normalizeClass(childNode.arguments[0]);
const normalized = normalizeClass(childNode.arguments[0], name);
if (normalized === null) return;
this.replace({
type: 'Literal',
@ -219,8 +340,60 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
});
},
});
/* This region compiled the `normalizeClass` call into a pseudo-AOT compilation, as in the following code.
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: normalizeClass("bar"),
* }, null);
* };
* },
* });
* ```
*
*
*
* ```ts
* const _sfc_main = defineComponent({
* setup(_props) {
* ...
* return (_ctx, _cache) => {
* ...
* return openBlock(), createElementBlock("div", {
* class: "bar",
* }, null);
* };
* },
* });
* ```
*/
//#endregion
}
//#region
if (node.declarations[0].init.arguments[1].elements.length === 1) {
(walk as typeof estreeWalker.walk)(ast, {
enter(childNode) {
if (childNode.type !== 'Identifier') return;
if (childNode.name !== ident) return;
this.replace({
type: 'Identifier',
name: node.declarations[0].id.name,
});
},
});
this.remove();
/* NOTE: The above logic is valid as long as the following two conditions are met.
*
* - the uniqueness of `ident` is kept throughout the module
* - `_export_sfc` is noop when the second argument is an empty array
*
* Otherwise, the below logic should be used instead.
this.replace({
type: 'VariableDeclaration',
declarations: [{
@ -236,6 +409,7 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
}],
kind: 'const',
});
*/
} else {
this.replace({
type: 'VariableDeclaration',
@ -263,6 +437,35 @@ export function unwindCssModuleClassName(ast: estree.Node): void {
kind: 'const',
});
}
/* This region removed the `__cssModules` reference from the second argument of `_export_sfc`, as in the following code.
*
* ```ts
* const SomeComponent = _export_sfc(_sfc_main, [["foo", bar], ["__cssModules", cssModules]]);
* ```
*
*
*
* ```ts
* const SomeComponent = _export_sfc(_sfc_main, [["foo", bar]]);
* ```
*
* When the declaration becomes noop, it is removed as follows.
*
* ```ts
* const _sfc_main = defineComponent({
* ...
* });
* const SomeComponent = _export_sfc(_sfc_main, []);
* ```
*
*
*
* ```ts
* const SomeComponent = defineComponent({
* ...
* });
*/
//#endregion
},
});
}

View file

@ -4,6 +4,7 @@
"type": "module",
"scripts": {
"watch": "vite",
"dev": "vite --config vite.config.local-dev.ts",
"build": "vite build",
"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
@ -22,11 +23,10 @@
"@rollup/plugin-alias": "5.1.0",
"@rollup/plugin-json": "6.0.1",
"@rollup/plugin-replace": "5.0.5",
"@rollup/pluginutils": "5.0.5",
"@rollup/pluginutils": "5.1.0",
"@syuilo/aiscript": "0.16.0",
"@tabler/icons-webfont": "2.37.0",
"@vitejs/plugin-vue": "4.5.1",
"@vue-macros/reactivity-transform": "0.4.0",
"@vue/compiler-sfc": "3.3.9",
"astring": "1.8.6",
"autosize": "6.0.1",
@ -35,14 +35,14 @@
"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
"buraha": "0.0.1",
"canvas-confetti": "1.6.1",
"chart.js": "4.4.0",
"chart.js": "4.4.1",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.0.1",
"cherrypick-js": "workspace:*",
"cherrypick-mfm-js": "github:kokonect-link/mfm.js",
"chromatic": "9.1.0",
"chromatic": "10.1.0",
"compare-versions": "6.1.0",
"cropperjs": "2.0.0-beta.4",
"date-fns": "2.30.0",
@ -61,9 +61,9 @@
"prismjs": "1.29.0",
"punycode": "2.3.1",
"querystring": "0.2.1",
"rollup": "4.6.1",
"rollup": "4.7.0",
"sanitize-html": "2.11.0",
"shiki": "^0.14.5",
"shiki": "0.14.6",
"sass": "1.69.5",
"strict-event-emitter-types": "2.0.0",
"temml": "0.10.13",
@ -75,41 +75,41 @@
"tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typescript": "5.3.2",
"typescript": "5.3.3",
"uuid": "9.0.1",
"v-code-diff": "1.7.2",
"vanilla-tilt": "1.8.1",
"vite": "5.0.5",
"vue": "3.3.9",
"vite": "5.0.7",
"vue": "3.3.11",
"vue-prism-editor": "2.0.0-alpha.2",
"vuedraggable": "next"
},
"devDependencies": {
"@storybook/addon-actions": "7.6.3",
"@storybook/addon-essentials": "7.6.3",
"@storybook/addon-interactions": "7.6.3",
"@storybook/addon-links": "7.6.3",
"@storybook/addon-storysource": "7.6.3",
"@storybook/addons": "7.6.3",
"@storybook/blocks": "7.6.3",
"@storybook/core-events": "7.6.3",
"@storybook/addon-actions": "7.6.4",
"@storybook/addon-essentials": "7.6.4",
"@storybook/addon-interactions": "7.6.4",
"@storybook/addon-links": "7.6.4",
"@storybook/addon-storysource": "7.6.4",
"@storybook/addons": "7.6.4",
"@storybook/blocks": "7.6.4",
"@storybook/core-events": "7.6.4",
"@storybook/jest": "0.2.3",
"@storybook/manager-api": "7.6.3",
"@storybook/preview-api": "7.6.3",
"@storybook/react": "7.6.3",
"@storybook/react-vite": "7.6.3",
"@storybook/manager-api": "7.6.4",
"@storybook/preview-api": "7.6.4",
"@storybook/react": "7.6.4",
"@storybook/react-vite": "7.6.4",
"@storybook/testing-library": "0.2.2",
"@storybook/theming": "7.6.3",
"@storybook/types": "7.6.3",
"@storybook/vue3": "7.6.3",
"@storybook/vue3-vite": "7.6.3",
"@storybook/theming": "7.6.4",
"@storybook/types": "7.6.4",
"@storybook/vue3": "7.6.4",
"@storybook/vue3-vite": "7.6.4",
"@testing-library/vue": "8.0.1",
"@types/autosize": "^4.0.1",
"@types/escape-regexp": "0.0.3",
"@types/estree": "1.0.5",
"@types/matter-js": "0.19.5",
"@types/micromatch": "4.0.6",
"@types/node": "20.10.3",
"@types/node": "20.10.4",
"@types/prismjs": "^1.26.0",
"@types/punycode": "2.1.3",
"@types/sanitize-html": "2.9.5",
@ -118,13 +118,13 @@
"@types/uuid": "9.0.7",
"@types/websocket": "1.0.10",
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "6.13.1",
"@typescript-eslint/parser": "6.13.1",
"@typescript-eslint/eslint-plugin": "6.13.2",
"@typescript-eslint/parser": "6.13.2",
"@vitest/coverage-v8": "0.34.6",
"@vue/runtime-core": "3.3.9",
"@vue/runtime-core": "3.3.11",
"acorn": "8.11.2",
"cross-env": "7.0.3",
"cypress": "13.6.0",
"cypress": "13.6.1",
"eslint": "8.55.0",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-storybook": "^0.6.13",
@ -139,13 +139,13 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"start-server-and-test": "2.0.3",
"storybook": "7.6.3",
"storybook": "7.6.4",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"summaly": "github:misskey-dev/summaly",
"vite-plugin-turbosnap": "1.0.3",
"vitest": "0.34.6",
"vitest-fetch-mock": "0.2.2",
"vue-eslint-parser": "9.3.2",
"vue-tsc": "1.8.24"
"vue-tsc": "1.8.25"
}
}

View file

@ -0,0 +1,11 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey, cherrypick contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
// devモードで起動される際index.htmlを使うときはrouterが暴発してしまってうまく読み込めない。
// よって、devモードとして起動されるときはビルド時に組み込む形としておく。
// (pnpm start時はpugファイルの中で静的リソースとして読み込むようになっており、この問題は起こっていない)
import '@tabler/icons-webfont/tabler-icons.scss';
import('@/_boot_.js');

View file

@ -201,6 +201,12 @@ export async function common(createVue: () => App<Element>) {
if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme));
if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme));
defaultStore.set('themeInitial', false);
} else {
if (defaultStore.state.darkMode) {
applyTheme(darkTheme.value);
} else {
applyTheme(lightTheme.value);
}
}
});

View file

@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
@ -56,11 +57,11 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void;
}>();
let forward = $ref(props.report.forwarded);
const forward = ref(props.report.forwarded);
function resolve() {
os.apiWithDialog('admin/resolve-abuse-user-report', {
forward: forward,
forward: forward.value,
reportId: props.report.id,
}).then(() => {
emit('resolved', props.report.id);

View file

@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
<script lang="ts" setup>
import { computed, watch } from 'vue';
import { computed, ref, watch } from 'vue';
import { PrismEditor } from 'vue-prism-editor';
import { highlight, languages } from 'prismjs/components/prism-core';
import MkInput from '@/components/MkInput.vue';
@ -75,7 +75,7 @@ const props = defineProps<{
previousExpiresAt?: string;
}
}>();
let expirationDate: Date | null = $ref(null);
const expirationDate = ref<Date | null>(null);
type NonNullType<T> = {
[P in keyof T]: NonNullable<T[P]>
@ -118,9 +118,9 @@ function highlighter(code) {
function renderExpirationDate(empty = false) {
if (value.value.expirationDate && !empty) {
expirationDate = new Date(value.value.expirationDate);
expirationDate.value = new Date(value.value.expirationDate);
} else {
expirationDate = null;
expirationDate.value = null;
}
}

View file

@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'cherrypick-js';
import { onMounted } from 'vue';
import { onMounted, ref, computed } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
@ -67,15 +67,15 @@ const props = withDefaults(defineProps<{
withDescription: true,
});
let achievements = $ref();
const lockedAchievements = $computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements ?? []).some(a => a.name === x)));
const achievements = ref();
const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
function fetch() {
os.api('users/achievements', { userId: props.user.id }).then(res => {
achievements = [];
achievements.value = [];
for (const t of ACHIEVEMENT_TYPES) {
const a = res.find(x => x.name === t);
if (a) achievements.push(a);
if (a) achievements.value.push(a);
}
//achievements = res.sort((a, b) => b.unlockedAt - a.unlockedAt);
});

View file

@ -138,45 +138,45 @@ const texts = computed(() => {
});
let enabled = true;
let majorGraduationColor = $ref<string>();
//let minorGraduationColor = $ref<string>();
let sHandColor = $ref<string>();
let mHandColor = $ref<string>();
let hHandColor = $ref<string>();
let nowColor = $ref<string>();
let h = $ref<number>(0);
let m = $ref<number>(0);
let s = $ref<number>(0);
let hAngle = $ref<number>(0);
let mAngle = $ref<number>(0);
let sAngle = $ref<number>(0);
let disableSAnimate = $ref(false);
const majorGraduationColor = ref<string>();
// const minorGraduationColor = ref<string>();
const sHandColor = ref<string>();
const mHandColor = ref<string>();
const hHandColor = ref<string>();
const nowColor = ref<string>();
const h = ref<number>(0);
const m = ref<number>(0);
const s = ref<number>(0);
const hAngle = ref<number>(0);
const mAngle = ref<number>(0);
const sAngle = ref<number>(0);
const disableSAnimate = ref(false);
let sOneRound = false;
const sLine = ref<SVGPathElement>();
function tick() {
const now = props.now();
now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset);
const previousS = s;
const previousM = m;
const previousH = h;
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
if (previousS === s && previousM === m && previousH === h) {
const previousS = s.value;
const previousM = m.value;
const previousH = h.value;
s.value = now.getSeconds();
m.value = now.getMinutes();
h.value = now.getHours();
if (previousS === s.value && previousM === m.value && previousH === h.value) {
return;
}
hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle = Math.PI * (m + s / 60) / 30;
hAngle.value = Math.PI * (h.value % (props.twentyfour ? 24 : 12) + (m.value + s.value / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle.value = Math.PI * (m.value + s.value / 60) / 30;
if (sOneRound && sLine.value) { // (59->0)
sAngle = Math.PI * 60 / 30;
sAngle.value = Math.PI * 60 / 30;
defaultIdlingRenderScheduler.delete(tick);
sLine.value.addEventListener('transitionend', () => {
disableSAnimate = true;
disableSAnimate.value = true;
requestAnimationFrame(() => {
sAngle = 0;
sAngle.value = 0;
requestAnimationFrame(() => {
disableSAnimate = false;
disableSAnimate.value = false;
if (enabled) {
defaultIdlingRenderScheduler.add(tick);
}
@ -184,9 +184,9 @@ function tick() {
});
}, { once: true });
} else {
sAngle = Math.PI * s / 30;
sAngle.value = Math.PI * s.value / 30;
}
sOneRound = s === 59;
sOneRound = s.value === 59;
}
tick();
@ -195,12 +195,12 @@ function calcColors() {
const computedStyle = getComputedStyle(document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
hHandColor = accent;
nowColor = accent;
sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
hHandColor.value = accent;
nowColor.value = accent;
}
calcColors();

View file

@ -43,6 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
fixed
:instant="true"
:initialText="c.form.text"
:initialCw="c.form.cw"
/>
</div>
<MkFolder v-else-if="c.type === 'folder'" :defaultOpen="c.opened">
@ -60,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { Ref } from 'vue';
import { Ref, ref } from 'vue';
import * as os from '@/os.js';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@ -87,16 +88,17 @@ function g(id) {
return props.components.find(x => x.value.id === id).value;
}
let valueForSwitch = $ref(c.default ?? false);
const valueForSwitch = ref(c.default ?? false);
function onSwitchUpdate(v) {
valueForSwitch = v;
valueForSwitch.value = v;
if (c.onChange) c.onChange(v);
}
function openPostForm() {
os.post({
initialText: c.form.text,
initialCw: c.form.cw,
instant: true,
});
}

View file

@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import { nextTick, onMounted, shallowRef } from 'vue';
import { vibrate } from '@/scripts/vibrate.js';
import { defaultStore } from '@/store.js';
@ -61,13 +61,13 @@ const emit = defineEmits<{
(ev: 'click', payload: MouseEvent): void;
}>();
let el = $shallowRef<HTMLElement | null>(null);
let ripples = $shallowRef<HTMLElement | null>(null);
const el = shallowRef<HTMLElement | null>(null);
const ripples = shallowRef<HTMLElement | null>(null);
onMounted(() => {
if (props.autofocus) {
nextTick(() => {
el!.focus();
el.value!.focus();
});
}
});
@ -90,11 +90,11 @@ function onMousedown(evt: MouseEvent): void {
const rect = target.getBoundingClientRect();
const ripple = document.createElement('div');
ripple.classList.add(ripples!.dataset.childrenClass!);
ripple.classList.add(ripples.value!.dataset.childrenClass!);
ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
ripples!.appendChild(ripple);
ripples.value!.appendChild(ripple);
const circleCenterX = evt.clientX - rect.left;
const circleCenterY = evt.clientY - rect.top;
@ -111,7 +111,7 @@ function onMousedown(evt: MouseEvent): void {
ripple.style.opacity = '0';
}, 1000);
window.setTimeout(() => {
if (ripples) ripples.removeChild(ripple);
if (ripples.value) ripples.value.removeChild(ripple);
}, 2000);
}
</script>

View file

@ -74,7 +74,7 @@ const props = defineProps({
},
});
let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
const negate = arr => arr.map(x => -x);
@ -268,7 +268,7 @@ const render = () => {
gradient,
},
},
plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])],
plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl.value)] : [])],
});
};

View file

@ -13,29 +13,30 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import { Chart, LegendItem } from 'chart.js';
const props = defineProps({
});
let chart = $shallowRef<Chart>();
let items = $shallowRef<LegendItem[]>([]);
const chart = shallowRef<Chart>();
const items = shallowRef<LegendItem[]>([]);
function update(_chart: Chart, _items: LegendItem[]) {
chart = _chart,
items = _items;
chart.value = _chart,
items.value = _items;
}
function onClick(item: LegendItem) {
if (chart == null) return;
const { type } = chart.config;
if (chart.value == null) return;
const { type } = chart.value.config;
if (type === 'pie' || type === 'doughnut') {
// Pie and doughnut charts only have a single dataset and visibility is per item
chart.toggleDataVisibility(item.index);
chart.value.toggleDataVisibility(item.index);
} else {
chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
chart.value.setDatasetVisibility(item.datasetIndex, !chart.value.isDatasetVisible(item.datasetIndex));
}
chart.update();
chart.value.update();
}
defineExpose({

View file

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, onMounted, onUnmounted } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
import * as os from '@/os.js';
import { useInterval } from '@/scripts/use-interval.js';
@ -29,8 +29,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
const saveData = game.saveData;
const cookies = computed(() => saveData.value?.cookies);
let cps = $ref(0);
let prevCookies = $ref(0);
const cps = ref(0);
const prevCookies = ref(0);
function onClick(ev: MouseEvent) {
const x = ev.clientX;
@ -48,9 +48,9 @@ function onClick(ev: MouseEvent) {
}
useInterval(() => {
const diff = saveData.value!.cookies - prevCookies;
cps = diff;
prevCookies = saveData.value!.cookies;
const diff = saveData.value!.cookies - prevCookies.value;
cps.value = diff;
prevCookies.value = saveData.value!.cookies;
}, 1000, {
immediate: false,
afterMounted: true,
@ -63,7 +63,7 @@ useInterval(game.save, 1000 * 5, {
onMounted(async () => {
await game.load();
prevCookies = saveData.value!.cookies;
prevCookies.value = saveData.value!.cookies;
});
onUnmounted(() => {

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.codeEditorRoot, { [$style.disabled]: disabled, [$style.focused]: focused }]">
<div :class="[$style.codeEditorRoot, { [$style.focused]: focused }]">
<div :class="$style.codeEditorScroller">
<textarea
ref="inputEl"

View file

@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onBeforeUnmount } from 'vue';
import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from './types/menu.vue';
import contains from '@/scripts/contains.js';
@ -34,9 +34,9 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let rootEl = $shallowRef<HTMLDivElement>();
const rootEl = shallowRef<HTMLDivElement>();
let zIndex = $ref<number>(os.claimZIndex('high'));
const zIndex = ref<number>(os.claimZIndex('high'));
const SCROLLBAR_THICKNESS = 16;
@ -44,8 +44,8 @@ onMounted(() => {
let left = props.ev.pageX + 1; // + 1
let top = props.ev.pageY + 1; // + 1
const width = rootEl.offsetWidth;
const height = rootEl.offsetHeight;
const width = rootEl.value.offsetWidth;
const height = rootEl.value.offsetHeight;
if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset;
@ -63,8 +63,8 @@ onMounted(() => {
left = 0;
}
rootEl.style.top = `${top}px`;
rootEl.style.left = `${left}px`;
rootEl.value.style.top = `${top}px`;
rootEl.value.style.left = `${left}px`;
document.body.addEventListener('mousedown', onMousedown);
});
@ -74,7 +74,7 @@ onBeforeUnmount(() => {
});
function onMousedown(evt: Event) {
if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
if (!contains(rootEl.value, evt.target) && (rootEl.value !== evt.target)) emit('closed');
}
</script>

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, shallowRef, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import Cropper from 'cropperjs';
import tinycolor from 'tinycolor2';
@ -56,10 +56,10 @@ const props = defineProps<{
}>();
const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
let dialogEl = $shallowRef<InstanceType<typeof MkModalWindow>>();
let imgEl = $shallowRef<HTMLImageElement>();
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
const imgEl = shallowRef<HTMLImageElement>();
let cropper: Cropper | null = null;
let loading = $ref(true);
const loading = ref(true);
const ok = async () => {
const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
@ -94,16 +94,16 @@ const ok = async () => {
const f = await promise;
emit('ok', f);
dialogEl!.close();
dialogEl.value!.close();
};
const cancel = () => {
emit('cancel');
dialogEl!.close();
dialogEl.value!.close();
};
const onImageLoad = () => {
loading = false;
loading.value = false;
if (cropper) {
cropper.getCropperImage()!.$center('contain');
@ -112,7 +112,7 @@ const onImageLoad = () => {
};
onMounted(() => {
cropper = new Cropper(imgEl!, {
cropper = new Cropper(imgEl.value!, {
});
const computedStyle = getComputedStyle(document.documentElement);

View file

@ -31,8 +31,8 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
<template #caption>
<span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
<span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
</template>
</MkInput>
<MkSelect v-if="select" v-model="selectedValue" autofocus>
@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkSelect>
<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
</div>
<div v-if="actions" :class="[$style.buttons, { [$style.changeButtonAlign]: actions.length > 2 }]">
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@ -124,24 +124,21 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
const inputValue = ref<string | number | null>(props.input?.default ?? null);
const selectedValue = ref(props.select?.default ?? null);
let disabledReason = $ref<null | 'charactersExceeded' | 'charactersBelow'>(null);
const okButtonDisabled = $computed<boolean>(() => {
const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => {
if (props.input) {
if (props.input.minLength) {
if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) {
disabledReason = 'charactersBelow';
return true;
return 'charactersBelow';
}
}
if (props.input.maxLength) {
if (inputValue.value && (inputValue.value as string).length > props.input.maxLength) {
disabledReason = 'charactersExceeded';
return true;
return 'charactersExceeded';
}
}
}
return false;
return null;
});
function done(canceled: boolean, result?) {

View file

@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
import { defaultStore } from '@/store.js';
@ -54,23 +55,23 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const modal = $shallowRef<InstanceType<typeof MkModal>>();
const picker = $shallowRef<InstanceType<typeof MkEmojiPicker>>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
function chosen(emoji: any) {
emit('done', emoji);
if (props.choseAndClose) {
modal?.close();
modal.value?.close();
}
}
function opening() {
picker?.reset();
picker?.focus();
picker.value?.reset();
picker.value?.focus();
//
setTimeout(() => {
picker?.focus();
picker.value?.focus();
}, 10);
}
</script>

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { shallowRef, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue';
@ -42,12 +42,12 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
let caption = $ref(props.default);
const caption = ref(props.default);
async function ok() {
emit('done', caption);
dialog.close();
emit('done', caption.value);
dialog.value.close();
}
</script>

View file

@ -43,7 +43,7 @@ window.addEventListener('resize', () => {
isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
});
let showEl = $ref(false);
const showEl = ref(false);
const miLocalStoragePrefix = 'ui:folder:' as const;
@ -103,7 +103,7 @@ onMounted(() => {
bg.value = _bg.toRgbString();
globalEvents.on('showEl', (showEl_receive) => {
showEl = showEl_receive;
showEl.value = showEl_receive;
});
});
</script>

View file

@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, onMounted } from 'vue';
import { nextTick, onMounted, shallowRef, ref } from 'vue';
import { defaultStore } from '@/store.js';
const props = withDefaults(defineProps<{
@ -75,10 +75,10 @@ const getBgColor = (el: HTMLElement) => {
}
};
let rootEl = $shallowRef<HTMLElement>();
let bgSame = $ref(false);
let opened = $ref(props.defaultOpen);
let openedAtLeastOnce = $ref(props.defaultOpen);
const rootEl = shallowRef<HTMLElement>();
const bgSame = ref(false);
const opened = ref(props.defaultOpen);
const openedAtLeastOnce = ref(props.defaultOpen);
function enter(el) {
const elementHeight = el.getBoundingClientRect().height;
@ -103,20 +103,20 @@ function afterLeave(el) {
}
function toggle() {
if (!opened) {
openedAtLeastOnce = true;
if (!opened.value) {
openedAtLeastOnce.value = true;
}
nextTick(() => {
opened = !opened;
opened.value = !opened.value;
});
}
onMounted(() => {
const computedStyle = getComputedStyle(document.documentElement);
const parentBg = getBgColor(rootEl.parentElement);
const parentBg = getBgColor(rootEl.value.parentElement);
const myBg = computedStyle.getPropertyValue('--panel');
bgSame = parentBg === myBg;
bgSame.value = parentBg === myBg;
});
defineExpose({

View file

@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onBeforeUnmount, onMounted } from 'vue';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import * as os from '@/os.js';
import { useStream } from '@/stream.js';
@ -49,7 +49,7 @@ import { globalEvents } from '@/events.js';
import { vibrate } from '@/scripts/vibrate.js';
import { defaultStore } from '@/store.js';
let showFollowButton = $ref(false);
const showFollowButton = ref(false);
const props = withDefaults(defineProps<{
user: Misskey.entities.UserDetailed,
@ -70,9 +70,9 @@ const emit = defineEmits<{
(_: 'update:user', value: Misskey.entities.UserDetailed): void
}>();
let isFollowing = $ref(props.user.isFollowing);
let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
let wait = $ref(false);
const isFollowing = ref(props.user.isFollowing);
const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou);
const wait = ref(false);
const connection = useStream().useChannel('main');
if (props.user.isFollowing == null) {
@ -84,16 +84,16 @@ if (props.user.isFollowing == null) {
function onFollowChange(user: Misskey.entities.UserDetailed) {
if (user.id === props.user.id) {
isFollowing = user.isFollowing;
hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
isFollowing.value = user.isFollowing;
hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
}
}
async function onClick() {
wait = true;
wait.value = true;
try {
if (isFollowing) {
if (isFollowing.value) {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.t('unfollowConfirm', { name: userName(props.user) }),
@ -105,11 +105,11 @@ async function onClick() {
userId: props.user.id,
});
} else {
if (hasPendingFollowRequestFromYou) {
if (hasPendingFollowRequestFromYou.value) {
await os.api('following/requests/cancel', {
userId: props.user.id,
});
hasPendingFollowRequestFromYou = false;
hasPendingFollowRequestFromYou.value = false;
} else {
await os.api('following/create', {
userId: props.user.id,
@ -120,7 +120,7 @@ async function onClick() {
withReplies: defaultStore.state.defaultWithReplies,
});
vibrate(defaultStore.state.vibrateSystem ? [30, 40, 100] : []);
hasPendingFollowRequestFromYou = true;
hasPendingFollowRequestFromYou.value = true;
claimAchievement('following1');
@ -141,7 +141,7 @@ async function onClick() {
} catch (err) {
console.error(err);
} finally {
wait = false;
wait.value = false;
}
}
@ -149,7 +149,7 @@ onMounted(() => {
connection.on('follow', onFollowChange);
connection.on('unfollow', onFollowChange);
showFollowButton = $i != null && $i.id !== props.user.id;
showFollowButton.value = $i != null && $i.id !== props.user.id;
globalEvents.emit('showFollowButton', showFollowButton);
});

View file

@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref } from 'vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue';
@ -53,19 +53,19 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let dialog: InstanceType<typeof MkModalWindow> = $ref();
const dialog = ref<InstanceType<typeof MkModalWindow>>();
let username = $ref('');
let email = $ref('');
let processing = $ref(false);
const username = ref('');
const email = ref('');
const processing = ref(false);
async function onSubmit() {
processing = true;
processing.value = true;
await os.apiWithDialog('request-reset-password', {
username,
email,
username: username.value,
email: email.value,
});
emit('done');
dialog.close();
dialog.value.close();
}
</script>

View file

@ -23,7 +23,7 @@ const query = ref(props.q);
const search = () => {
const sp = new URLSearchParams();
sp.append('q', query.value);
window.open(`https://www.google.com/search?${sp.toString()}`, '_blank');
window.open(`https://www.google.com/search?${sp.toString()}`, '_blank', 'noopener');
};
</script>

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick, watch } from 'vue';
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
@ -27,11 +27,11 @@ const props = defineProps<{
src: string;
}>();
const rootEl = $shallowRef<HTMLDivElement>(null);
const chartEl = $shallowRef<HTMLCanvasElement>(null);
const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date();
let chartInstance: Chart = null;
let fetching = $ref(true);
const fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({
position: 'middle',
@ -42,8 +42,8 @@ async function renderChart() {
chartInstance.destroy();
}
const wide = rootEl.offsetWidth > 700;
const narrow = rootEl.offsetWidth < 400;
const wide = rootEl.value.offsetWidth > 700;
const narrow = rootEl.value.offsetWidth < 400;
const weeks = wide ? 50 : narrow ? 10 : 25;
const chartLimit = 7 * weeks;
@ -88,7 +88,7 @@ async function renderChart() {
values = raw.deliverFailed;
}
fetching = false;
fetching.value = false;
await nextTick();
@ -101,7 +101,7 @@ async function renderChart() {
const marginEachCell = 4;
chartInstance = new Chart(chartEl, {
chartInstance = new Chart(chartEl.value, {
type: 'matrix',
data: {
datasets: [{
@ -210,7 +210,7 @@ async function renderChart() {
}
watch(() => props.src, () => {
fetching = true;
fetching.value = true;
renderChart();
});

View file

@ -21,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { $ref } from 'vue/macros';
import DrawBlurhash from '@/workers/draw-blurhash?worker';
import TestWebGL2 from '@/workers/test-webgl2?worker';
import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js';
@ -58,7 +57,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
</script>
<script lang="ts" setup>
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import { render } from 'buraha';
import { defaultStore } from '@/store.js';
@ -100,41 +99,41 @@ const viewId = uuid();
const canvas = shallowRef<HTMLCanvasElement>();
const root = shallowRef<HTMLDivElement>();
const img = shallowRef<HTMLImageElement>();
let loaded = $ref(false);
let canvasWidth = $ref(64);
let canvasHeight = $ref(64);
let imgWidth = $ref(props.width);
let imgHeight = $ref(props.height);
let bitmapTmp = $ref<CanvasImageSource | undefined>();
const hide = computed(() => !loaded || props.forceBlurhash);
const loaded = ref(false);
const canvasWidth = ref(64);
const canvasHeight = ref(64);
const imgWidth = ref(props.width);
const imgHeight = ref(props.height);
const bitmapTmp = ref<CanvasImageSource | undefined>();
const hide = computed(() => !loaded.value || props.forceBlurhash);
function waitForDecode() {
if (props.src != null && props.src !== '') {
nextTick()
.then(() => img.value?.decode())
.then(() => {
loaded = true;
loaded.value = true;
}, error => {
console.log('Error occurred during decoding image', img.value, error);
});
} else {
loaded = false;
loaded.value = false;
}
}
watch([() => props.width, () => props.height, root], () => {
const ratio = props.width / props.height;
if (ratio > 1) {
canvasWidth = Math.round(64 * ratio);
canvasHeight = 64;
canvasWidth.value = Math.round(64 * ratio);
canvasHeight.value = 64;
} else {
canvasWidth = 64;
canvasHeight = Math.round(64 / ratio);
canvasWidth.value = 64;
canvasHeight.value = Math.round(64 / ratio);
}
const clientWidth = root.value?.clientWidth ?? 300;
imgWidth = clientWidth;
imgHeight = Math.round(clientWidth / ratio);
imgWidth.value = clientWidth;
imgHeight.value = Math.round(clientWidth / ratio);
}, {
immediate: true,
});
@ -142,15 +141,15 @@ watch([() => props.width, () => props.height, root], () => {
function drawImage(bitmap: CanvasImageSource) {
// canvasmountedTmp
if (!canvas.value) {
bitmapTmp = bitmap;
bitmapTmp.value = bitmap;
return;
}
// canvas
bitmapTmp = undefined;
bitmapTmp.value = undefined;
const ctx = canvas.value.getContext('2d');
if (!ctx) return;
ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
ctx.drawImage(bitmap, 0, 0, canvasWidth.value, canvasHeight.value);
}
function drawAvg() {
@ -162,7 +161,7 @@ function drawAvg() {
// avgColor
ctx.beginPath();
ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value);
}
async function draw() {
@ -214,8 +213,8 @@ watch(() => props.hash, () => {
onMounted(() => {
// drawImagemounted
if (bitmapTmp) {
drawImage(bitmapTmp);
if (bitmapTmp.value) {
drawImage(bitmapTmp.value);
}
waitForDecode();
});

View file

@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkMiniChart from '@/components/MkMiniChart.vue';
import * as os from '@/os.js';
@ -24,12 +25,12 @@ const props = defineProps<{
instance: Misskey.entities.FederationInstance;
}>();
let chartValues = $ref<number[] | null>(null);
const chartValues = ref<number[] | null>(null);
os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
//
res['requests.received'].splice(0, 1);
chartValues = res['requests.received'];
chartValues.value = res['requests.received'];
});
function getInstanceIcon(instance): string {

View file

@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref, shallowRef } from 'vue';
import { Chart } from 'chart.js';
import MkSelect from '@/components/MkSelect.vue';
import MkChart from '@/components/MkChart.vue';
@ -100,11 +100,11 @@ import { initChart } from '@/scripts/init-chart.js';
initChart();
const chartLimit = 500;
let chartSpan = $ref<'hour' | 'day'>('hour');
let chartSrc = $ref('active-users');
let heatmapSrc = $ref('active-users');
let subDoughnutEl = $shallowRef<HTMLCanvasElement>();
let pubDoughnutEl = $shallowRef<HTMLCanvasElement>();
const chartSpan = ref<'hour' | 'day'>('hour');
const chartSrc = ref('active-users');
const heatmapSrc = ref('active-users');
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
const { handler: externalTooltipHandler1 } = useChartTooltip({
position: 'middle',
@ -163,7 +163,7 @@ function createDoughnut(chartEl, tooltip, data) {
onMounted(() => {
os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followersCount,
@ -172,7 +172,7 @@ onMounted(() => {
},
})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
createDoughnut(pubDoughnutEl, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
name: x.host,
color: x.themeColor,
value: x.followingCount,

View file

@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
import { instanceName } from '@/config.js';
import { instance as Instance } from '@/instance.js';
import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
@ -30,7 +30,7 @@ const instance = props.instance ?? {
themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
};
const faviconUrl = $computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico');
const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico');
const themeColor = instance.themeColor ?? '#777777';

View file

@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import { navbarItemDef } from '@/navbar.js';
import { defaultStore } from '@/store.js';
@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
deviceKind === 'smartphone' ? 'drawer' :
'dialog';
const modal = $shallowRef<InstanceType<typeof MkModal>>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const menu = defaultStore.state.menu;
@ -63,7 +63,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k =>
}));
function close() {
modal.close();
modal.value.close();
}
</script>

View file

@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<component
:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel" :target="target"
:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
:title="url"
@click.stop
>
@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import { url as local } from '@/config.js';
import { useTooltip } from '@/scripts/use-tooltip.js';
import * as os from '@/os.js';
@ -30,13 +30,13 @@ const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
const el = $ref();
const el = ref();
useTooltip($$(el), (showing) => {
useTooltip(el, (showing) => {
os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
showing,
url: props.url,
source: el,
source: el.value,
}, {}, 'closed');
});
</script>

View file

@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef, watch } from 'vue';
import { shallowRef, watch, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import { i18n } from '@/i18n.js';
@ -42,7 +42,7 @@ const props = withDefaults(defineProps<{
});
const audioEl = shallowRef<HTMLAudioElement>();
let hide = $ref(true);
const hide = ref(true);
watch(audioEl, () => {
if (audioEl.value) {

View file

@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted, watch } from 'vue';
import { onMounted, onUnmounted, watch, ref, computed } from 'vue';
import * as Misskey from 'cherrypick-js';
import { getStaticImageUrl } from '@/scripts/media-proxy.js';
import bytes from '@/filters/bytes.js';
@ -78,20 +78,20 @@ const props = withDefaults(defineProps<{
controls: true,
});
let hide = $ref(true);
let darkMode: boolean = $ref(defaultStore.state.darkMode);
const hide = ref(true);
const darkMode = ref<boolean>(defaultStore.state.darkMode);
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
const playAnimation = ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation.value = false;
let playAnimationTimer = setTimeout(() => playAnimation.value = false, 5000);
const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
? props.image.url
: (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.media) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
: (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.media) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation.value)
? getStaticImageUrl(props.image.url)
: props.image.thumbnailUrl,
);
let clickToShowMessage = $computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
const clickToShowMessage = computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
? i18n.ts.clickToShow
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
: defaultStore.state.nsfwOpenBehavior === 'doubleClick'
@ -103,12 +103,12 @@ function onClick(ev: MouseEvent) {
if (!props.controls) {
return;
}
if (!hide) return;
if (!hide.value) return;
if (defaultStore.state.nsfwOpenBehavior === 'doubleClick') {
os.popup(MkRippleEffect, { x: ev.clientX, y: ev.clientY }, {}, 'end');
}
if (defaultStore.state.nsfwOpenBehavior === 'click') {
hide = false;
hide.value = false;
}
}
@ -116,20 +116,20 @@ function onDblclick() {
if (!props.controls) {
return;
}
if (hide && defaultStore.state.nsfwOpenBehavior === 'doubleClick') {
hide = false;
if (hide.value && defaultStore.state.nsfwOpenBehavior === 'doubleClick') {
hide.value = false;
}
}
function resetTimer() {
playAnimation = true;
playAnimation.value = true;
clearTimeout(playAnimationTimer);
playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
playAnimationTimer = setTimeout(() => playAnimation.value = false, 5000);
}
// Plugin:register_note_view_interruptor 使watch
watch(() => props.image, () => {
hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
}, {
deep: true,
immediate: true,
@ -140,7 +140,7 @@ function showMenu(ev: MouseEvent) {
text: i18n.ts.hide,
icon: 'ti ti-eye-off',
action: () => {
hide = true;
hide.value = true;
},
}, ...(iAmModerator ? [{
text: i18n.ts.markAsSensitive,

View file

@ -63,7 +63,7 @@ async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLE
</script>
<script lang="ts" setup>
import { onMounted, onUnmounted, shallowRef } from 'vue';
import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
import * as Misskey from 'cherrypick-js';
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from 'photoswipe';
@ -86,7 +86,7 @@ const container = shallowRef<HTMLElement | null | undefined>(undefined);
const gallery = shallowRef<HTMLDivElement>();
const pswpZIndex = os.claimZIndex('middle');
document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
const count = $computed(() => props.mediaList.filter(media => previewable(media)).length);
const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
let lightbox: PhotoSwipeLightbox | null;
const popstateHandler = (): void => {

View file

@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, shallowRef, watch } from 'vue';
import { computed, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'cherrypick-js';
import bytes from '@/filters/bytes.js';
import { defaultStore } from '@/store.js';
@ -47,7 +47,7 @@ const props = defineProps<{
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
let clickToShowMessage = $computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
const clickToShowMessage = computed(() => defaultStore.state.nsfwOpenBehavior === 'click'
? i18n.ts.clickToShow
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
: defaultStore.state.nsfwOpenBehavior === 'doubleClick'

View file

@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { toUnicode } from 'punycode';
import { computed, onMounted, onUnmounted } from 'vue';
import { computed, onMounted, onUnmounted, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { host as localHost } from '@/config.js';
import { $i } from '@/account.js';
@ -48,18 +48,18 @@ const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue
bg.setAlpha(0.1);
const bgCss = bg.toRgbString();
let playAnimation = $ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation = false;
let playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
const avatarUrl = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation)
const playAnimation = ref(true);
if (defaultStore.state.showingAnimatedImages === 'interaction') playAnimation.value = false;
let playAnimationTimer = setTimeout(() => playAnimation.value = false, 5000);
const avatarUrl = computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) || (['interaction', 'inactive'].includes(<string>defaultStore.state.showingAnimatedImages) && !playAnimation.value)
? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`)
: `/avatar/@${props.username}@${props.host}`,
);
function resetTimer() {
playAnimation = true;
playAnimation.value = true;
clearTimeout(playAnimationTimer);
playAnimationTimer = setTimeout(() => playAnimation = false, 5000);
playAnimationTimer = setTimeout(() => playAnimation.value = false, 5000);
}
onMounted(() => {

View file

@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { Ref, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
import { Ref, computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
import { focusPrev, focusNext } from '@/scripts/focus.js';
import MkSwitchButton from '@/components/MkSwitch.button.vue';
import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
@ -91,19 +91,19 @@ const emit = defineEmits<{
(ev: 'hide'): void;
}>();
let itemsEl = $shallowRef<HTMLDivElement>();
const itemsEl = shallowRef<HTMLDivElement>();
let items2: InnerMenuItem[] = $ref([]);
const items2 = ref<InnerMenuItem[]>([]);
let child = $shallowRef<InstanceType<typeof XChild>>();
const child = shallowRef<InstanceType<typeof XChild>>();
let keymap = $computed(() => ({
const keymap = computed(() => ({
'up|k|shift+tab': focusUp,
'down|j|tab': focusDown,
'esc': close,
}));
let childShowingItem = $ref<MenuItem | null>();
const childShowingItem = ref<MenuItem | null>();
let preferClick = isTouchUsing || props.asDrawer;
@ -116,22 +116,22 @@ watch(() => props.items, () => {
if (item && 'then' in item) { // if item is Promise
items[i] = { type: 'pending' };
item.then(actualItem => {
items2[i] = actualItem;
items2.value[i] = actualItem;
});
}
}
items2 = items as InnerMenuItem[];
items2.value = items as InnerMenuItem[];
}, {
immediate: true,
});
const childMenu = ref<MenuItem[] | null>();
let childTarget = $shallowRef<HTMLElement | null>();
const childTarget = shallowRef<HTMLElement | null>();
function closeChild() {
childMenu.value = null;
childShowingItem = null;
childShowingItem.value = null;
}
function childActioned() {
@ -140,8 +140,8 @@ function childActioned() {
}
const onGlobalMousedown = (event: MouseEvent) => {
if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return;
if (child && child.checkHit(event)) return;
if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return;
if (child.value && child.value.checkHit(event)) return;
closeChild();
};
@ -178,10 +178,10 @@ async function showChildren(item: MenuParent, ev: MouseEvent) {
});
emit('hide');
} else {
childTarget = ev.currentTarget ?? ev.target;
childTarget.value = ev.currentTarget ?? ev.target;
//
childMenu.value = children;
childShowingItem = item;
childShowingItem.value = item;
}
}
@ -210,7 +210,7 @@ function switchItem(item: MenuSwitch & { ref: any }) {
onMounted(() => {
if (props.viaKeyboard) {
nextTick(() => {
if (itemsEl) focusNext(itemsEl.children[0], true, false);
if (itemsEl.value) focusNext(itemsEl.value.children[0], true, false);
});
}

View file

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import { watch, ref } from 'vue';
import { v4 as uuid } from 'uuid';
import tinycolor from 'tinycolor2';
import { useInterval } from '@/scripts/use-interval.js';
@ -43,11 +43,11 @@ const props = defineProps<{
const viewBoxX = 50;
const viewBoxY = 50;
const gradientId = uuid();
let polylinePoints = $ref('');
let polygonPoints = $ref('');
let headX = $ref<number | null>(null);
let headY = $ref<number | null>(null);
let clock = $ref<number | null>(null);
const polylinePoints = ref('');
const polygonPoints = ref('');
const headX = ref<number | null>(null);
const headY = ref<number | null>(null);
const clock = ref<number | null>(null);
const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
const color = accent.toRgbString();
@ -60,12 +60,12 @@ function draw(): void {
(1 - (n / peak)) * viewBoxY,
]);
polylinePoints = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
polylinePoints.value = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
polygonPoints = `0,${ viewBoxY } ${ polylinePoints } ${ viewBoxX },${ viewBoxY }`;
polygonPoints.value = `0,${ viewBoxY } ${ polylinePoints.value } ${ viewBoxX },${ viewBoxY }`;
headX = _polylinePoints.at(-1)![0];
headY = _polylinePoints.at(-1)![1];
headX.value = _polylinePoints.at(-1)![0];
headY.value = _polylinePoints.at(-1)![1];
}
watch(() => props.src, draw, { immediate: true });

View file

@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue';
import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue';
import * as os from '@/os.js';
import { isTouchUsing } from '@/scripts/touch.js';
import { defaultStore } from '@/store.js';
@ -89,14 +89,14 @@ const emit = defineEmits<{
provide('modal', true);
let maxHeight = $ref<number>();
let fixed = $ref(false);
let transformOrigin = $ref('center');
let showing = $ref(true);
let content = $shallowRef<HTMLElement>();
const maxHeight = ref<number>();
const fixed = ref(false);
const transformOrigin = ref('center');
const showing = ref(true);
const content = shallowRef<HTMLElement>();
const zIndex = os.claimZIndex(props.zPriority);
let useSendAnime = $ref(false);
const type = $computed<ModalTypes>(() => {
const useSendAnime = ref(false);
const type = computed<ModalTypes>(() => {
if (props.preferType === 'auto') {
if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
return 'drawer';
@ -107,26 +107,26 @@ const type = $computed<ModalTypes>(() => {
return props.preferType!;
}
});
const isEnableBgTransparent = $computed(() => props.transparentBg && (type === 'popup'));
let transitionName = $computed((() =>
const isEnableBgTransparent = computed(() => props.transparentBg && (type.value === 'popup'));
const transitionName = computed((() =>
defaultStore.state.animation
? useSendAnime
? useSendAnime.value
? 'send'
: type === 'drawer'
: type.value === 'drawer'
? 'modal-drawer'
: type === 'popup'
: type.value === 'popup'
? 'modal-popup'
: 'modal'
: ''
));
let transitionDuration = $computed((() =>
transitionName === 'send'
const transitionDuration = computed((() =>
transitionName.value === 'send'
? 400
: transitionName === 'modal-popup'
: transitionName.value === 'modal-popup'
? 100
: transitionName === 'modal'
: transitionName.value === 'modal'
? 200
: transitionName === 'modal-drawer'
: transitionName.value === 'modal-drawer'
? 200
: 0
));
@ -135,12 +135,12 @@ let contentClicking = false;
function close(opts: { useSendAnimation?: boolean } = {}) {
if (opts.useSendAnimation) {
useSendAnime = true;
useSendAnime.value = true;
}
// eslint-disable-next-line vue/no-mutating-props
if (props.src) props.src.style.pointerEvents = 'auto';
showing = false;
showing.value = false;
emit('close');
}
@ -149,8 +149,8 @@ function onBgClick() {
emit('click');
}
if (type === 'drawer') {
maxHeight = window.innerHeight / 1.5;
if (type.value === 'drawer') {
maxHeight.value = window.innerHeight / 1.5;
}
const keymap = {
@ -162,21 +162,21 @@ const SCROLLBAR_THICKNESS = 16;
const align = () => {
if (props.src == null) return;
if (type === 'drawer') return;
if (type === 'dialog') return;
if (type.value === 'drawer') return;
if (type.value === 'dialog') return;
if (content == null) return;
if (content.value == null) return;
const srcRect = props.src.getBoundingClientRect();
const width = content!.offsetWidth;
const height = content!.offsetHeight;
const width = content.value!.offsetWidth;
const height = content.value!.offsetHeight;
let left;
let top;
const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
if (props.anchor.x === 'center') {
left = x + (props.src.offsetWidth / 2) - (width / 2);
@ -194,7 +194,7 @@ const align = () => {
top = y + props.src.offsetHeight;
}
if (fixed) {
if (fixed.value) {
//
if (left + width > (window.innerWidth - SCROLLBAR_THICKNESS)) {
left = (window.innerWidth - SCROLLBAR_THICKNESS) - width;
@ -207,16 +207,16 @@ const align = () => {
if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = (upperSpace + MARGIN) - height;
}
} else {
top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
} else {
//
@ -231,16 +231,16 @@ const align = () => {
if (top + height - window.pageYOffset > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
if (props.noOverlap && props.anchor.x === 'center') {
if (underSpace >= (upperSpace / 3)) {
maxHeight = underSpace;
maxHeight.value = underSpace;
} else {
maxHeight = upperSpace;
maxHeight.value = upperSpace;
top = window.pageYOffset + ((upperSpace + MARGIN) - height);
}
} else {
top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.pageYOffset - 1;
}
} else {
maxHeight = underSpace;
maxHeight.value = underSpace;
}
}
@ -255,29 +255,29 @@ const align = () => {
let transformOriginX = 'center';
let transformOriginY = 'center';
if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) {
if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) {
transformOriginY = 'top';
} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) {
transformOriginY = 'bottom';
}
if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) {
if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) {
transformOriginX = 'left';
} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) {
} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) {
transformOriginX = 'right';
}
transformOrigin = `${transformOriginX} ${transformOriginY}`;
transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
content.style.left = left + 'px';
content.style.top = top + 'px';
content.value.style.left = left + 'px';
content.value.style.top = top + 'px';
};
const onOpened = () => {
emit('opened');
//
const el = content!.children[0];
const el = content.value!.children[0];
el.addEventListener('mousedown', ev => {
contentClicking = true;
window.addEventListener('mouseup', ev => {
@ -299,7 +299,7 @@ onMounted(() => {
// eslint-disable-next-line vue/no-mutating-props
props.src.style.pointerEvents = 'none';
}
fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
await nextTick();
@ -307,7 +307,7 @@ onMounted(() => {
}, { immediate: true });
nextTick(() => {
alignObserver.observe(content!);
alignObserver.observe(content.value!);
});
});

View file

@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import MkModal from './MkModal.vue';
const props = withDefaults(defineProps<{
@ -44,14 +44,14 @@ const emit = defineEmits<{
(event: 'ok'): void;
}>();
let modal = $shallowRef<InstanceType<typeof MkModal>>();
let rootEl = $shallowRef<HTMLElement>();
let headerEl = $shallowRef<HTMLElement>();
let bodyWidth = $ref(0);
let bodyHeight = $ref(0);
const modal = shallowRef<InstanceType<typeof MkModal>>();
const rootEl = shallowRef<HTMLElement>();
const headerEl = shallowRef<HTMLElement>();
const bodyWidth = ref(0);
const bodyHeight = ref(0);
const close = () => {
modal.close();
modal.value.close();
};
const onBgClick = () => {
@ -67,14 +67,14 @@ const onKeydown = (evt) => {
};
const ro = new ResizeObserver((entries, observer) => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
bodyWidth.value = rootEl.value.offsetWidth;
bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
});
onMounted(() => {
bodyWidth = rootEl.offsetWidth;
bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
ro.observe(rootEl);
bodyWidth.value = rootEl.value.offsetWidth;
bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
ro.observe(rootEl.value);
});
onUnmounted(() => {

View file

@ -241,7 +241,7 @@ import { concat } from '@/scripts/array.js';
import { vibrate } from '@/scripts/vibrate.js';
import detectLanguage from '@/scripts/detect-language.js';
let showEl = $ref(false);
const showEl = ref(false);
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@ -263,12 +263,12 @@ const emit = defineEmits<{
const inChannel = inject('inChannel', null);
const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
let note = $ref(deepClone(props.note));
const note = ref(deepClone(props.note));
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result: Misskey.entities.Note | null = deepClone(note);
let result: Misskey.entities.Note | null = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
try {
result = await interruptor.handler(result);
@ -280,15 +280,15 @@ if (noteViewInterruptors.length > 0) {
console.error(err);
}
}
note = result;
note.value = result;
});
}
const isRenote = (
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null
);
const el = shallowRef<HTMLElement>();
@ -298,29 +298,29 @@ const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const heartReactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
const urls = $computed(() => parsed ? extractUrlFromMfm(parsed) : null);
const isLong = shouldCollapsed(appearNote, urls ?? []);
const isMFM = shouldMfmCollapsed(appearNote);
const collapsed = ref(appearNote.cw == null && (isLong || (isMFM && defaultStore.state.collapseDefault) || (appearNote.files.length > 0 && defaultStore.state.allMediaNoteCollapse)));
const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
const isMFM = shouldMfmCollapsed(appearNote.value);
const collapsed = ref(appearNote.value.cw == null && (isLong || (isMFM && defaultStore.state.collapseDefault) || (appearNote.value.files.length > 0 && defaultStore.state.allMediaNoteCollapse)));
const isDeleted = ref(false);
const muted = ref(checkMute(appearNote, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote, $i?.hardMutedWords));
const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
const translation = ref<any>(null);
const translating = ref(false);
const viewTextSource = ref(false);
const noNyaize = ref(false);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id));
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const router = useRouter();
let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null)));
const collapseLabel = computed(() => {
return concat([
appearNote.files && appearNote.files.length !== 0 ? [i18n.t('_cw.files', { count: appearNote.files.length })] : [],
appearNote.value.files && appearNote.value.files.length !== 0 ? [i18n.t('_cw.files', { count: appearNote.value.files.length })] : [],
] as string[][]).join(' / ');
});
@ -346,26 +346,26 @@ const keymap = {
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
});
onMounted(() => {
globalEvents.on('showEl', (showEl_receive) => {
showEl = showEl_receive;
showEl.value = showEl_receive;
});
});
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);
note.value = deepClone(to);
}, { deep: true });
} else {
useNoteCapture({
rootEl: el,
note: $$(appearNote),
pureNote: $$(note),
note: appearNote,
pureNote: note,
isDeletedRef: isDeleted,
});
}
@ -373,7 +373,7 @@ if (props.mock) {
if (!props.mock) {
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
noteId: appearNote.id,
noteId: appearNote.value.id,
limit: 11,
});
@ -384,7 +384,7 @@ if (!props.mock) {
os.popup(MkUsersTooltip, {
showing,
users,
count: appearNote.renoteCount,
count: appearNote.value.renoteCount,
targetElement: renoteButton.value,
}, {}, 'closed');
});
@ -392,14 +392,14 @@ if (!props.mock) {
function noteClick(ev: MouseEvent) {
if (document.getSelection().type === 'Range' || !expandOnNoteClick) ev.stopPropagation();
else router.push(notePage(appearNote));
else router.push(notePage(appearNote.value));
}
function renote(viaKeyboard = false) {
pleaseLogin();
showMovedDialog();
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
os.popupMenu(menu, renoteButton.value, {
viaKeyboard,
});
@ -409,7 +409,7 @@ async function renoteOnly() {
pleaseLogin();
showMovedDialog();
await getRenoteOnly({ note: note, renoteButton, mock: props.mock });
await getRenoteOnly({ note: note.value, renoteButton, mock: props.mock });
}
function quote(viaKeyboard = false): void {
@ -417,17 +417,17 @@ function quote(viaKeyboard = false): void {
if (props.mock) {
return;
}
if (appearNote.channel) {
if (appearNote.value.channel) {
os.post({
renote: appearNote,
channel: appearNote.channel,
renote: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
os.post({
renote: appearNote,
renote: appearNote.value,
}, () => {
focus();
});
@ -439,8 +439,8 @@ function reply(viaKeyboard = false): void {
return;
}
os.post({
reply: appearNote,
channel: appearNote.channel,
reply: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
@ -450,14 +450,14 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
if (appearNote.reactionAcceptance === 'likeOnly') {
if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.play('reaction');
if (props.mock) {
return;
}
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: '❤️',
});
const el = reactButton.value as HTMLElement | null | undefined;
@ -482,7 +482,7 @@ function react(viaKeyboard = false): void {
}
async function toggleReaction(reaction) {
const oldReaction = note.myReaction;
const oldReaction = note.value.myReaction;
if (oldReaction) {
const confirm = await os.confirm({
type: 'warning',
@ -493,11 +493,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/delete', {
noteId: note.id,
noteId: note.value.id,
}).then(() => {
if (oldReaction !== reaction) {
os.api('notes/reactions/create', {
noteId: note.id,
noteId: note.value.id,
reaction: reaction,
});
}
@ -506,11 +506,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
}
if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
}
@ -526,10 +526,10 @@ function heartReact(): void {
}
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: '❤️',
});
if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
const el = heartReactButton.value as HTMLElement | null | undefined;
@ -575,7 +575,7 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
os.contextMenu(menu, ev).then(focus).finally(cleanup);
}
}
@ -585,7 +585,7 @@ function menu(viaKeyboard = false): void {
return;
}
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
os.popupMenu(menu, menuButton.value, {
viaKeyboard,
}).then(focus).finally(cleanup);
@ -596,12 +596,12 @@ async function clip() {
return;
}
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
}
const isForeignLanguage: boolean = appearNote.text != null && (() => {
const isForeignLanguage: boolean = appearNote.value.text != null && (() => {
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
const postLang = detectLanguage(appearNote.text);
const postLang = detectLanguage(appearNote.value.text);
return postLang !== '' && postLang !== targetLang;
})();
@ -616,7 +616,7 @@ async function translate(): Promise<void> {
}
const res = await os.api('notes/translate', {
noteId: appearNote.id,
noteId: appearNote.value.id,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
translating.value = false;
@ -637,7 +637,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -647,7 +647,7 @@ function showRenoteMenu(viaKeyboard = false): void {
if (isMyRenote) {
pleaseLogin();
os.popupMenu([
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
null,
getUnrenote(),
], renoteTime.value, {
@ -655,9 +655,9 @@ function showRenoteMenu(viaKeyboard = false): void {
});
} else {
os.popupMenu([
getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
null,
getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
], renoteTime.value, {
viaKeyboard: viaKeyboard,
@ -683,7 +683,7 @@ function focusAfter() {
function readPromo() {
os.api('promo/read', {
noteId: appearNote.id,
noteId: appearNote.value.id,
});
isDeleted.value = true;
}

View file

@ -195,7 +195,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'history' }]" @click="tab = 'history'"><i class="ti ti-pencil"></i> {{ i18n.ts.edited }}</button>
</div>
<div>
<div v-if="tab === 'replies'" :class="$style.tab_replies">
<div v-if="tab === 'replies'">
<MkPostForm v-if="!note.isHidden && !isMobile && defaultStore.state.showFixedPostFormInReplies" class="post-form _panel" fixed :reply="appearNote"></MkPostForm>
<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
<div v-if="replies.length > 2 && !repliesLoaded" style="padding: 16px">
@ -326,12 +326,12 @@ const props = defineProps<{
const inChannel = inject('inChannel', null);
let note = $ref(deepClone(props.note));
const note = ref(deepClone(props.note));
// plugin
if (noteViewInterruptors.length > 0) {
onMounted(async () => {
let result: Misskey.entities.Note | null = deepClone(note);
let result: Misskey.entities.Note | null = deepClone(note.value);
for (const interruptor of noteViewInterruptors) {
try {
result = await interruptor.handler(result);
@ -343,15 +343,15 @@ if (noteViewInterruptors.length > 0) {
console.error(err);
}
}
note = result;
note.value = result;
});
}
const isRenote = (
note.renote != null &&
note.text == null &&
note.fileIds.length === 0 &&
note.poll == null
note.value.renote != null &&
note.value.text == null &&
note.value.fileIds.length === 0 &&
note.value.poll == null
);
const el = shallowRef<HTMLElement>();
@ -361,21 +361,21 @@ const renoteTime = shallowRef<HTMLElement>();
const reactButton = shallowRef<HTMLElement>();
const heartReactButton = shallowRef<HTMLElement>();
const clipButton = shallowRef<HTMLElement>();
let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
const isMyRenote = $i && ($i.id === note.userId);
const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
const isMyRenote = $i && ($i.id === note.value.userId);
const showContent = ref(false);
const isDeleted = ref(false);
const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
const translation = ref(null);
const translating = ref(false);
const viewTextSource = ref(false);
const noNyaize = ref(false);
const parsed = appearNote.text ? mfm.parse(appearNote.text) : null;
const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
const urls = parsed ? extractUrlFromMfm(parsed) : null;
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
const conversation = ref<Misskey.entities.Note[]>([]);
const replies = ref<Misskey.entities.Note[]>([]);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i.id);
const keymap = {
'r': () => reply(true),
@ -388,41 +388,41 @@ const keymap = {
provide('react', (reaction: string) => {
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
});
let tab = $ref('replies');
let reactionTabType = $ref(null);
const tab = ref('replies');
const reactionTabType = ref(null);
const renotesPagination = $computed(() => ({
const renotesPagination = computed(() => ({
endpoint: 'notes/renotes',
limit: 10,
params: {
noteId: appearNote.id,
noteId: appearNote.value.id,
},
}));
const reactionsPagination = $computed(() => ({
const reactionsPagination = computed(() => ({
endpoint: 'notes/reactions',
limit: 10,
params: {
noteId: appearNote.id,
type: reactionTabType,
noteId: appearNote.value.id,
type: reactionTabType.value,
},
}));
useNoteCapture({
rootEl: el,
note: $$(appearNote),
pureNote: $$(note),
note: appearNote,
pureNote: note,
isDeletedRef: isDeleted,
});
useTooltip(renoteButton, async (showing) => {
const renotes = await os.api('notes/renotes', {
noteId: appearNote.id,
noteId: appearNote.value.id,
limit: 11,
});
@ -433,7 +433,7 @@ useTooltip(renoteButton, async (showing) => {
os.popup(MkUsersTooltip, {
showing,
users,
count: appearNote.renoteCount,
count: appearNote.value.renoteCount,
targetElement: renoteButton.value,
}, {}, 'closed');
});
@ -442,7 +442,7 @@ function renote(viaKeyboard = false) {
pleaseLogin();
showMovedDialog();
const { menu } = getRenoteMenu({ note: note, renoteButton });
const { menu } = getRenoteMenu({ note: note.value, renoteButton });
os.popupMenu(menu, renoteButton.value, {
viaKeyboard,
});
@ -452,22 +452,22 @@ async function renoteOnly() {
pleaseLogin();
showMovedDialog();
await getRenoteOnly({ note: note, renoteButton });
await getRenoteOnly({ note: note.value, renoteButton });
}
function quote(viaKeyboard = false): void {
pleaseLogin();
if (appearNote.channel) {
if (appearNote.value.channel) {
os.post({
renote: appearNote,
channel: appearNote.channel,
renote: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
});
}
os.post({
renote: appearNote,
renote: appearNote.value,
}, () => {
focus();
});
@ -477,8 +477,8 @@ function reply(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
os.post({
reply: appearNote,
channel: appearNote.channel,
reply: appearNote.value,
channel: appearNote.value.channel,
animation: !viaKeyboard,
}, () => {
focus();
@ -488,11 +488,11 @@ function reply(viaKeyboard = false): void {
function react(viaKeyboard = false): void {
pleaseLogin();
showMovedDialog();
if (appearNote.reactionAcceptance === 'likeOnly') {
if (appearNote.value.reactionAcceptance === 'likeOnly') {
sound.play('reaction');
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: '❤️',
});
const el = reactButton.value as HTMLElement | null | undefined;
@ -513,7 +513,7 @@ function react(viaKeyboard = false): void {
}
async function toggleReaction(reaction) {
const oldReaction = note.myReaction;
const oldReaction = note.value.myReaction;
if (oldReaction) {
const confirm = await os.confirm({
type: 'warning',
@ -524,11 +524,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/delete', {
noteId: note.id,
noteId: note.value.id,
}).then(() => {
if (oldReaction !== reaction) {
os.api('notes/reactions/create', {
noteId: note.id,
noteId: note.value.id,
reaction: reaction,
});
}
@ -537,11 +537,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: reaction,
});
}
if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
}
@ -553,10 +553,10 @@ function heartReact(): void {
sound.play('reaction');
os.api('notes/reactions/create', {
noteId: appearNote.id,
noteId: appearNote.value.id,
reaction: '❤️',
});
if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
const el = heartReactButton.value as HTMLElement | null | undefined;
@ -590,21 +590,21 @@ function onContextmenu(ev: MouseEvent): void {
ev.preventDefault();
react();
} else {
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted });
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted });
os.contextMenu(menu, ev).then(focus).finally(cleanup);
}
}
function menu(viaKeyboard = false): void {
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted });
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted });
os.popupMenu(menu, menuButton.value, {
viaKeyboard,
}).then(focus).finally(cleanup);
}
const isForeignLanguage: boolean = appearNote.text != null && (() => {
const isForeignLanguage: boolean = appearNote.value.text != null && (() => {
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
const postLang = detectLanguage(appearNote.text);
const postLang = detectLanguage(appearNote.value.text);
return postLang !== '' && postLang !== targetLang;
})();
@ -615,7 +615,7 @@ async function translate(): Promise<void> {
vibrate(defaultStore.state.vibrateSystem ? 5 : []);
const res = await os.api('notes/translate', {
noteId: appearNote.id,
noteId: appearNote.value.id,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
translating.value = false;
@ -625,7 +625,7 @@ async function translate(): Promise<void> {
}
async function clip() {
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted }), clipButton.value).then(focus);
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted }), clipButton.value).then(focus);
}
function showRenoteMenu(viaKeyboard = false): void {
@ -637,7 +637,7 @@ function showRenoteMenu(viaKeyboard = false): void {
danger: true,
action: () => {
os.api('notes/delete', {
noteId: note.id,
noteId: note.value.id,
});
isDeleted.value = true;
},
@ -645,7 +645,7 @@ function showRenoteMenu(viaKeyboard = false): void {
viaKeyboard: viaKeyboard,
});
} else {
os.popupMenu([getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote)], renoteTime.value, {
os.popupMenu([getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote)], renoteTime.value, {
viaKeyboard: viaKeyboard,
});
}
@ -661,7 +661,7 @@ function blur() {
function loadRepliesSimple() {
os.api('notes/children', {
noteId: appearNote.id,
noteId: appearNote.value.id,
limit: 3,
}).then(res => {
replies.value = res;
@ -673,7 +673,7 @@ const repliesLoaded = ref(false);
function loadReplies() {
repliesLoaded.value = true;
os.api('notes/children', {
noteId: appearNote.id,
noteId: appearNote.value.id,
limit: 30,
}).then(res => {
replies.value = res;
@ -685,7 +685,7 @@ const conversationLoaded = ref(false);
function loadConversation() {
conversationLoaded.value = true;
os.api('notes/conversation', {
noteId: appearNote.replyId,
noteId: appearNote.value.replyId,
}).then(res => {
conversation.value = res.reverse();
});

View file

@ -55,7 +55,6 @@ import { i18n } from '@/i18n.js';
import { notePage } from '@/filters/note.js';
import { userPage } from '@/filters/user.js';
import { defaultStore } from '@/store.js';
import { deepClone } from '@/scripts/clone.js';
import { useRouter } from '@/router.js';
import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
@ -65,12 +64,11 @@ const props = defineProps<{
const mock = inject<boolean>('mock', false);
let note = $ref(deepClone(props.note));
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && note.user.instance);
const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && props.note.user.instance);
const router = useRouter();
function showOnRemote() {
if (props.note.url ?? props.note.uri === undefined) router.push(notePage(note));
if (props.note.url ?? props.note.uri === undefined) router.push(notePage(props.note));
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
else window.open(props.note.url ?? props.note.uri);
}

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkNoteHeader from '@/components/MkNoteHeader.vue';
import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@ -41,15 +41,15 @@ const props = defineProps<{
note: Misskey.entities.Note;
}>();
let showEl = $ref(false);
const showEl = ref(false);
const showContent = $ref(false);
const showContent = ref(false);
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const router = useRouter();
onMounted(() => {
globalEvents.on('showEl', (showEl_receive) => {
showEl = showEl_receive;
showEl.value = showEl_receive;
});
});

View file

@ -57,7 +57,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
import { defaultStore } from '@/store.js';
import { useRouter } from '@/router.js';
let hideLine = $ref(false);
const hideLine = ref(false);
const props = withDefaults(defineProps<{
note: Misskey.entities.Note;
@ -74,16 +74,16 @@ const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
const expandOnNoteClick = defaultStore.state.expandOnNoteClick;
const router = useRouter();
let showContent = $ref(false);
let replies: Misskey.entities.Note[] = $ref([]);
const showContent = ref(false);
const replies = ref<Misskey.entities.Note[]>([]);
if (props.detail) {
os.api('notes/children', {
noteId: props.note.id,
limit: 5,
}).then(res => {
replies = res;
hideLine = true;
replies.value = res;
hideLine.value = true;
});
}

View file

@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
@ -37,11 +37,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('high');
let showing = $ref(true);
const showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 4000);
});
</script>

View file

@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref, Ref } from 'vue';
import { ref, Ref, shallowRef } from 'vue';
import MkSwitch from './MkSwitch.vue';
import MkInfo from './MkInfo.vue';
import MkButton from './MkButton.vue';
@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{
excludeTypes: () => [],
});
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
@ -61,7 +61,7 @@ function ok() {
.filter(type => !typesMap[type].value),
});
if (dialog) dialog.close();
if (dialog.value) dialog.value.close();
}
function disableAll() {

View file

@ -45,7 +45,7 @@ const props = defineProps<{
const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
let pagination = $computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? {
const pagination = computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? {
endpoint: 'i/notifications-grouped' as const,
limit: 20,
params: computed(() => ({

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
@ -23,13 +23,13 @@ const props = withDefaults(defineProps<{
maxHeight: 200,
});
let content = $shallowRef<HTMLElement>();
let omitted = $ref(false);
let ignoreOmit = $ref(false);
const content = shallowRef<HTMLElement>();
const omitted = ref(false);
const ignoreOmit = ref(false);
const calcOmit = () => {
if (omitted || ignoreOmit) return;
omitted = content.offsetHeight > props.maxHeight;
if (omitted.value || ignoreOmit.value) return;
omitted.value = content.value.offsetHeight > props.maxHeight;
};
const omitObserver = new ResizeObserver((entries, observer) => {
@ -38,7 +38,7 @@ const omitObserver = new ResizeObserver((entries, observer) => {
onMounted(() => {
calcOmit();
omitObserver.observe(content);
omitObserver.observe(content.value);
});
onUnmounted(() => {

View file

@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ComputedRef, onMounted, onUnmounted, provide, shallowRef } from 'vue';
import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue';
import RouterView from '@/components/global/RouterView.vue';
import MkWindow from '@/components/MkWindow.vue';
import { popout as _popout } from '@/scripts/popout.js';
@ -55,16 +55,16 @@ defineEmits<{
const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue')));
const contents = shallowRef<HTMLElement>();
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
let windowEl = $shallowRef<InstanceType<typeof MkWindow>>();
const history = $ref<{ path: string; key: any; }[]>([{
const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
const history = ref<{ path: string; key: any; }[]>([{
path: router.getCurrentPath(),
key: router.getCurrentKey(),
}]);
const buttonsLeft = $computed(() => {
const buttonsLeft = computed(() => {
const buttons = [];
if (history.length > 1) {
if (history.value.length > 1) {
buttons.push({
icon: 'ti ti-arrow-left',
onClick: back,
@ -73,7 +73,7 @@ const buttonsLeft = $computed(() => {
return buttons;
});
const buttonsRight = $computed(() => {
const buttonsRight = computed(() => {
const buttons = [{
icon: 'ti ti-reload',
title: i18n.ts.reload,
@ -86,21 +86,21 @@ const buttonsRight = $computed(() => {
return buttons;
});
let reloadCount = $ref(0);
const reloadCount = ref(0);
router.addListener('push', ctx => {
history.push({ path: ctx.path, key: ctx.key });
history.value.push({ path: ctx.path, key: ctx.key });
});
provide('router', router);
provideMetadataReceiver((info) => {
pageMetadata = info;
pageMetadata.value = info;
});
provide('shouldOmitHeaderTitle', true);
provide('shouldHeaderThin', true);
provide('forceSpacerMin', true);
const contextmenu = $computed(() => ([{
const contextmenu = computed(() => ([{
icon: 'ti ti-player-eject',
text: i18n.ts.showInPage,
action: expand,
@ -112,8 +112,8 @@ const contextmenu = $computed(() => ([{
icon: 'ti ti-external-link',
text: i18n.ts.openInNewTab,
action: () => {
window.open(url + router.getCurrentPath(), '_blank');
windowEl.close();
window.open(url + router.getCurrentPath(), '_blank', 'noopener');
windowEl.value.close();
},
}, {
icon: 'ti ti-link',
@ -124,26 +124,26 @@ const contextmenu = $computed(() => ([{
}]));
function back() {
history.pop();
router.replace(history.at(-1)!.path, history.at(-1)!.key);
history.value.pop();
router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
}
function reload() {
reloadCount++;
reloadCount.value++;
}
function close() {
windowEl.close();
windowEl.value.close();
}
function expand() {
mainRouter.push(router.getCurrentPath(), 'forcePage');
windowEl.close();
windowEl.value.close();
}
function popout() {
_popout(router.getCurrentPath(), windowEl.$el);
windowEl.close();
_popout(router.getCurrentPath(), windowEl.value.$el);
windowEl.value.close();
}
useScrollPositionManager(() => getScrollContainer(contents.value), router);

View file

@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts">
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, watch } from 'vue';
import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'cherrypick-js';
import * as os from '@/os.js';
import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
@ -105,12 +105,12 @@ const emit = defineEmits<{
(ev: 'status', error: boolean): void;
}>();
let rootEl = $shallowRef<HTMLElement>();
const rootEl = shallowRef<HTMLElement>();
//
let backed = $ref(false);
const backed = ref(false);
let scrollRemove = $ref<(() => void) | null>(null);
const scrollRemove = ref<(() => void) | null>(null);
/**
* 表示するアイテムのソース
@ -142,8 +142,8 @@ const {
enableInfiniteScroll,
} = defaultStore.reactiveState;
const contentEl = $computed(() => props.pagination.pageEl ?? rootEl);
const scrollableElement = $computed(() => contentEl ? getScrollContainer(contentEl) : document.body);
const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body);
const visibility = useDocumentVisibility();
@ -153,35 +153,35 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10;
//
// https://qiita.com/mkataigi/items/0154aefd2223ce23398e
let scrollObserver = $ref<IntersectionObserver>();
const scrollObserver = ref<IntersectionObserver>();
watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
if (scrollObserver) scrollObserver.disconnect();
watch([() => props.pagination.reversed, scrollableElement], () => {
if (scrollObserver.value) scrollObserver.value.disconnect();
scrollObserver = new IntersectionObserver(entries => {
backed = entries[0].isIntersecting;
scrollObserver.value = new IntersectionObserver(entries => {
backed.value = entries[0].isIntersecting;
}, {
root: scrollableElement,
root: scrollableElement.value,
rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
threshold: 0.01,
});
}, { immediate: true });
watch($$(rootEl), () => {
scrollObserver?.disconnect();
watch(rootEl, () => {
scrollObserver.value?.disconnect();
nextTick(() => {
if (rootEl) scrollObserver?.observe(rootEl);
if (rootEl.value) scrollObserver.value?.observe(rootEl.value);
});
});
watch([$$(backed), $$(contentEl)], () => {
if (!backed) {
if (!contentEl) return;
watch([backed, contentEl], () => {
if (!backed.value) {
if (!contentEl.value) return;
scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE);
} else {
if (scrollRemove) scrollRemove();
scrollRemove = null;
if (scrollRemove.value) scrollRemove.value();
scrollRemove.value = null;
}
});
@ -254,14 +254,14 @@ const fetchMore = async (): Promise<void> => {
}
const reverseConcat = _res => {
const oldHeight = scrollableElement ? scrollableElement.scrollHeight : getBodyScrollHeight();
const oldScroll = scrollableElement ? scrollableElement.scrollTop : window.scrollY;
const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight();
const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY;
items.value = concatMapWithArray(items.value, _res);
return nextTick(() => {
if (scrollableElement) {
scroll(scrollableElement, { top: oldScroll + (scrollableElement.scrollHeight - oldHeight), behavior: 'instant' });
if (scrollableElement.value) {
scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
} else {
window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
}
@ -351,7 +351,7 @@ const appearFetchMoreAhead = async (): Promise<void> => {
fetchMoreAppearTimeout();
};
const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl!, TOLERANCE);
const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
watch(visibility, () => {
if (visibility.value === 'hidden') {
@ -445,11 +445,11 @@ onActivated(() => {
});
onDeactivated(() => {
isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl ? rootEl.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl.value ? rootEl.value.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
});
function toBottom() {
scrollToBottom(contentEl!);
scrollToBottom(contentEl.value!);
}
onBeforeMount(() => {
@ -477,13 +477,13 @@ onBeforeUnmount(() => {
clearTimeout(preventAppearFetchMoreTimer.value);
preventAppearFetchMoreTimer.value = null;
}
scrollObserver?.disconnect();
scrollObserver.value?.disconnect();
});
defineExpose({
items,
queue,
backed,
backed: backed.value,
more,
reload,
prepend,

View file

@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, shallowRef, ref } from 'vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@ -49,22 +49,22 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = $shallowRef<InstanceType<typeof MkInput>>();
const password = $ref('');
const token = $ref(null);
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
const password = ref('');
const token = ref(null);
function onClose() {
emit('cancelled');
if (dialog) dialog.close();
if (dialog.value) dialog.value.close();
}
function done(res) {
emit('done', { password, token });
if (dialog) dialog.close();
emit('done', { password: password.value, token: token.value });
if (dialog.value) dialog.value.close();
}
onMounted(() => {
if (passwordInput) passwordInput.focus();
if (passwordInput.value) passwordInput.value.focus();
});
</script>

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
const props = withDefaults(defineProps<{
@ -23,13 +23,13 @@ const emit = defineEmits<{
(ev: 'end'): void;
}>();
let up = $ref(false);
const up = ref(false);
const zIndex = os.claimZIndex('middle');
const angle = (45 - (Math.random() * 90)) + 'deg';
onMounted(() => {
window.setTimeout(() => {
up = true;
up.value = true;
}, 10);
window.setTimeout(() => {

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { ref, shallowRef } from 'vue';
import MkModal from './MkModal.vue';
import MkMenu from './MkMenu.vue';
import { MenuItem } from '@/types/menu.js';
@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: 'closing'): void;
}>();
let modal = $shallowRef<InstanceType<typeof MkModal>>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const manualShowing = ref(true);
const hiding = ref(false);
@ -60,14 +60,14 @@ function hide() {
hiding.value = true;
// close
modal?.close();
modal.value?.close();
}
function close() {
manualShowing.value = false;
// close
modal?.close();
modal.value?.close();
}
</script>

View file

@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, ref } from 'vue';
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import * as mfm from 'cherrypick-mfm-js';
import * as Misskey from 'cherrypick-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@ -142,6 +142,7 @@ const props = withDefaults(defineProps<{
mention?: Misskey.entities.User;
specified?: Misskey.entities.User;
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
@ -170,51 +171,51 @@ const emit = defineEmits<{
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
}>();
const textareaEl = $shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = $shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = $shallowRef<HTMLInputElement | null>(null);
const visibilityButton = $shallowRef<HTMLElement | null>(null);
const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement | null>(null);
let posting = $ref(false);
let posted = $ref(false);
let text = $ref(props.initialText ?? '');
let files = $ref(props.initialFiles ?? []);
let poll = $ref<{
const posting = ref(false);
const posted = ref(false);
const text = ref(props.initialText ?? '');
const files = ref(props.initialFiles ?? []);
const poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
let event = $ref<{
const event = ref<{
title: string;
start: string;
end: string | null;
metadata: Record<string, string>;
} | null>(null);
let useCw = $ref(false);
let showPreview = $ref(defaultStore.state.showPreview);
let showProfilePreview = $ref(defaultStore.state.showProfilePreview);
watch($$(showPreview), () => defaultStore.set('showPreview', showPreview));
watch($$(showProfilePreview), () => defaultStore.set('showProfilePreview', showProfilePreview));
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
let visibleUsers = $ref([]);
const useCw = ref<boolean>(!!props.initialCw);
const showPreview = ref(defaultStore.state.showPreview);
const showProfilePreview = ref(defaultStore.state.showProfilePreview);
watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
watch(showProfilePreview, () => defaultStore.set('showProfilePreview', showProfilePreview.value));
const cw = ref<string | null>(props.initialCw ?? null);
const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
const visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
let autocomplete = $ref(null);
let draghover = $ref(false);
let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
let imeText = $ref('');
let showingOptions = $ref(false);
let disableRightClick = $ref(false);
const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
const autocomplete = ref(null);
const draghover = ref(false);
const quoteId = ref(null);
const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
const imeText = ref('');
const showingOptions = ref(false);
const disableRightClick = ref(false);
const textAreaReadOnly = ref(false);
const draftKey = $computed((): string => {
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
if (props.renote) {
@ -228,9 +229,9 @@ const draftKey = $computed((): string => {
return key;
});
const placeholder = $computed((): string => {
const placeholder = computed((): string => {
let postTo = '';
postTo = '[' + i18n.ts._visibility[visibility] + '] ';
postTo = '[' + i18n.ts._visibility[visibility.value] + '] ';
if (props.renote) {
return postTo + i18n.ts._postForm.quotePlaceholder;
@ -251,7 +252,7 @@ const placeholder = $computed((): string => {
}
});
const submitText = $computed((): string => {
const submitText = computed((): string => {
return props.renote
? i18n.ts.quote
: props.reply
@ -263,45 +264,45 @@ const submitText = $computed((): string => {
: i18n.ts.note;
});
const textLength = $computed((): number => {
return (text + imeText).trim().length;
const textLength = computed((): number => {
return (text.value + imeText.value).trim().length;
});
const maxTextLength = $computed((): number => {
const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000;
});
const canPost = $computed((): boolean => {
return !props.mock && !posting && !posted &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote || !!event) &&
(textLength <= maxTextLength) &&
(!poll || poll.choices.length >= 2);
const canPost = computed((): boolean => {
return !props.mock && !posting.value && !posted.value &&
(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote || !!event) &&
(textLength.value <= maxTextLength.value) &&
(!poll.value || poll.value.choices.length >= 2);
});
const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags'));
const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags'));
watch($$(text), () => {
watch(text, () => {
checkMissingMention();
}, { immediate: true });
watch($$(visibility), () => {
watch(visibility, () => {
checkMissingMention();
}, { immediate: true });
watch($$(visibleUsers), () => {
watch(visibleUsers, () => {
checkMissingMention();
}, {
deep: true,
});
if (props.mention) {
text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text += ' ';
text.value = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text.value += ' ';
}
if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) {
text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
text.value = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
}
if (props.reply && props.reply.text != null) {
@ -319,32 +320,32 @@ if (props.reply && props.reply.text != null) {
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
//
if (text.includes(`${mention} `)) continue;
if (text.value.includes(`${mention} `)) continue;
text += `${mention} `;
text.value += `${mention} `;
}
}
if ($i?.isSilenced && visibility === 'public') {
visibility = 'home';
if ($i?.isSilenced && visibility.value === 'public') {
visibility.value = 'home';
}
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
}
//
if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) {
if (props.reply.visibility === 'home' && visibility === 'followers') {
visibility = 'followers';
} else if (['home', 'followers'].includes(props.reply.visibility) && visibility === 'specified') {
visibility = 'specified';
if (props.reply.visibility === 'home' && visibility.value === 'followers') {
visibility.value = 'followers';
} else if (['home', 'followers'].includes(props.reply.visibility) && visibility.value === 'specified') {
visibility.value = 'specified';
} else {
visibility = props.reply.visibility;
visibility.value = props.reply.visibility;
}
if (visibility === 'specified') {
if (visibility.value === 'specified') {
if (props.reply.visibleUserIds) {
os.api('users/show', {
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
@ -362,59 +363,59 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
}
if (props.specified) {
visibility = 'specified';
visibility.value = 'specified';
pushVisibleUser(props.specified);
}
// keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true;
cw = props.reply.cw;
useCw.value = true;
cw.value = props.reply.cw;
}
function watchForDraft() {
watch($$(text), () => saveDraft());
watch($$(useCw), () => saveDraft());
watch($$(cw), () => saveDraft());
watch($$(disableRightClick), () => saveDraft());
watch($$(poll), () => saveDraft());
watch($$(event), () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft());
watch($$(localOnly), () => saveDraft());
watch(text, () => saveDraft());
watch(useCw, () => saveDraft());
watch(cw, () => saveDraft());
watch(disableRightClick, () => saveDraft());
watch(poll, () => saveDraft());
watch(event, () => saveDraft());
watch(files, () => saveDraft(), { deep: true });
watch(visibility, () => saveDraft());
watch(localOnly, () => saveDraft());
}
function checkMissingMention() {
if (visibility === 'specified') {
const ast = mfm.parse(text);
if (visibility.value === 'specified') {
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions = true;
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions.value = true;
return;
}
}
}
hasNotSpecifiedMentions = false;
hasNotSpecifiedMentions.value = false;
}
function addMissingMention() {
const ast = mfm.parse(text);
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
os.api('users/show', { username: x.username, host: x.host }).then(user => {
visibleUsers.push(user);
visibleUsers.value.push(user);
});
}
}
}
function togglePoll() {
if (poll) {
poll = null;
if (poll.value) {
poll.value = null;
} else {
poll = {
poll.value = {
choices: ['', ''],
multiple: false,
expiresAt: null,
@ -437,13 +438,13 @@ function toggleEvent() {
}
function addTag(tag: string) {
insertTextAtCursor(textareaEl, ` #${tag} `);
insertTextAtCursor(textareaEl.value, ` #${tag} `);
}
function focus() {
if (textareaEl) {
textareaEl.focus();
textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length);
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(textareaEl.value.value.length, textareaEl.value.value.length);
}
}
@ -452,55 +453,55 @@ function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
for (const file of files_) {
files.push(file);
files.value.push(file);
}
});
}
function detachFile(id) {
files = files.filter(x => x.id !== id);
files.value = files.value.filter(x => x.id !== id);
}
function updateFileSensitive(file, sensitive) {
if (props.mock) {
emit('fileChangeSensitive', file.id, sensitive);
}
files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive;
files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive;
}
function updateFileName(file, name) {
files[files.findIndex(x => x.id === file.id)].name = name;
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
}
function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void {
files[files.findIndex(x => x.id === file.id)] = newFile;
files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
}
function upload(file: File, name?: string): void {
if (props.mock) return;
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.push(res);
files.value.push(res);
});
}
function setVisibility() {
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
return;
}
os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility,
currentVisibility: visibility.value,
isSilenced: $i?.isSilenced,
localOnly: localOnly,
src: visibilityButton,
localOnly: localOnly.value,
src: visibilityButton.value,
}, {
changeVisibility: v => {
visibility = v;
visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('visibility', visibility);
defaultStore.set('visibility', visibility.value);
}
},
}, 'closed');
@ -508,14 +509,14 @@ function setVisibility() {
async function toggleLocalOnly() {
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
return;
}
const neverShowInfo = miLocalStorage.getItem('neverShowLocalOnlyInfo');
if (!localOnly && neverShowInfo !== 'true') {
if (!localOnly.value && neverShowInfo !== 'true') {
const confirm = await os.actions({
type: 'question',
title: i18n.ts.disableFederationConfirm,
@ -545,7 +546,7 @@ async function toggleLocalOnly() {
}
}
localOnly = !localOnly;
localOnly.value = !localOnly.value;
}
async function toggleReactionAcceptance() {
@ -558,15 +559,15 @@ async function toggleReactionAcceptance() {
{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
],
default: reactionAcceptance,
default: reactionAcceptance.value,
});
if (select.canceled) return;
reactionAcceptance = select.result;
reactionAcceptance.value = select.result;
}
function pushVisibleUser(user) {
if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.push(user);
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);
}
}
@ -574,22 +575,22 @@ function addVisibleUser() {
os.selectUser().then(user => {
pushVisibleUser(user);
if (!text.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
text = `@${Misskey.acct.toString(user)} ${text}`;
if (!text.value.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
text.value = `@${Misskey.acct.toString(user)} ${text.value}`;
}
});
}
function removeVisibleUser(user) {
visibleUsers = erase(user, visibleUsers);
visibleUsers.value = erase(user, visibleUsers.value);
}
function clear() {
text = '';
files = [];
poll = null;
event = null;
quoteId = null;
text.value = '';
files.value = [];
poll.value = null;
event.value = null;
quoteId.value = null;
}
function onKeydown(ev: KeyboardEvent) {
@ -600,22 +601,22 @@ function onKeydown(ev: KeyboardEvent) {
}
if (defaultStore.state.postFormVisibilityHotkey) {
if (ev.ctrlKey && ev.shiftKey && (visibility === 'specified')) visibility = 'public';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'public')) visibility = 'home';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'home')) visibility = 'followers';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'followers')) visibility = 'specified';
if ((ev.ctrlKey || ev.metaKey) && ev.altKey) localOnly = !localOnly;
if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'specified')) visibility.value = 'public';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'public')) visibility.value = 'home';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'home')) visibility.value = 'followers';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'followers')) visibility.value = 'specified';
if ((ev.ctrlKey || ev.metaKey) && ev.altKey) localOnly.value = !localOnly.value;
}
if (ev.key === 'Escape') emit('esc');
}
function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data;
imeText.value = ev.data;
}
function onCompositionEnd(ev: CompositionEvent) {
imeText = '';
imeText.value = '';
}
async function onPaste(ev: ClipboardEvent) {
@ -633,7 +634,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData('text');
if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) {
ev.preventDefault();
os.confirm({
@ -641,11 +642,11 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => {
if (canceled) {
insertTextAtCursor(textareaEl, paste);
insertTextAtCursor(textareaEl.value, paste);
return;
}
quoteId = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
});
}
}
@ -656,7 +657,7 @@ function onDragover(ev) {
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
ev.preventDefault();
draghover = true;
draghover.value = true;
switch (ev.dataTransfer.effectAllowed) {
case 'all':
case 'uninitialized':
@ -677,15 +678,15 @@ function onDragover(ev) {
}
function onDragenter(ev) {
draghover = true;
draghover.value = true;
}
function onDragleave(ev) {
draghover = false;
draghover.value = false;
}
function onDrop(ev): void {
draghover = false;
draghover.value = false;
//
if (ev.dataTransfer.files.length > 0) {
@ -698,7 +699,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
files.push(file);
files.value.push(file);
ev.preventDefault();
}
//#endregion
@ -709,17 +710,17 @@ function saveDraft() {
const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
draftData[draftKey] = {
draftData[draftKey.value] = {
updatedAt: new Date(),
data: {
text: text,
useCw: useCw,
cw: cw,
disableRightClick: disableRightClick,
visibility: visibility,
localOnly: localOnly,
files: files,
poll: poll,
text: text.value,
useCw: useCw.value,
cw: cw.value,
disableRightClick: disableRightClick.value,
visibility: visibility.value,
localOnly: localOnly.value,
files: files.value,
poll: poll.value,
event: event,
},
};
@ -730,13 +731,13 @@ function saveDraft() {
function deleteDraft() {
const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
delete draftData[draftKey];
delete draftData[draftKey.value];
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
}
async function post(ev?: MouseEvent) {
if (useCw && (cw == null || cw.trim() === '')) {
if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
os.alert({
type: 'error',
text: i18n.ts.cwNotationRequired,
@ -755,13 +756,13 @@ async function post(ev?: MouseEvent) {
if (props.mock) return;
const annoying =
text.includes('$[x2') ||
text.includes('$[x3') ||
text.includes('$[x4') ||
text.includes('$[scale') ||
text.includes('$[position');
text.value.includes('$[x2') ||
text.value.includes('$[x3') ||
text.value.includes('$[x4') ||
text.value.includes('$[scale') ||
text.value.includes('$[position');
if (annoying && visibility === 'public') {
if (annoying && visibility.value === 'public') {
const { canceled, result } = await os.actions({
type: 'warning',
text: i18n.ts.thisPostMayBeAnnoying,
@ -781,29 +782,29 @@ async function post(ev?: MouseEvent) {
if (canceled) return;
if (result === 'cancel') return;
if (result === 'home') {
visibility = 'home';
visibility.value = 'home';
}
}
let postData = {
text: text === '' ? null : text,
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
text: text.value === '' ? null : text.value,
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
channelId: props.channel ? props.channel.id : undefined,
poll: poll,
event: event,
cw: useCw ? cw ?? '' : null,
localOnly: localOnly,
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
reactionAcceptance,
disableRightClick: disableRightClick,
poll: poll.value,
event: event.value,
cw: useCw.value ? cw.value ?? '' : null,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
reactionAcceptance: reactionAcceptance.value,
disableRightClick: disableRightClick.value,
noteId: props.updateMode ? props.initialNote?.id : undefined,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
}
@ -820,15 +821,15 @@ async function post(ev?: MouseEvent) {
let token = undefined;
if (postAccount) {
if (postAccount.value) {
const storedAccounts = await getAccounts();
token = storedAccounts.find(x => x.id === postAccount.id)?.token;
token = storedAccounts.find(x => x.id === postAccount.value.id)?.token;
}
posting = true;
posting.value = true;
os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => {
if (props.freezeAfterPosted) {
posted = true;
posted.value = true;
} else {
clear();
}
@ -845,8 +846,8 @@ async function post(ev?: MouseEvent) {
const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[];
miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
}
posting = false;
postAccount = null;
posting.value = false;
postAccount.value = null;
incNotesCount();
if (notesCount === 1) {
@ -891,13 +892,13 @@ async function post(ev?: MouseEvent) {
}
});
}).catch(err => {
posting = false;
posting.value = false;
os.alert({
type: 'error',
text: err.message + '\n' + (err as any).id,
});
});
textareaEl.style.height = '140px';
textareaEl.value.style.height = '140px';
if (props.updateMode) sound.play('noteEdited');
vibrate(defaultStore.state.vibrateSystem ? [10, 20, 10, 20, 10, 20, 60] : []);
}
@ -908,7 +909,7 @@ function cancel() {
function insertMention() {
os.selectUser().then(user => {
insertTextAtCursor(textareaEl, '@' + Misskey.acct.toString(user) + ' ');
insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' ');
});
}
@ -918,7 +919,7 @@ async function insertEmoji(ev: MouseEvent) {
emojiPicker.show(
ev.currentTarget ?? ev.target,
emoji => {
insertTextAtCursor(textareaEl, emoji);
insertTextAtCursor(textareaEl.value, emoji);
},
() => {
textAreaReadOnly.value = false;
@ -932,11 +933,11 @@ function showActions(ev) {
text: action.title,
action: () => {
action.handler({
text: text,
cw: cw,
text: text.value,
cw: cw.value,
}, (key, value) => {
if (key === 'text') { text = value; }
if (key === 'cw') { useCw = value !== null; cw = value; }
if (key === 'text') { text.value = value; }
if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
});
},
})), ev.currentTarget ?? ev.target);
@ -946,7 +947,7 @@ async function openMfmCheatSheet() {
os.popup(defineAsyncComponent(() => import('@/components/MkMfmCheatSheetDialog.vue')), {}, {}, 'closed');
}
let postAccount = $ref<Misskey.entities.UserDetailed | null>(null);
const postAccount = ref<Misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) {
if (props.mock) return;
@ -954,12 +955,12 @@ function openAccountMenu(ev: MouseEvent) {
openAccountMenu_({
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id,
active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => {
if (account.id === $i.id) {
postAccount = null;
postAccount.value = null;
} else {
postAccount = account;
postAccount.value = account;
}
},
}, ev);
@ -970,12 +971,12 @@ function showPreviewMenu(ev: MouseEvent) {
type: 'switch',
text: i18n.ts.previewNoteText,
icon: 'ti ti-eye',
ref: $$(showPreview),
ref: showPreview,
}, {
type: 'switch',
text: i18n.ts.previewNoteProfile,
icon: 'ti ti-user-circle',
ref: $$(showProfilePreview),
ref: showProfilePreview,
}], ev.currentTarget ?? ev.target);
}
@ -989,27 +990,27 @@ onMounted(() => {
}
// TODO: detach when unmount
new Autocomplete(textareaEl, $$(text));
new Autocomplete(cwInputEl, $$(cw));
new Autocomplete(hashtagsInputEl, $$(hashtags));
new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl.value, hashtags);
nextTick(() => {
// 稿
if (!props.instant && !props.mention && !props.specified && !props.mock) {
const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey];
const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value];
if (draft) {
text = draft.data.text;
useCw = draft.data.useCw;
cw = draft.data.cw;
disableRightClick = draft.data.disableRightClick;
visibility = draft.data.visibility;
localOnly = draft.data.localOnly;
files = (draft.data.files || []).filter(draftFile => draftFile);
text.value = draft.data.text;
useCw.value = draft.data.useCw;
cw.value = draft.data.cw;
disableRightClick.value = draft.data.disableRightClick;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
files.value = (draft.data.files || []).filter(draftFile => draftFile);
if (draft.data.poll) {
poll = draft.data.poll;
poll.value = draft.data.poll;
}
if (draft.data.event) {
event = draft.data.event;
event.value = draft.data.event;
}
}
}
@ -1017,12 +1018,12 @@ onMounted(() => {
//
if (props.initialNote) {
const init = props.initialNote;
text = init.text ? init.text : '';
files = init.files;
cw = init.cw;
useCw = init.cw != null;
text.value = init.text ? init.text : '';
files.value = init.files;
cw.value = init.cw;
useCw.value = init.cw != null;
if (init.poll) {
poll = {
poll.value = {
choices: init.poll.choices.map(x => x.text),
multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt,
@ -1037,10 +1038,10 @@ onMounted(() => {
metadata: init.event.metadata,
};
}
visibility = init.visibility;
localOnly = init.localOnly;
quoteId = init.renote ? init.renote.id : null;
disableRightClick = init.disableRightClick != null;
visibility.value = init.visibility;
localOnly.value = init.localOnly;
quoteId.value = init.renote ? init.renote.id : null;
disableRightClick.value = init.disableRightClick != null;
}
nextTick(() => watchForDraft());

View file

@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { shallowRef } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkModal from '@/components/MkModal.vue';
import MkPostForm from '@/components/MkPostForm.vue';
@ -22,6 +22,7 @@ const props = defineProps<{
mention?: Misskey.entities.User;
specified?: Misskey.entities.User;
initialText?: string;
initialCw?: string;
initialVisibility?: typeof Misskey.noteVisibilities;
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
@ -37,11 +38,11 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
let modal = $shallowRef<InstanceType<typeof MkModal>>();
let form = $shallowRef<InstanceType<typeof MkPostForm>>();
const modal = shallowRef<InstanceType<typeof MkModal>>();
const form = shallowRef<InstanceType<typeof MkPostForm>>();
function onPosted() {
modal.close({
modal.value.close({
useSendAnimation: true,
});
}

View file

@ -122,7 +122,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, ref } from 'vue';
import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
import * as mfm from 'cherrypick-mfm-js';
import * as Misskey from 'cherrypick-js';
import insertTextAtCursor from 'insert-text-at-cursor';
@ -163,6 +163,7 @@ const props = withDefaults(defineProps<{
mention?: Misskey.entities.User;
specified?: Misskey.entities.User;
initialText?: string;
initialCw?: string;
initialVisibility?: (typeof Misskey.noteVisibilities)[number];
initialFiles?: Misskey.entities.DriveFile[];
initialLocalOnly?: boolean;
@ -172,8 +173,8 @@ const props = withDefaults(defineProps<{
fixed?: boolean;
autofocus?: boolean;
freezeAfterPosted?: boolean;
mock?: boolean;
updateMode?: boolean;
mock?: boolean;
updateMode?: boolean;
}>(), {
initialVisibleUsers: () => [],
autofocus: false,
@ -187,57 +188,57 @@ const emit = defineEmits<{
(ev: 'cancel'): void;
(ev: 'esc'): void;
// Mock
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
// Mock
(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
}>();
const textareaEl = $shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = $shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = $shallowRef<HTMLInputElement | null>(null);
const visibilityButton = $shallowRef<HTMLElement | null>(null);
const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
const cwInputEl = shallowRef<HTMLInputElement | null>(null);
const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
const visibilityButton = shallowRef<HTMLElement | null>(null);
let showForm = $ref(false);
const showForm = ref(false);
let posting = $ref(false);
let posted = $ref(false);
let text = $ref(props.initialText ?? '');
let files = $ref(props.initialFiles ?? []);
let poll = $ref<{
const posting = ref(false);
const posted = ref(false);
const text = ref(props.initialText ?? '');
const files = ref(props.initialFiles ?? []);
const poll = ref<{
choices: string[];
multiple: boolean;
expiresAt: string | null;
expiredAfter: string | null;
} | null>(null);
let event = $ref<{
const event = ref<{
title: string;
start: string;
end: string | null;
metadata: Record<string, string>;
} | null>(null);
let useCw = $ref(false);
let showPreview = $ref(defaultStore.state.showPreviewInReplies);
let showProfilePreview = $ref(defaultStore.state.showProfilePreview);
watch($$(showPreview), () => defaultStore.set('showPreviewInReplies', showPreview));
watch($$(showProfilePreview), () => defaultStore.set('showProfilePreview', showProfilePreview));
let cw = $ref<string | null>(null);
let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
let visibleUsers = $ref([]);
const useCw = ref<boolean>(!!props.initialCw);
const showPreview = ref(defaultStore.state.showPreview);
const showProfilePreview = ref(defaultStore.state.showProfilePreview);
watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
watch(showProfilePreview, () => defaultStore.set('showProfilePreview', showProfilePreview.value));
const cw = ref<string | null>(props.initialCw ?? null);
const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
const visibleUsers = ref([]);
if (props.initialVisibleUsers) {
props.initialVisibleUsers.forEach(pushVisibleUser);
}
let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
let autocomplete = $ref(null);
let draghover = $ref(false);
let quoteId = $ref(null);
let hasNotSpecifiedMentions = $ref(false);
let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
let imeText = $ref('');
let showingOptions = $ref(false);
let disableRightClick = $ref(false);
const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
const autocomplete = ref(null);
const draghover = ref(false);
const quoteId = ref(null);
const hasNotSpecifiedMentions = ref(false);
const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
const imeText = ref('');
const showingOptions = ref(false);
const disableRightClick = ref(false);
const textAreaReadOnly = ref(false);
const draftKey = $computed((): string => {
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
if (props.renote) {
@ -251,9 +252,9 @@ const draftKey = $computed((): string => {
return key;
});
const placeholder = $computed((): string => {
const placeholder = computed((): string => {
let postTo = '';
postTo = '[' + i18n.ts._visibility[visibility] + '] ';
postTo = '[' + i18n.ts._visibility[visibility.value] + '] ';
if (!$i) {
return i18n.ts._postForm.signinRequiredPlaceholder;
@ -276,7 +277,7 @@ const placeholder = $computed((): string => {
}
});
const submitText = $computed((): string => {
const submitText = computed((): string => {
return !$i
? i18n.ts.login
: props.renote
@ -290,41 +291,41 @@ const submitText = $computed((): string => {
: i18n.ts.note;
});
const textLength = $computed((): number => {
return (text + imeText).trim().length;
const textLength = computed((): number => {
return (text.value + imeText.value).trim().length;
});
const maxTextLength = $computed((): number => {
const maxTextLength = computed((): number => {
return instance ? instance.maxNoteTextLength : 1000;
});
const canPost = $computed((): boolean => {
return !props.mock && !posting && !posted &&
(1 <= textLength || 1 <= files.length || !!poll || !!props.renote || !!event) &&
(textLength <= maxTextLength) &&
(!poll || poll.choices.length >= 2);
const canPost = computed((): boolean => {
return !props.mock && !posting.value && !posted.value &&
(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote || !!event.value) &&
(textLength.value <= maxTextLength.value) &&
(!poll.value || poll.value.choices.length >= 2);
});
const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags'));
const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags'));
watch($$(text), () => {
watch(text, () => {
checkMissingMention();
}, { immediate: true });
watch($$(visibility), () => {
watch(visibility, () => {
checkMissingMention();
}, { immediate: true });
watch($$(visibleUsers), () => {
watch(visibleUsers, () => {
checkMissingMention();
}, {
deep: true,
});
if (props.mention) {
text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text += ' ';
text.value = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
text.value += ' ';
}
if (props.reply && props.reply.text != null) {
@ -342,30 +343,30 @@ if (props.reply && props.reply.text != null) {
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
//
if (text.includes(`${mention} `)) continue;
if (text.value.includes(`${mention} `)) continue;
}
}
if ($i?.isSilenced && visibility === 'public') {
visibility = 'home';
if ($i?.isSilenced && visibility.value === 'public') {
visibility.value = 'home';
}
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
}
//
if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) {
if (props.reply.visibility === 'home' && visibility === 'followers') {
visibility = 'followers';
} else if (['home', 'followers'].includes(props.reply.visibility) && visibility === 'specified') {
visibility = 'specified';
if (props.reply.visibility === 'home' && visibility.value === 'followers') {
visibility.value = 'followers';
} else if (['home', 'followers'].includes(props.reply.visibility) && visibility.value === 'specified') {
visibility.value = 'specified';
} else {
visibility = props.reply.visibility;
visibility.value = props.reply.visibility;
}
if (visibility === 'specified') {
if (visibility.value === 'specified') {
if (props.reply.visibleUserIds) {
os.api('users/show', {
userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
@ -383,59 +384,59 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
}
if (props.specified) {
visibility = 'specified';
visibility.value = 'specified';
pushVisibleUser(props.specified);
}
// keep cw when reply
if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
useCw = true;
cw = props.reply.cw;
useCw.value = true;
cw.value = props.reply.cw;
}
function watchForDraft() {
watch($$(text), () => saveDraft());
watch($$(useCw), () => saveDraft());
watch($$(cw), () => saveDraft());
watch($$(disableRightClick), () => saveDraft());
watch($$(poll), () => saveDraft());
watch($$(event), () => saveDraft());
watch($$(files), () => saveDraft(), { deep: true });
watch($$(visibility), () => saveDraft());
watch($$(localOnly), () => saveDraft());
watch(text, () => saveDraft());
watch(useCw, () => saveDraft());
watch(cw, () => saveDraft());
watch(disableRightClick, () => saveDraft());
watch(poll, () => saveDraft());
watch(event, () => saveDraft());
watch(files, () => saveDraft(), { deep: true });
watch(visibility, () => saveDraft());
watch(localOnly, () => saveDraft());
}
function checkMissingMention() {
if (visibility === 'specified') {
const ast = mfm.parse(text);
if (visibility.value === 'specified') {
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions = true;
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
hasNotSpecifiedMentions.value = true;
return;
}
}
}
hasNotSpecifiedMentions = false;
hasNotSpecifiedMentions.value = false;
}
function addMissingMention() {
const ast = mfm.parse(text);
const ast = mfm.parse(text.value);
for (const x of extractMentions(ast)) {
if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
os.api('users/show', { username: x.username, host: x.host }).then(user => {
visibleUsers.push(user);
visibleUsers.value.push(user);
});
}
}
}
function togglePoll() {
if (poll) {
poll = null;
if (poll.value) {
poll.value = null;
} else {
poll = {
poll.value = {
choices: ['', ''],
multiple: false,
expiresAt: null,
@ -445,10 +446,10 @@ function togglePoll() {
}
function toggleEvent() {
if (event) {
event = null;
if (event.value) {
event.value = null;
} else {
event = {
event.value = {
title: '',
start: (new Date()).toString(),
end: null,
@ -458,13 +459,13 @@ function toggleEvent() {
}
function addTag(tag: string) {
insertTextAtCursor(textareaEl, ` #${tag} `);
insertTextAtCursor(textareaEl.value, ` #${tag} `);
}
function focus() {
if (textareaEl) {
textareaEl.focus();
textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length);
if (textareaEl.value) {
textareaEl.value.focus();
textareaEl.value.setSelectionRange(textareaEl.value.value.length, textareaEl.value.value.length);
}
}
@ -473,55 +474,55 @@ function chooseFileFrom(ev) {
selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
for (const file of files_) {
files.push(file);
files.value.push(file);
}
});
}
function detachFile(id) {
files = files.filter(x => x.id !== id);
files.value = files.value.filter(x => x.id !== id);
}
function updateFileSensitive(file, sensitive) {
if (props.mock) {
emit('fileChangeSensitive', file.id, sensitive);
}
files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive;
files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive;
}
function updateFileName(file, name) {
files[files.findIndex(x => x.id === file.id)].name = name;
files.value[files.value.findIndex(x => x.id === file.id)].name = name;
}
function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void {
files[files.findIndex(x => x.id === file.id)] = newFile;
files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
}
function upload(file: File, name?: string): void {
if (props.mock) return;
uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
files.push(res);
files.value.push(res);
});
}
function setVisibility() {
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
return;
}
os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
currentVisibility: visibility,
currentVisibility: visibility.value,
isSilenced: $i?.isSilenced,
localOnly: localOnly,
src: visibilityButton,
localOnly: localOnly.value,
src: visibilityButton.value,
}, {
changeVisibility: v => {
visibility = v;
visibility.value = v;
if (defaultStore.state.rememberNoteVisibility) {
defaultStore.set('visibility', visibility);
defaultStore.set('visibility', visibility.value);
}
},
}, 'closed');
@ -529,14 +530,14 @@ function setVisibility() {
async function toggleLocalOnly() {
if (props.channel) {
visibility = 'public';
localOnly = true; // TODO:
visibility.value = 'public';
localOnly.value = true; // TODO:
return;
}
const neverShowInfo = miLocalStorage.getItem('neverShowLocalOnlyInfo');
if (!localOnly && neverShowInfo !== 'true') {
if (!localOnly.value && neverShowInfo !== 'true') {
const confirm = await os.actions({
type: 'question',
title: i18n.ts.disableFederationConfirm,
@ -566,7 +567,7 @@ async function toggleLocalOnly() {
}
}
localOnly = !localOnly;
localOnly.value = !localOnly.value;
}
async function toggleReactionAcceptance() {
@ -579,15 +580,15 @@ async function toggleReactionAcceptance() {
{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
],
default: reactionAcceptance,
default: reactionAcceptance.value,
});
if (select.canceled) return;
reactionAcceptance = select.result;
reactionAcceptance.value = select.result;
}
function pushVisibleUser(user) {
if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.push(user);
if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
visibleUsers.value.push(user);
}
}
@ -595,22 +596,22 @@ function addVisibleUser() {
os.selectUser().then(user => {
pushVisibleUser(user);
if (!text.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
text = `@${Misskey.acct.toString(user)} ${text}`;
if (!text.value.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
text.value = `@${Misskey.acct.toString(user)} ${text.value}`;
}
});
}
function removeVisibleUser(user) {
visibleUsers = erase(user, visibleUsers);
visibleUsers.value = erase(user, visibleUsers.value);
}
function clear() {
text = '';
files = [];
poll = null;
event = null;
quoteId = null;
text.value = '';
files.value = [];
poll.value = null;
event.value = null;
quoteId.value = null;
}
function onKeydown(ev: KeyboardEvent) {
@ -621,22 +622,22 @@ function onKeydown(ev: KeyboardEvent) {
}
if (defaultStore.state.postFormVisibilityHotkey) {
if (ev.ctrlKey && ev.shiftKey && (visibility === 'specified')) visibility = 'public';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'public')) visibility = 'home';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'home')) visibility = 'followers';
else if (ev.ctrlKey && ev.shiftKey && (visibility === 'followers')) visibility = 'specified';
if ((ev.ctrlKey || ev.metaKey) && ev.altKey) localOnly = !localOnly;
if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'specified')) visibility.value = 'public';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'public')) visibility.value = 'home';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'home')) visibility.value = 'followers';
else if (ev.ctrlKey && ev.shiftKey && (visibility.value === 'followers')) visibility.value = 'specified';
if ((ev.ctrlKey || ev.metaKey) && ev.altKey) localOnly.value = !localOnly.value;
}
if (ev.key === 'Escape') emit('esc');
}
function onCompositionUpdate(ev: CompositionEvent) {
imeText = ev.data;
imeText.value = ev.data;
}
function onCompositionEnd(ev: CompositionEvent) {
imeText = '';
imeText.value = '';
}
async function onPaste(ev: ClipboardEvent) {
@ -654,7 +655,7 @@ async function onPaste(ev: ClipboardEvent) {
const paste = ev.clipboardData.getData('text');
if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) {
ev.preventDefault();
os.confirm({
@ -662,11 +663,11 @@ async function onPaste(ev: ClipboardEvent) {
text: i18n.ts.quoteQuestion,
}).then(({ canceled }) => {
if (canceled) {
insertTextAtCursor(textareaEl, paste);
insertTextAtCursor(textareaEl.value, paste);
return;
}
quoteId = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
});
}
}
@ -677,7 +678,7 @@ function onDragover(ev) {
const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
if (isFile || isDriveFile) {
ev.preventDefault();
draghover = true;
draghover.value = true;
switch (ev.dataTransfer.effectAllowed) {
case 'all':
case 'uninitialized':
@ -698,15 +699,15 @@ function onDragover(ev) {
}
function onDragenter(ev) {
draghover = true;
draghover.value = true;
}
function onDragleave(ev) {
draghover = false;
draghover.value = false;
}
function onDrop(ev): void {
draghover = false;
draghover.value = false;
//
if (ev.dataTransfer.files.length > 0) {
@ -719,7 +720,7 @@ function onDrop(ev): void {
const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
if (driveFile != null && driveFile !== '') {
const file = JSON.parse(driveFile);
files.push(file);
files.value.push(file);
ev.preventDefault();
}
//#endregion
@ -730,17 +731,17 @@ function saveDraft() {
const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
draftData[draftKey] = {
draftData[draftKey.value] = {
updatedAt: new Date(),
data: {
text: text,
useCw: useCw,
cw: cw,
disableRightClick: disableRightClick,
visibility: visibility,
localOnly: localOnly,
files: files,
poll: poll,
text: text.value,
useCw: useCw.value,
cw: cw.value,
disableRightClick: disableRightClick.value,
visibility: visibility.value,
localOnly: localOnly.value,
files: files.value,
poll: poll.value,
event: event,
},
};
@ -751,13 +752,13 @@ function saveDraft() {
function deleteDraft() {
const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
delete draftData[draftKey];
delete draftData[draftKey.value];
miLocalStorage.setItem('drafts', JSON.stringify(draftData));
}
async function post(ev?: MouseEvent) {
if (useCw && (cw == null || cw.trim() === '')) {
if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
os.alert({
type: 'error',
text: i18n.ts.cwNotationRequired,
@ -776,13 +777,13 @@ async function post(ev?: MouseEvent) {
if (props.mock) return;
const annoying =
text.includes('$[x2') ||
text.includes('$[x3') ||
text.includes('$[x4') ||
text.includes('$[scale') ||
text.includes('$[position');
text.value.includes('$[x2') ||
text.value.includes('$[x3') ||
text.value.includes('$[x4') ||
text.value.includes('$[scale') ||
text.value.includes('$[position');
if (annoying && visibility === 'public') {
if (annoying && visibility.value === 'public') {
const { canceled, result } = await os.actions({
type: 'warning',
text: i18n.ts.thisPostMayBeAnnoying,
@ -802,29 +803,29 @@ async function post(ev?: MouseEvent) {
if (canceled) return;
if (result === 'cancel') return;
if (result === 'home') {
visibility = 'home';
visibility.value = 'home';
}
}
let postData = {
text: text === '' ? null : text,
fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
text: text.value === '' ? null : text.value,
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
replyId: props.reply ? props.reply.id : undefined,
renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
channelId: props.channel ? props.channel.id : undefined,
poll: poll,
event: event,
cw: useCw ? cw ?? '' : null,
localOnly: localOnly,
visibility: visibility,
visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
reactionAcceptance,
disableRightClick: disableRightClick,
poll: poll.value,
event: event.value,
cw: useCw.value ? cw.value ?? '' : null,
localOnly: localOnly.value,
visibility: visibility.value,
visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
reactionAcceptance: reactionAcceptance.value,
disableRightClick: disableRightClick.value,
noteId: props.updateMode ? props.initialNote?.id : undefined,
};
if (withHashtags && hashtags && hashtags.trim() !== '') {
const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
}
@ -841,15 +842,15 @@ async function post(ev?: MouseEvent) {
let token = undefined;
if (postAccount) {
if (postAccount.value) {
const storedAccounts = await getAccounts();
token = storedAccounts.find(x => x.id === postAccount.id)?.token;
token = storedAccounts.find(x => x.id === postAccount.value.id)?.token;
}
posting = true;
posting.value = true;
os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => {
if (props.freezeAfterPosted) {
posted = true;
posted.value = true;
} else {
clear();
}
@ -866,8 +867,8 @@ async function post(ev?: MouseEvent) {
const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[];
miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
}
posting = false;
postAccount = null;
posting.value = false;
postAccount.value = null;
incNotesCount();
if (notesCount === 1) {
@ -912,13 +913,13 @@ async function post(ev?: MouseEvent) {
}
});
}).catch(err => {
posting = false;
posting.value = false;
os.alert({
type: 'error',
text: err.message + '\n' + (err as any).id,
});
});
textareaEl.style.height = '35px';
textareaEl.value.style.height = '35px';
if (props.updateMode) sound.play('noteEdited');
vibrate(defaultStore.state.vibrateSystem ? [10, 20, 10, 20, 10, 20, 60] : []);
}
@ -929,7 +930,7 @@ function cancel() {
function insertMention() {
os.selectUser().then(user => {
insertTextAtCursor(textareaEl, '@' + Misskey.acct.toString(user) + ' ');
insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' ');
});
}
@ -939,7 +940,7 @@ async function insertEmoji(ev: MouseEvent) {
emojiPicker.show(
ev.currentTarget ?? ev.target,
emoji => {
insertTextAtCursor(textareaEl, emoji);
insertTextAtCursor(textareaEl.value, emoji);
},
() => {
textAreaReadOnly.value = false;
@ -953,11 +954,11 @@ function showActions(ev) {
text: action.title,
action: () => {
action.handler({
text: text,
cw: cw,
text: text.value,
cw: cw.value,
}, (key, value) => {
if (key === 'text') { text = value; }
if (key === 'cw') { useCw = value !== null; cw = value; }
if (key === 'text') { text.value = value; }
if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
});
},
})), ev.currentTarget ?? ev.target);
@ -967,25 +968,27 @@ async function openMfmCheatSheet() {
os.popup(defineAsyncComponent(() => import('@/components/MkMfmCheatSheetDialog.vue')), {}, {}, 'closed');
}
let postAccount = $ref<Misskey.entities.UserDetailed | null>(null);
const postAccount = ref<Misskey.entities.UserDetailed | null>(null);
function openAccountMenu(ev: MouseEvent) {
if (props.mock) return;
openAccountMenu_({
withExtraOperation: false,
includeCurrentAccount: true,
active: postAccount != null ? postAccount.id : $i.id,
active: postAccount.value != null ? postAccount.value.id : $i.id,
onChoose: (account) => {
if (account.id === $i.id) {
postAccount = null;
postAccount.value = null;
} else {
postAccount = account;
postAccount.value = account;
}
},
}, ev);
}
function formClick() {
if ($i) showForm = true;
if ($i) showForm.value = true;
if (props.reply && props.reply.text != null) {
const ast = mfm.parse(props.reply.text);
@ -1002,9 +1005,9 @@ function formClick() {
if ($i.username === x.username && (x.host == null || x.host === host)) continue;
//
if (text.includes(`${mention} `)) continue;
if (text.value.includes(`${mention} `)) continue;
text += `${mention} `;
text.value += `${mention} `;
}
}
}
@ -1020,12 +1023,12 @@ function showPreviewMenu(ev: MouseEvent) {
type: 'switch',
text: i18n.ts.previewNoteText,
icon: 'ti ti-eye',
ref: $$(showPreview),
ref: showPreview,
}, {
type: 'switch',
text: i18n.ts.previewNoteProfile,
icon: 'ti ti-user-circle',
ref: $$(showProfilePreview),
ref: showProfilePreview,
}], ev.currentTarget ?? ev.target);
}
@ -1038,30 +1041,30 @@ onMounted(() => {
});
}
autosize(textareaEl);
autosize(textareaEl.value);
// TODO: detach when unmount
new Autocomplete(textareaEl, $$(text));
new Autocomplete(cwInputEl, $$(cw));
new Autocomplete(hashtagsInputEl, $$(hashtags));
new Autocomplete(textareaEl.value, text);
new Autocomplete(cwInputEl.value, cw);
new Autocomplete(hashtagsInputEl.value, hashtags);
nextTick(() => {
// 稿
if (!props.instant && !props.mention && !props.specified && !props.mock) {
const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey];
const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value];
if (draft) {
text = draft.data.text;
useCw = draft.data.useCw;
cw = draft.data.cw;
disableRightClick = draft.data.disableRightClick;
visibility = draft.data.visibility;
localOnly = draft.data.localOnly;
files = (draft.data.files || []).filter(draftFile => draftFile);
text.value = draft.data.text;
useCw.value = draft.data.useCw;
cw.value = draft.data.cw;
disableRightClick.value = draft.data.disableRightClick;
visibility.value = draft.data.visibility;
localOnly.value = draft.data.localOnly;
files.value = (draft.data.files || []).filter(draftFile => draftFile);
if (draft.data.poll) {
poll = draft.data.poll;
poll.value = draft.data.poll;
}
if (draft.data.event) {
event = draft.data.event;
event.value = draft.data.event;
}
}
}
@ -1069,12 +1072,12 @@ onMounted(() => {
//
if (props.initialNote) {
const init = props.initialNote;
text = init.text ? init.text : '';
files = init.files;
cw = init.cw;
useCw = init.cw != null;
text.value = init.text ? init.text : '';
files.value = init.files;
cw.value = init.cw;
useCw.value = init.cw != null;
if (init.poll) {
poll = {
poll.value = {
choices: init.poll.choices.map(x => x.text),
multiple: init.poll.multiple,
expiresAt: init.poll.expiresAt,
@ -1082,17 +1085,17 @@ onMounted(() => {
};
}
if (init.event) {
event = {
event.value = {
title: init.event.title,
start: init.event.start,
end: init.event.end,
metadata: init.event.metadata,
};
}
visibility = init.visibility;
localOnly = init.localOnly;
quoteId = init.renote ? init.renote.id : null;
disableRightClick = init.disableRightClick != null;
visibility.value = init.visibility;
localOnly.value = init.localOnly;
quoteId.value = init.renote ? init.renote.id : null;
disableRightClick.value = init.disableRightClick != null;
}
nextTick(() => watchForDraft());
@ -1155,15 +1158,22 @@ defineExpose({
flex: 0 1 100px;
}
.cancel {
padding: 0;
font-size: 1em;
height: 100%;
flex: 0 1 50px;
}
.account {
height: 100%;
display: inline-flex;
vertical-align: bottom;
flex: 0 1 50px;
&.fixed {
margin: 0 0 0 12px;
}
&.fixed {
margin: 0 0 0 12px;
}
}
.avatar {
@ -1258,10 +1268,10 @@ defineExpose({
.preview {
padding: 16px 20px 0 20px;
// min-height: 75px;
// min-height: 75px;
max-height: 150px;
overflow: auto;
margin: 16px 8px;
margin: 16px 8px;
}
.withQuote {
@ -1347,9 +1357,9 @@ defineExpose({
max-width: 100%;
min-width: 100%;
width: 100%;
min-height: 35px;
min-height: 35px;
height: 100%;
resize: none;
resize: none;
}
.textCount {

View file

@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, onUnmounted } from 'vue';
import { onMounted, onUnmounted, watch, ref, shallowRef } from 'vue';
import { deviceKind } from '@/scripts/device-kind.js';
import { i18n } from '@/i18n.js';
import { getScrollContainer } from '@/scripts/scroll.js';
@ -37,15 +37,15 @@ const RELEASE_TRANSITION_DURATION = 200;
const PULL_BRAKE_BASE = 1.5;
const PULL_BRAKE_FACTOR = 170;
let isPullStart = $ref(false);
let isPullEnd = $ref(false);
let isRefreshing = $ref(false);
let pullDistance = $ref(0);
const isPullStart = ref(false);
const isPullEnd = ref(false);
const isRefreshing = ref(false);
const pullDistance = ref(0);
let supportPointerDesktop = false;
let startScreenY: number | null = null;
const rootEl = $shallowRef<HTMLDivElement>();
const rootEl = shallowRef<HTMLDivElement>();
let scrollEl: HTMLElement | null = null;
let disabled = false;
@ -70,17 +70,17 @@ function getScreenY(event) {
}
function moveStart(event) {
if (!isPullStart && !isRefreshing && !disabled) {
isPullStart = true;
if (!isPullStart.value && !isRefreshing.value && !disabled) {
isPullStart.value = true;
startScreenY = getScreenY(event);
pullDistance = 0;
pullDistance.value = 0;
}
}
function moveBySystem(to: number): Promise<void> {
return new Promise(r => {
const startHeight = pullDistance;
const overHeight = pullDistance - to;
const startHeight = pullDistance.value;
const overHeight = pullDistance.value - to;
if (overHeight < 1) {
r();
return;
@ -89,36 +89,36 @@ function moveBySystem(to: number): Promise<void> {
let intervalId = setInterval(() => {
const time = Date.now() - startTime;
if (time > RELEASE_TRANSITION_DURATION) {
pullDistance = to;
pullDistance.value = to;
clearInterval(intervalId);
r();
return;
}
const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time;
if (pullDistance < nextHeight) return;
pullDistance = nextHeight;
if (pullDistance.value < nextHeight) return;
pullDistance.value = nextHeight;
}, 1);
});
}
async function fixOverContent() {
if (pullDistance > FIRE_THRESHOLD) {
if (pullDistance.value > FIRE_THRESHOLD) {
await moveBySystem(FIRE_THRESHOLD);
}
}
async function closeContent() {
if (pullDistance > 0) {
if (pullDistance.value > 0) {
await moveBySystem(0);
}
}
function moveEnd() {
if (isPullStart && !isRefreshing) {
if (isPullStart.value && !isRefreshing.value) {
startScreenY = null;
if (isPullEnd) {
isPullEnd = false;
isRefreshing = true;
if (isPullEnd.value) {
isPullEnd.value = false;
isRefreshing.value = true;
fixOverContent().then(() => {
emit('refresh');
props.refresher().then(() => {
@ -126,17 +126,17 @@ function moveEnd() {
});
});
} else {
closeContent().then(() => isPullStart = false);
closeContent().then(() => isPullStart.value = false);
}
}
}
function moving(event: TouchEvent | PointerEvent) {
if (!isPullStart || isRefreshing || disabled) return;
if (!isPullStart.value || isRefreshing.value || disabled) return;
if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) {
pullDistance = 0;
isPullEnd = false;
if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) {
pullDistance.value = 0;
isPullEnd.value = false;
moveEnd();
return;
}
@ -147,16 +147,16 @@ function moving(event: TouchEvent | PointerEvent) {
const moveScreenY = getScreenY(event);
const moveHeight = moveScreenY - startScreenY!;
pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
if (pullDistance > 0) {
if (pullDistance.value > 0) {
if (event.cancelable) event.preventDefault();
}
isPullEnd = pullDistance >= FIRE_THRESHOLD;
isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
if (!isPullEnd) isVibrate = false;
else if (isPullEnd && !isVibrate) {
if (!isPullEnd.value) isVibrate = false;
else if (isPullEnd.value && !isVibrate) {
vibrate(defaultStore.state.vibrateSystem ? 10 : []);
isVibrate = true;
}
@ -169,8 +169,8 @@ function moving(event: TouchEvent | PointerEvent) {
*/
function refreshFinished() {
closeContent().then(() => {
isPullStart = false;
isRefreshing = false;
isPullStart.value = false;
isRefreshing.value = false;
});
}
@ -192,26 +192,26 @@ function onScrollContainerScroll() {
}
function registerEventListenersForReadyToPull() {
if (rootEl == null) return;
rootEl.addEventListener('touchstart', moveStart, { passive: true });
rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falsepreventDefault使
if (rootEl.value == null) return;
rootEl.value.addEventListener('touchstart', moveStart, { passive: true });
rootEl.value.addEventListener('touchmove', moving, { passive: false }); // passive: falsepreventDefault使
}
function unregisterEventListenersForReadyToPull() {
if (rootEl == null) return;
rootEl.removeEventListener('touchstart', moveStart);
rootEl.removeEventListener('touchmove', moving);
if (rootEl.value == null) return;
rootEl.value.removeEventListener('touchstart', moveStart);
rootEl.value.removeEventListener('touchmove', moving);
}
onMounted(() => {
if (rootEl == null) return;
if (rootEl.value == null) return;
scrollEl = getScrollContainer(rootEl);
scrollEl = getScrollContainer(rootEl.value);
if (scrollEl == null) return;
scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true });
rootEl.addEventListener('touchend', moveEnd, { passive: true });
rootEl.value.addEventListener('touchend', moveEnd, { passive: true });
registerEventListenersForReadyToPull();
});

View file

@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { $i, getAccounts } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
import { instance } from '@/instance.js';
@ -62,26 +63,26 @@ defineProps<{
}>();
// ServiceWorker registration
let registration = $ref<ServiceWorkerRegistration | undefined>();
const registration = ref<ServiceWorkerRegistration | undefined>();
// If this browser supports push notification
let supported = $ref(false);
const supported = ref(false);
// If this browser has already subscribed to push notification
let pushSubscription = $ref<PushSubscription | null>(null);
let pushRegistrationInServer = $ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
const pushSubscription = ref<PushSubscription | null>(null);
const pushRegistrationInServer = ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
function subscribe() {
if (!registration || !supported || !instance.swPublickey) return;
if (!registration.value || !supported.value || !instance.swPublickey) return;
// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
return promiseDialog(registration.pushManager.subscribe({
return promiseDialog(registration.value.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
})
.then(async subscription => {
pushSubscription = subscription;
pushSubscription.value = subscription;
// Register
pushRegistrationInServer = await api('sw/register', {
pushRegistrationInServer.value = await api('sw/register', {
endpoint: subscription.endpoint,
auth: encode(subscription.getKey('auth')),
publickey: encode(subscription.getKey('p256dh')),
@ -102,12 +103,12 @@ function subscribe() {
}
async function unsubscribe() {
if (!pushSubscription) return;
if (!pushSubscription.value) return;
const endpoint = pushSubscription.endpoint;
const endpoint = pushSubscription.value.endpoint;
const accounts = await getAccounts();
pushRegistrationInServer = undefined;
pushRegistrationInServer.value = undefined;
if ($i && accounts.length >= 2) {
apiWithDialog('sw/unregister', {
@ -115,11 +116,11 @@ async function unsubscribe() {
endpoint,
});
} else {
pushSubscription.unsubscribe();
pushSubscription.value.unsubscribe();
apiWithDialog('sw/unregister', {
endpoint,
});
pushSubscription = null;
pushSubscription.value = null;
}
}
@ -150,20 +151,20 @@ if (navigator.serviceWorker == null) {
// TODO:
} else {
navigator.serviceWorker.ready.then(async swr => {
registration = swr;
registration.value = swr;
pushSubscription = await registration.pushManager.getSubscription();
pushSubscription.value = await registration.value.pushManager.getSubscription();
if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) {
supported = true;
supported.value = true;
if (pushSubscription) {
if (pushSubscription.value) {
const res = await api('sw/show-registration', {
endpoint: pushSubscription.endpoint,
endpoint: pushSubscription.value.endpoint,
});
if (res) {
pushRegistrationInServer = res;
pushRegistrationInServer.value = res;
}
}
}
@ -171,6 +172,6 @@ if (navigator.serviceWorker == null) {
}
defineExpose({
pushRegistrationInServer: $$(pushRegistrationInServer),
pushRegistrationInServer: pushRegistrationInServer,
});
</script>

View file

@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { computed } from 'vue';
import { defaultStore } from '@/store.js';
const props = defineProps<{
@ -38,7 +38,7 @@ const emit = defineEmits<{
(ev: 'update:modelValue', value: any): void;
}>();
let checked = $computed(() => props.modelValue === props.value);
const checked = computed(() => props.modelValue === props.value);
function toggle(): void {
if (props.disabled) return;

View file

@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, watch } from 'vue';
import { onMounted, ref, shallowRef, watch } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
@ -57,30 +57,30 @@ const props = defineProps<{
noteId: Misskey.entities.Note['id'];
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
let note = $ref<Misskey.entities.Note>();
let tab = $ref<string>();
let reactions = $ref<string[]>();
let users = $ref();
const note = ref<Misskey.entities.Note>();
const tab = ref<string>();
const reactions = ref<string[]>();
const users = ref();
watch($$(tab), async () => {
watch(tab, async () => {
const res = await os.api('notes/reactions', {
noteId: props.noteId,
type: tab,
limit: 30,
});
users = res.map(x => x.user);
users.value = res.map(x => x.user);
});
onMounted(() => {
os.api('notes/show', {
noteId: props.noteId,
}).then((res) => {
reactions = Object.keys(res.reactions);
tab = reactions[0];
note = res;
reactions.value = Object.keys(res.reactions);
tab.value = reactions.value[0];
note.value = res;
});
});
</script>

View file

@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
import MkReactionIcon from '@/components/MkReactionIcon.vue';
@ -27,13 +27,13 @@ const emit = defineEmits<{
(ev: 'end'): void;
}>();
let up = $ref(false);
const up = ref(false);
const zIndex = os.claimZIndex('middle');
const angle = (90 - (Math.random() * 180)) + 'deg';
onMounted(() => {
window.setTimeout(() => {
up = true;
up.value = true;
}, 10);
window.setTimeout(() => {

View file

@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import * as Misskey from 'cherrypick-js';
import { inject, watch } from 'vue';
import { inject, watch, ref } from 'vue';
import XReaction from '@/components/MkReactionsViewer.reaction.vue';
import { defaultStore } from '@/store.js';
@ -38,31 +38,31 @@ const emit = defineEmits<{
const initialReactions = new Set(Object.keys(props.note.reactions));
let reactions = $ref<[string, number][]>([]);
let hasMoreReactions = $ref(false);
const reactions = ref<[string, number][]>([]);
const hasMoreReactions = ref(false);
if (props.note.myReaction && !Object.keys(reactions).includes(props.note.myReaction)) {
reactions[props.note.myReaction] = props.note.reactions[props.note.myReaction];
if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) {
reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
}
function onMockToggleReaction(emoji: string, count: number) {
if (!mock) return;
const i = reactions.findIndex((item) => item[0] === emoji);
const i = reactions.value.findIndex((item) => item[0] === emoji);
if (i < 0) return;
emit('mockUpdateMyReaction', emoji, (count - reactions[i][1]));
emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
}
watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {
let newReactions: [string, number][] = [];
hasMoreReactions = Object.keys(newSource).length > maxNumber;
hasMoreReactions.value = Object.keys(newSource).length > maxNumber;
for (let i = 0; i < reactions.length; i++) {
const reaction = reactions[i][0];
for (let i = 0; i < reactions.value.length; i++) {
const reaction = reactions.value[i][0];
if (reaction in newSource && newSource[reaction] !== 0) {
reactions[i][1] = newSource[reaction];
newReactions.push(reactions[i]);
reactions.value[i][1] = newSource[reaction];
newReactions.push(reactions.value[i]);
}
}
@ -80,7 +80,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]);
}
reactions = newReactions;
reactions.value = newReactions;
}, { immediate: true, deep: true });
</script>

View file

@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref, shallowRef } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkModalWindow from '@/components/MkModalWindow.vue';
import MkUserCardMini from '@/components/MkUserCardMini.vue';
@ -50,11 +50,11 @@ const props = defineProps<{
noteId: Misskey.entities.Note['id'];
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
let note = $ref<Misskey.entities.Note>();
let renotes = $ref();
let users = $ref();
const note = ref<Misskey.entities.Note>();
const renotes = ref();
const users = ref();
onMounted(async () => {
const res = await os.api('notes/renotes', {
@ -62,8 +62,8 @@ onMounted(async () => {
limit: 30,
});
renotes = res;
users = res.map(x => x.user);
renotes.value = res;
users.value = res.map(x => x.user);
});
</script>

View file

@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, nextTick } from 'vue';
import { onMounted, nextTick, shallowRef, ref } from 'vue';
import { Chart } from 'chart.js';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
@ -23,11 +23,11 @@ import { initChart } from '@/scripts/init-chart.js';
initChart();
const rootEl = $shallowRef<HTMLDivElement>(null);
const chartEl = $shallowRef<HTMLCanvasElement>(null);
const rootEl = shallowRef<HTMLDivElement>(null);
const chartEl = shallowRef<HTMLCanvasElement>(null);
const now = new Date();
let chartInstance: Chart = null;
let fetching = $ref(true);
const fetching = ref(true);
const { handler: externalTooltipHandler } = useChartTooltip({
position: 'middle',
@ -38,8 +38,8 @@ async function renderChart() {
chartInstance.destroy();
}
const wide = rootEl.offsetWidth > 600;
const narrow = rootEl.offsetWidth < 400;
const wide = rootEl.value.offsetWidth > 600;
const narrow = rootEl.value.offsetWidth < 400;
const maxDays = wide ? 10 : narrow ? 5 : 7;
@ -66,7 +66,7 @@ async function renderChart() {
}
}
fetching = false;
fetching.value = false;
await nextTick();
@ -83,7 +83,7 @@ async function renderChart() {
const marginEachCell = 12;
chartInstance = new Chart(chartEl, {
chartInstance = new Chart(chartEl.value, {
type: 'matrix',
data: {
datasets: [{

View file

@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent } from 'vue';
import { defineAsyncComponent, ref } from 'vue';
import { toUnicode } from 'punycode/';
import * as Misskey from 'cherrypick-js';
import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
@ -63,19 +63,19 @@ import { login } from '@/account.js';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
const squareAvatars = $ref(defaultStore.state.squareAvatars);
const squareAvatars = ref(defaultStore.state.squareAvatars);
let signing = $ref(false);
let user = $ref<Misskey.entities.UserDetailed | null>(null);
let username = $ref('');
let password = $ref('');
let token = $ref('');
let host = $ref(toUnicode(configHost));
let totpLogin = $ref(false);
let queryingKey = $ref(false);
let credentialRequest = $ref<CredentialRequestOptions | null>(null);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
const signing = ref(false);
const user = ref<Misskey.entities.UserDetailed | null>(null);
const username = ref('');
const password = ref('');
const token = ref('');
const host = ref(toUnicode(configHost));
const totpLogin = ref(false);
const queryingKey = ref(false);
const credentialRequest = ref<CredentialRequestOptions | null>(null);
const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const emit = defineEmits<{
(ev: 'login', v: any): void;
@ -101,11 +101,11 @@ const props = defineProps({
function onUsernameChange(): void {
os.api('users/show', {
username: username,
username: username.value,
}).then(userResponse => {
user = userResponse;
user.value = userResponse;
}, () => {
user = null;
user.value = null;
});
}
@ -116,21 +116,21 @@ function onLogin(res: any): Promise<void> | void {
}
async function queryKey(): Promise<void> {
queryingKey = true;
await webAuthnRequest(credentialRequest)
queryingKey.value = true;
await webAuthnRequest(credentialRequest.value)
.catch(() => {
queryingKey = false;
queryingKey.value = false;
return Promise.reject(null);
}).then(credential => {
credentialRequest = null;
queryingKey = false;
signing = true;
credentialRequest.value = null;
queryingKey.value = false;
signing.value = true;
return os.api('signin', {
username,
password,
username: username.value,
password: password.value,
credential: credential.toJSON(),
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
});
}).then(res => {
emit('login', res);
@ -141,39 +141,39 @@ async function queryKey(): Promise<void> {
type: 'error',
text: i18n.ts.signinFailed,
});
signing = false;
signing.value = false;
});
}
function onSubmit(): void {
signing = true;
if (!totpLogin && user && user.twoFactorEnabled) {
if (webAuthnSupported() && user.securityKeys) {
signing.value = true;
if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
if (webAuthnSupported() && user.value.securityKeys) {
os.api('signin', {
username,
password,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
}).then(res => {
totpLogin = true;
signing = false;
credentialRequest = parseRequestOptionsFromJSON({
totpLogin.value = true;
signing.value = false;
credentialRequest.value = parseRequestOptionsFromJSON({
publicKey: res,
});
})
.then(() => queryKey())
.catch(loginFailed);
} else {
totpLogin = true;
signing = false;
totpLogin.value = true;
signing.value = false;
}
} else {
os.api('signin', {
username,
password,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
token: user?.twoFactorEnabled ? token : undefined,
username: username.value,
password: password.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
token: user.value?.twoFactorEnabled ? token.value : undefined,
}).then(res => {
emit('login', res);
onLogin(res);
@ -221,8 +221,8 @@ function loginFailed(err: any): void {
}
}
totpLogin = false;
signing = false;
totpLogin.value = false;
signing.value = false;
}
function resetPassword(): void {

View file

@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { shallowRef } from 'vue';
import MkSignin from '@/components/MkSignin.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
import { i18n } from '@/i18n.js';
@ -39,15 +39,15 @@ const emit = defineEmits<{
(ev: 'cancelled'): void;
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
function onClose() {
emit('cancelled');
if (dialog) dialog.close();
if (dialog.value) dialog.value.close();
}
function onLogin(res) {
emit('done', res);
if (dialog) dialog.close();
if (dialog.value) dialog.value.close();
}
</script>

View file

@ -79,7 +79,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { ref, computed } from 'vue';
import { toUnicode } from 'punycode/';
import MkButton from './MkButton.vue';
import MkInput from './MkInput.vue';
@ -105,36 +105,36 @@ const emit = defineEmits<{
const host = toUnicode(config.host);
let hcaptcha = $ref<Captcha | undefined>();
let recaptcha = $ref<Captcha | undefined>();
let turnstile = $ref<Captcha | undefined>();
const hcaptcha = ref<Captcha | undefined>();
const recaptcha = ref<Captcha | undefined>();
const turnstile = ref<Captcha | undefined>();
let username: string = $ref('');
let password: string = $ref('');
let retypedPassword: string = $ref('');
let invitationCode: string = $ref('');
let email = $ref('');
let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
let submitting: boolean = $ref(false);
let hCaptchaResponse = $ref(null);
let reCaptchaResponse = $ref(null);
let turnstileResponse = $ref(null);
let usernameAbortController: null | AbortController = $ref(null);
let emailAbortController: null | AbortController = $ref(null);
const username = ref<string>('');
const password = ref<string>('');
const retypedPassword = ref<string>('');
const invitationCode = ref<string>('');
const email = ref('');
const usernameState = ref<null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range'>(null);
const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error'>(null);
const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
const submitting = ref<boolean>(false);
const hCaptchaResponse = ref(null);
const reCaptchaResponse = ref(null);
const turnstileResponse = ref(null);
const usernameAbortController = ref<null | AbortController>(null);
const emailAbortController = ref<null | AbortController>(null);
let back: boolean = $ref(false);
const back = ref<boolean>(false);
const shouldDisableSubmitting = $computed((): boolean => {
return submitting ||
instance.enableHcaptcha && !hCaptchaResponse ||
instance.enableRecaptcha && !reCaptchaResponse ||
instance.enableTurnstile && !turnstileResponse ||
instance.emailRequiredForSignup && emailState !== 'ok' ||
usernameState !== 'ok' ||
passwordRetypeState !== 'match';
const shouldDisableSubmitting = computed((): boolean => {
return submitting.value ||
instance.enableHcaptcha && !hCaptchaResponse.value ||
instance.enableRecaptcha && !reCaptchaResponse.value ||
instance.enableTurnstile && !turnstileResponse.value ||
instance.emailRequiredForSignup && emailState.value !== 'ok' ||
usernameState.value !== 'ok' ||
passwordRetypeState.value !== 'match';
});
function getPasswordStrength(source: string): number {
@ -162,57 +162,57 @@ function getPasswordStrength(source: string): number {
}
function onChangeUsername(): void {
if (username === '') {
usernameState = null;
if (username.value === '') {
usernameState.value = null;
return;
}
{
const err =
!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
username.length < 1 ? 'min-range' :
username.length > 20 ? 'max-range' :
!username.value.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
username.value.length < 1 ? 'min-range' :
username.value.length > 20 ? 'max-range' :
null;
if (err) {
usernameState = err;
usernameState.value = err;
return;
}
}
if (usernameAbortController != null) {
usernameAbortController.abort();
if (usernameAbortController.value != null) {
usernameAbortController.value.abort();
}
usernameState = 'wait';
usernameAbortController = new AbortController();
usernameState.value = 'wait';
usernameAbortController.value = new AbortController();
os.api('username/available', {
username,
}, undefined, usernameAbortController.signal).then(result => {
usernameState = result.available ? 'ok' : 'unavailable';
username: username.value,
}, undefined, usernameAbortController.value.signal).then(result => {
usernameState.value = result.available ? 'ok' : 'unavailable';
}).catch((err) => {
if (err.name !== 'AbortError') {
usernameState = 'error';
usernameState.value = 'error';
}
});
}
function onChangeEmail(): void {
if (email === '') {
emailState = null;
if (email.value === '') {
emailState.value = null;
return;
}
if (emailAbortController != null) {
emailAbortController.abort();
if (emailAbortController.value != null) {
emailAbortController.value.abort();
}
emailState = 'wait';
emailAbortController = new AbortController();
emailState.value = 'wait';
emailAbortController.value = new AbortController();
os.api('email-address/available', {
emailAddress: email,
}, undefined, emailAbortController.signal).then(result => {
emailState = result.available ? 'ok' :
emailAddress: email.value,
}, undefined, emailAbortController.value.signal).then(result => {
emailState.value = result.available ? 'ok' :
result.reason === 'used' ? 'unavailable:used' :
result.reason === 'format' ? 'unavailable:format' :
result.reason === 'disposable' ? 'unavailable:disposable' :
@ -221,66 +221,66 @@ function onChangeEmail(): void {
'unavailable';
}).catch((err) => {
if (err.name !== 'AbortError') {
emailState = 'error';
emailState.value = 'error';
}
});
}
function onChangePassword(): void {
if (password === '') {
passwordStrength = '';
if (password.value === '') {
passwordStrength.value = '';
return;
}
const strength = getPasswordStrength(password);
passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
const strength = getPasswordStrength(password.value);
passwordStrength.value = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
}
function onChangePasswordRetype(): void {
if (retypedPassword === '') {
passwordRetypeState = null;
if (retypedPassword.value === '') {
passwordRetypeState.value = null;
return;
}
passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
passwordRetypeState.value = password.value === retypedPassword.value ? 'match' : 'not-match';
}
function goBack() {
back = true;
back.value = true;
emit('back');
}
async function onSubmit(): Promise<void> {
if (submitting) return;
submitting = true;
if (submitting.value) return;
submitting.value = true;
try {
if (back) {
submitting = false;
hcaptcha?.reset?.();
recaptcha?.reset?.();
turnstile?.reset?.();
if (back.value) {
submitting.value = false;
hcaptcha.value?.reset?.();
recaptcha.value?.reset?.();
turnstile.value?.reset?.();
} else {
await os.api('signup', {
username,
password,
emailAddress: email,
invitationCode,
'hcaptcha-response': hCaptchaResponse,
'g-recaptcha-response': reCaptchaResponse,
'turnstile-response': turnstileResponse,
username: username.value,
password: password.value,
emailAddress: email.value,
invitationCode: invitationCode.value,
'hcaptcha-response': hCaptchaResponse.value,
'g-recaptcha-response': reCaptchaResponse.value,
'turnstile-response': turnstileResponse.value,
});
if (instance.emailRequiredForSignup) {
os.alert({
type: 'success',
title: i18n.ts._signup.almostThere,
text: i18n.t('_signup.emailSent', { email }),
text: i18n.t('_signup.emailSent', { email: email.value }),
});
emit('signupEmailPending');
} else {
const res = await os.api('signin', {
username,
password,
username: username.value,
password: password.value,
});
emit('signup', res);
@ -290,10 +290,10 @@ async function onSubmit(): Promise<void> {
}
}
} catch {
submitting = false;
hcaptcha?.reset?.();
recaptcha?.reset?.();
turnstile?.reset?.();
submitting.value = false;
hcaptcha.value?.reset?.();
recaptcha.value?.reset?.();
turnstile.value?.reset?.();
os.alert({
type: 'error',

View file

@ -33,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { $ref } from 'vue/macros';
import { shallowRef, ref } from 'vue';
import XSignup from '@/components/MkSignupDialog.form.vue';
import XServerRules from '@/components/MkSignupDialog.rules.vue';
import MkModalWindow from '@/components/MkModalWindow.vue';
@ -52,17 +52,17 @@ const emit = defineEmits<{
(ev: 'closed'): void;
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const isAcceptedServerRule = $ref(false);
const isAcceptedServerRule = ref(false);
function onSignup(res) {
emit('done', res);
dialog.close();
dialog.value.close();
}
function onSignupEmailPending() {
dialog.close();
dialog.value.close();
}
</script>

View file

@ -160,7 +160,7 @@ const emit = defineEmits<{
(ev: 'removeReaction', emoji: string): void;
}>();
let note = $ref(deepClone(props.note));
const note = ref(deepClone(props.note));
const el = shallowRef<HTMLElement>();
const menuButton = shallowRef<HTMLElement>();
@ -184,7 +184,7 @@ const parsed = props.note.text ? mfm.parse(props.note.text) : null;
const isLong = shouldCollapsed(props.note, []);
const isMFM = shouldMfmCollapsed(props.note);
const collapsed = $ref(isLong || (isMFM && defaultStore.state.collapseDefault) || props.note.files.length > 0 || props.note.poll);
const collapsed = ref(isLong || (isMFM && defaultStore.state.collapseDefault) || props.note.files.length > 0 || props.note.poll);
const collapseLabel = computed(() => {
return concat([
@ -194,13 +194,13 @@ const collapseLabel = computed(() => {
if (props.mock) {
watch(() => props.note, (to) => {
note = deepClone(to);
note.value = deepClone(to);
}, { deep: true });
} else {
useNoteCapture({
rootEl: el,
note: $$(note),
pureNote: $$(note),
note: note,
pureNote: note,
isDeletedRef: isDeleted,
});
}
@ -229,7 +229,7 @@ function renote(viaKeyboard = false) {
pleaseLogin();
showMovedDialog();
const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
os.popupMenu(menu, renoteButton.value, {
viaKeyboard,
});
@ -239,7 +239,7 @@ async function renoteOnly() {
pleaseLogin();
showMovedDialog();
await getRenoteOnly({ note: note, renoteButton, mock: props.mock });
await getRenoteOnly({ note: note.value, renoteButton, mock: props.mock });
}
function quote(viaKeyboard = false): void {
@ -312,7 +312,7 @@ function react(viaKeyboard = false): void {
}
async function toggleReaction(reaction) {
const oldReaction = note.myReaction;
const oldReaction = note.value.myReaction;
if (oldReaction) {
const confirm = await os.confirm({
type: 'warning',
@ -323,11 +323,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/delete', {
noteId: note.id,
noteId: note.value.id,
}).then(() => {
if (oldReaction !== reaction) {
os.api('notes/reactions/create', {
noteId: note.id,
noteId: note.value.id,
reaction: reaction,
});
}
@ -336,11 +336,11 @@ async function toggleReaction(reaction) {
sound.play('reaction');
os.api('notes/reactions/create', {
noteId: note.id,
noteId: note.value.id,
reaction: reaction,
});
}
if (note.text && note.text.length > 100 && (Date.now() - new Date(note.createdAt).getTime() < 1000 * 3)) {
if (note.value.text && note.value.text.length > 100 && (Date.now() - new Date(note.value.createdAt).getTime() < 1000 * 3)) {
claimAchievement('reactWithoutRead');
}
}
@ -390,7 +390,7 @@ function menu(viaKeyboard = false): void {
return;
}
const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, viewTextSource, noNyaize, menuButton, isDeleted, currentClip: currentClip?.value });
os.popupMenu(menu, menuButton.value, {
viaKeyboard,
}).then(focus).finally(cleanup);
@ -401,12 +401,12 @@ async function clip() {
return;
}
os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
}
const isForeignLanguage: boolean = note.text != null && (() => {
const isForeignLanguage: boolean = note.value.text != null && (() => {
const targetLang = (miLocalStorage.getItem('lang') ?? navigator.language).slice(0, 2);
const postLang = detectLanguage(note.text);
const postLang = detectLanguage(note.value.text);
return postLang !== '' && postLang !== targetLang;
})();

View file

@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="[$style.root, { [$style.disabled]: disabled, [$style.checked]: checked }]">
<div :class="[$style.root, { [$style.disabled]: disabled }]">
<input
ref="input"
type="checkbox"
@ -64,9 +64,6 @@ const toggle = () => {
opacity: 0.6;
cursor: not-allowed;
}
//&.checked {
//}
}
.input {

View file

@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, watch, onBeforeUnmount } from 'vue';
import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue';
import tinycolor from 'tinycolor2';
const loaded = !!window.TagCanvas;
@ -23,13 +23,13 @@ const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
const computedStyle = getComputedStyle(document.documentElement);
const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
let available = $ref(false);
let rootEl = $shallowRef<HTMLElement | null>(null);
let canvasEl = $shallowRef<HTMLCanvasElement | null>(null);
let tagsEl = $shallowRef<HTMLElement | null>(null);
let width = $ref(300);
const available = ref(false);
const rootEl = shallowRef<HTMLElement | null>(null);
const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
const tagsEl = shallowRef<HTMLElement | null>(null);
const width = ref(300);
watch($$(available), () => {
watch(available, () => {
try {
window.TagCanvas.Start(idForCanvas, idForTags, {
textColour: '#ffffff',
@ -52,15 +52,15 @@ watch($$(available), () => {
});
onMounted(() => {
width = rootEl.offsetWidth;
width.value = rootEl.value.offsetWidth;
if (loaded) {
available = true;
available.value = true;
} else {
document.head.appendChild(Object.assign(document.createElement('script'), {
async: true,
src: '/client-assets/tagcanvas.min.js',
})).addEventListener('load', () => available = true);
})).addEventListener('load', () => available.value = true);
}
});

View file

@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { computed, watch, onUnmounted, provide, onMounted } from 'vue';
import { computed, watch, onMounted, onUnmounted, provide, ref } from 'vue';
import { Connection } from 'cherrypick-js/built/streaming.js';
import MkNotes from '@/components/MkNotes.vue';
import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
@ -68,8 +68,8 @@ type TimelineQueryType = {
roleId?: string
}
const prComponent: InstanceType<typeof MkPullToRefresh> = $ref();
const tlComponent: InstanceType<typeof MkNotes> = $ref();
const prComponent = ref<InstanceType<typeof MkPullToRefresh>>();
const tlComponent = ref<InstanceType<typeof MkNotes>>();
let tlNotesCount = 0;
@ -80,7 +80,7 @@ const prepend = note => {
note._shouldInsertAd_ = true;
}
tlComponent.pagingComponent?.prepend(note);
tlComponent.value.pagingComponent?.prepend(note);
emit('note');
@ -282,7 +282,7 @@ function reloadTimeline() {
return new Promise<void>((res) => {
tlNotesCount = 0;
tlComponent.pagingComponent?.reload().then(() => {
tlComponent.value.pagingComponent?.reload().then(() => {
res();
});
});

View file

@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted } from 'vue';
import { onMounted, ref } from 'vue';
import * as os from '@/os.js';
import { defaultStore } from '@/store.js';
@ -35,11 +35,11 @@ const emit = defineEmits<{
}>();
const zIndex = os.claimZIndex('high');
let showing = $ref(true);
const showing = ref(true);
onMounted(() => {
window.setTimeout(() => {
showing = false;
showing.value = false;
}, 4000);
});
</script>

View file

@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { } from 'vue';
import { shallowRef, ref } from 'vue';
import * as Misskey from 'cherrypick-js';
import MkInput from './MkInput.vue';
import MkSwitch from './MkSwitch.vue';
@ -67,37 +67,37 @@ const emit = defineEmits<{
(ev: 'done', result: { name: string | null, permissions: string[] }): void;
}>();
const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
let name = $ref(props.initialName);
let permissions = $ref({});
const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
const name = ref(props.initialName);
const permissions = ref({});
if (props.initialPermissions) {
for (const kind of props.initialPermissions) {
permissions[kind] = true;
permissions.value[kind] = true;
}
} else {
for (const kind of Misskey.permissions) {
permissions[kind] = false;
permissions.value[kind] = false;
}
}
function ok(): void {
emit('done', {
name: name,
permissions: Object.keys(permissions).filter(p => permissions[p]),
name: name.value,
permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
});
dialog.close();
dialog.value.close();
}
function disableAll(): void {
for (const p in permissions) {
permissions[p] = false;
for (const p in permissions.value) {
permissions.value[p] = false;
}
}
function enableAll(): void {
for (const p in permissions) {
permissions[p] = true;
for (const p in permissions.value) {
permissions.value[p] = true;
}
}
</script>

View file

@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { onMounted, shallowRef } from 'vue';
import { onMounted, ref, shallowRef } from 'vue';
import MkModal from '@/components/MkModal.vue';
import MkButton from '@/components/MkButton.vue';
import MkSparkle from '@/components/MkSparkle.vue';
@ -39,7 +39,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js';
import { clearCache } from '@/scripts/clear-cache.js';
let showChangelog = $ref(false);
const showChangelog = ref(false);
const modal = shallowRef<InstanceType<typeof MkModal>>();

View file

@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import { defineAsyncComponent, onUnmounted } from 'vue';
import { defineAsyncComponent, onUnmounted, ref } from 'vue';
import type { summaly } from 'summaly';
import { url as local } from '@/config.js';
import { i18n } from '@/i18n.js';
@ -107,36 +107,36 @@ const props = withDefaults(defineProps<{
});
const MOBILE_THRESHOLD = 500;
const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
const self = props.url.startsWith(local);
const attr = self ? 'to' : 'href';
const target = self ? null : '_blank';
let fetching = $ref(true);
let title = $ref<string | null>(null);
let description = $ref<string | null>(null);
let thumbnail = $ref<string | null>(null);
let icon = $ref<string | null>(null);
let sitename = $ref<string | null>(null);
let sensitive = $ref<boolean>(false);
let player = $ref({
const fetching = ref(true);
const title = ref<string | null>(null);
const description = ref<string | null>(null);
const thumbnail = ref<string | null>(null);
const icon = ref<string | null>(null);
const sitename = ref<string | null>(null);
const sensitive = ref<boolean>(false);
const player = ref({
url: null,
width: null,
height: null,
} as SummalyResult['player']);
let playerEnabled = $ref(false);
let tweetId = $ref<string | null>(null);
let tweetExpanded = $ref(props.detail);
const playerEnabled = ref(false);
const tweetId = ref<string | null>(null);
const tweetExpanded = ref(props.detail);
const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
let tweetHeight = $ref(150);
let unknownUrl = $ref(false);
const tweetHeight = ref(150);
const unknownUrl = ref(false);
const requestUrl = new URL(props.url);
if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com' || requestUrl.hostname === 'x.com' || requestUrl.hostname === 'mobile.x.com') {
const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
if (m) tweetId = m[1];
if (m) tweetId.value = m[1];
}
if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
@ -148,8 +148,8 @@ requestUrl.hash = '';
window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`)
.then(res => {
if (!res.ok) {
fetching = false;
unknownUrl = true;
fetching.value = false;
unknownUrl.value = true;
return;
}
@ -157,21 +157,21 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
})
.then((info: SummalyResult) => {
if (info.url == null) {
fetching = false;
unknownUrl = true;
fetching.value = false;
unknownUrl.value = true;
return;
}
fetching = false;
unknownUrl = false;
fetching.value = false;
unknownUrl.value = false;
title = info.title;
description = info.description;
thumbnail = info.thumbnail;
icon = info.icon;
sitename = info.sitename;
player = info.player;
sensitive = info.sensitive ?? false;
title.value = info.title;
description.value = info.description;
thumbnail.value = info.thumbnail;
icon.value = info.icon;
sitename.value = info.sitename;
player.value = info.player;
sensitive.value = info.sensitive ?? false;
});
function adjustTweetHeight(message: any) {
@ -180,7 +180,7 @@ function adjustTweetHeight(message: any) {
if (embed?.method !== 'twttr.private.resize') return;
if (embed?.id !== embedId) return;
const height = embed?.params[0]?.height;
if (height) tweetHeight = height;
if (height) tweetHeight.value = height;
}
const openPlayer = (): void => {

Some files were not shown because too many files have changed in this diff Show more