Compare commits

...

20 commits

Author SHA1 Message Date
a858ee31c6 Added doc comment to alert()
Some checks failed
Check SPDX-License-Identifier / check-spdx-license-id (push) Has been cancelled
Check copyright year / check_copyright_year (push) Has been cancelled
Publish Docker image (develop) / Build (linux/amd64) (push) Has been cancelled
Publish Docker image (develop) / Build (linux/arm64) (push) Has been cancelled
Dockle / dockle (push) Has been cancelled
Lint / pnpm_install (push) Has been cancelled
Storybook / build (push) Has been cancelled
Test (frontend) / vitest (20.16.0) (push) Has been cancelled
Test (frontend) / e2e (chrome, 20.16.0) (push) Has been cancelled
Test (production install and build) / production (20.16.0) (push) Has been cancelled
Publish Docker image (develop) / merge (push) Has been cancelled
Lint / lint (backend) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Lint / lint (frontend-embed) (push) Has been cancelled
Lint / lint (frontend-shared) (push) Has been cancelled
Lint / lint (misskey-bubble-game) (push) Has been cancelled
Lint / lint (misskey-js) (push) Has been cancelled
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Lint / typecheck (misskey-js) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
2024-11-17 14:34:54 +02:00
0bd7ed8191 Added doc comment to toast() 2024-11-17 14:34:47 +02:00
ee574ae154 Added doc comment to pageWindow 2024-11-17 14:34:39 +02:00
a505f36252 Added doc comment to popup 2024-11-17 14:34:31 +02:00
a516383c66 Added internal comments to popup() 2024-11-17 14:34:21 +02:00
1613dafc39 Added docs to z-index generator. 2024-11-17 14:23:44 +02:00
8457fa9b3b Added docs to popup list and popup id counter. 2024-11-17 14:23:31 +02:00
2e51e779e7 Added docs idb proxy.
Some checks are pending
Check SPDX-License-Identifier / check-spdx-license-id (push) Waiting to run
Check copyright year / check_copyright_year (push) Waiting to run
Publish Docker image (develop) / Build (linux/amd64) (push) Waiting to run
Publish Docker image (develop) / Build (linux/arm64) (push) Waiting to run
Publish Docker image (develop) / merge (push) Blocked by required conditions
Dockle / dockle (push) Waiting to run
Storybook / build (push) Waiting to run
Test (production install and build) / production (20.16.0) (push) Waiting to run
2024-11-17 14:02:17 +02:00
7f6b486976 Added docs to Keys for localStorage. 2024-11-17 14:02:11 +02:00
8c1508fae4 Added docs to miLocalStorage. 2024-11-17 14:02:08 +02:00
aeb568664d Added docs (and observations) to notes count. 2024-11-17 14:02:02 +02:00
ecb990fb77 Added docs to $i, iAmModerator, iAmAdmin. 2024-11-17 14:01:53 +02:00
饺子w (Yumechi)
a11b77a415
fix(backend): Webhook Test一致性 (#14863)
* fix(backend): Webhook Test一致性

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

* UserWebhookPayload<'followed'> 修正

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>

---------

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
2024-11-12 09:51:18 +09:00
syuilo
1bc4f400c0 Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop 2024-11-11 16:35:23 +09:00
syuilo
458c72c153 Update about-misskey.vue 2024-11-11 16:35:13 +09:00
syuilo
6bd3ed2074
Update CHANGELOG.md 2024-11-10 15:10:04 +09:00
かっこかり
31e5f0bd09
fix(frontend): メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正 (#14928)
* fix(frontend): メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正

* Update MkSignupDialog.form.vue

* fix condition
2024-11-10 15:08:58 +09:00
かっこかり
e0a83e9c9e
Update CHANGELOG.md (書き方を揃える) 2024-11-09 15:57:10 +09:00
かっこかり
1496700b37
Update CHANGELOG.md
たぶんリリースワークフローはこうしないと認識してくれない
2024-11-09 15:51:49 +09:00
syuilo
00cbf9fe80
Update CONTRIBUTING.md 2024-11-09 14:09:02 +09:00
14 changed files with 219 additions and 32 deletions

View file

@ -1,8 +1,10 @@
## 2024.10.2
## 2024.11.0
### General
- Feat: コンテンツの表示にログインを必須にできるように
- Feat: 過去のノートを非公開化/フォロワーのみ表示可能にできるように
- Enhance: 依存関係の更新
- Enhance: l10nの更新
### Client
- Enhance: Bull DashboardでRelationship Queueの状態も確認できるように
@ -25,12 +27,14 @@
- Fix: リンク切れを修正
= Fix: ノート投稿ボタンにホバー時のスタイルが適用されていないのを修正
(Cherry-picked from https://github.com/taiyme/misskey/pull/305)
- Fix: メールアドレス登録有効化時の「完了」ダイアログボックスの表示条件を修正
### Server
- Enhance: 起動前の疎通チェックで、DBとメイン以外のRedisの疎通確認も行うように
(Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588)
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715)
- fix(backend): フォロワーへのメッセージの絵文字をemojisに含めるように
- Enhance: リモートユーザーの照会をオリジナルにリダイレクトするように
- Fix: フォロワーへのメッセージの絵文字をemojisに含めるように
- Fix: Nested proxy requestsを検出した際にブロックするように
[ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236)
- Fix: 招待コードの発行可能な残り数算出に使用すべきロールポリシーの値が違う問題を修正
@ -41,7 +45,7 @@
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712)
- Fix: FTT無効時にユーザーリストタイムラインが使用できない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709)
- Enhance: リモートユーザーの照会をオリジナルにリダイレクトするように
- Fix: User Webhookテスト機能のMock Payloadを修正
### Misskey.js
- Fix: Stream初期化時、別途WebSocketを指定する場合の型定義を修正

View file

@ -85,6 +85,7 @@ Be willing to comment on the good points and not just the things you want fixed
読んでおくといいやつ
- https://blog.lacolaco.net/posts/1e2cf439b3c2/
- https://konifar-zatsu.hatenadiary.jp/entry/2024/11/05/192421
### Review perspective
- Scope

View file

@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common';
import type { IActivity } from '@/core/activitypub/type.js';
import type { MiDriveFile } from '@/models/DriveFile.js';
import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
import type { MiWebhook, WebhookEventTypes, webhookEventTypes } from '@/models/Webhook.js';
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
@ -35,6 +35,7 @@ import type {
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
import { type UserWebhookPayload } from './UserWebhookService.js';
@Injectable()
export class QueueService {
@ -468,10 +469,10 @@ export class QueueService {
* @see UserWebhookDeliverProcessorService
*/
@bindThis
public userWebhookDeliver(
public userWebhookDeliver<T extends WebhookEventTypes>(
webhook: MiWebhook,
type: typeof webhookEventTypes[number],
content: unknown,
type: T,
content: UserWebhookPayload<T>,
opts?: { attempts?: number },
) {
const data: UserWebhookDeliverJobData = {

View file

@ -6,11 +6,23 @@
import { Inject, Injectable } from '@nestjs/common';
import * as Redis from 'ioredis';
import { type WebhooksRepository } from '@/models/_.js';
import { MiWebhook } from '@/models/Webhook.js';
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
import { GlobalEvents } from '@/core/GlobalEventService.js';
import type { OnApplicationShutdown } from '@nestjs/common';
import type { Packed } from '@/misc/json-schema.js';
export type UserWebhookPayload<T extends WebhookEventTypes> =
T extends 'note' | 'reply' | 'renote' |'mention' ? {
note: Packed<'Note'>,
} :
T extends 'follow' | 'unfollow' ? {
user: Packed<'UserDetailedNotMe'>,
} :
T extends 'followed' ? {
user: Packed<'UserLite'>,
} : never;
@Injectable()
export class UserWebhookService implements OnApplicationShutdown {

View file

@ -10,7 +10,7 @@ import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWeb
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js';
import { UserWebhookService } from '@/core/UserWebhookService.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
@ -306,10 +306,10 @@ export class WebhookTestService {
* - on
*/
@bindThis
public async testUserWebhook(
public async testUserWebhook<T extends WebhookEventTypes>(
params: {
webhookId: MiWebhook['id'],
type: WebhookEventTypes,
type: T,
override?: Partial<Omit<MiWebhook, 'id'>>,
},
sender: MiUser | null,
@ -321,7 +321,7 @@ export class WebhookTestService {
}
const webhook = webhooks[0];
const send = (contents: unknown) => {
const send = <U extends WebhookEventTypes>(type: U, contents: UserWebhookPayload<U>) => {
const merged = {
...webhook,
...params.override,
@ -329,7 +329,7 @@ export class WebhookTestService {
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加するチェック処理などをスキップする意図.
// また、Jobの試行回数も1回だけ.
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 });
};
const dummyNote1 = generateDummyNote({
@ -361,33 +361,40 @@ export class WebhookTestService {
switch (params.type) {
case 'note': {
send(toPackedNote(dummyNote1));
send('note', { note: toPackedNote(dummyNote1) });
break;
}
case 'reply': {
send(toPackedNote(dummyReply1));
send('reply', { note: toPackedNote(dummyReply1) });
break;
}
case 'renote': {
send(toPackedNote(dummyRenote1));
send('renote', { note: toPackedNote(dummyRenote1) });
break;
}
case 'mention': {
send(toPackedNote(dummyMention1));
send('mention', { note: toPackedNote(dummyMention1) });
break;
}
case 'follow': {
send(toPackedUserDetailedNotMe(dummyUser1));
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
break;
}
case 'followed': {
send(toPackedUserLite(dummyUser2));
send('followed', { user: toPackedUserLite(dummyUser2) });
break;
}
case 'unfollow': {
send(toPackedUserDetailedNotMe(dummyUser3));
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
break;
}
// まだ実装されていない (#9485)
case 'reaction': return;
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _exhaustiveAssertion: never = params.type;
return;
}
}
}

View file

@ -7,7 +7,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { beforeAll, describe, jest } from '@jest/globals';
import { WebhookTestService } from '@/core/WebhookTestService.js';
import { UserWebhookService } from '@/core/UserWebhookService.js';
import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
import { GlobalModule } from '@/GlobalModule.js';
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
@ -122,7 +122,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('note');
expect((calls[2] as any).id).toBe('dummy-note-1');
expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1');
});
test('reply', async () => {
@ -131,7 +131,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('reply');
expect((calls[2] as any).id).toBe('dummy-reply-1');
expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1');
});
test('renote', async () => {
@ -140,7 +140,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('renote');
expect((calls[2] as any).id).toBe('dummy-renote-1');
expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1');
});
test('mention', async () => {
@ -149,7 +149,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('mention');
expect((calls[2] as any).id).toBe('dummy-mention-1');
expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1');
});
test('follow', async () => {
@ -158,7 +158,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('follow');
expect((calls[2] as any).id).toBe('dummy-user-1');
expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1');
});
test('followed', async () => {
@ -167,7 +167,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('followed');
expect((calls[2] as any).id).toBe('dummy-user-2');
expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2');
});
test('unfollow', async () => {
@ -176,7 +176,7 @@ describe('WebhookTestService', () => {
const calls = queueService.userWebhookDeliver.mock.calls[0];
expect((calls[0] as any).id).toBe('dummy-webhook');
expect(calls[1]).toBe('unfollow');
expect((calls[2] as any).id).toBe('dummy-user-3');
expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3');
});
describe('NoSuchWebhookError', () => {

View file

@ -22,23 +22,66 @@ type Account = Misskey.entities.MeDetailed & { token: string };
const accountData = miLocalStorage.getItem('account');
// TODO: 外部からはreadonlyに
/**
* Reactive state for the current account. "I" as in "I am logged in".
* Initialized from local storage if available, otherwise null.
*
* @type {Account | null}
*/
export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
/**
* Whether the current account is a moderator.
*
* @type {boolean}
*/
export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
/**
* Whether the current account is an administrator.
*
* @type {boolean}
*/
export const iAmAdmin = $i != null && $i.isAdmin;
/**
* Whether it is necessary to sign in; checks if the current
* account is null and throws an error if so.
*
* @throws {Error} If the current account is null
* @returns {Account} The current account
*/
export function signinRequired() {
if ($i == null) throw new Error('signin required');
return $i;
}
/**
* Extracts the current number of notes from the current account.
*
* Note: This appears to only be used for the "notes1" achievement.
*
* Also, separating it like this might cause counts to get out-of-sync.
*/
export let notesCount = $i == null ? 0 : $i.notesCount;
/**
* Increments the number of notes by one.
*
* Documentation TODO: What about $i.notesCount? Why not increment that?
*/
export function incNotesCount() {
notesCount++;
}
export async function signout() {
if (!$i) return;
// If we're not signed in, there's nothing to do.
if (!$i) {
// Error log:
console.error('signout() called when not signed in');
return;
}
waiting();
miLocalStorage.removeItem('account');

View file

@ -277,7 +277,7 @@ async function onSubmit(): Promise<void> {
return null;
});
if (res) {
if (res && res.ok) {
if (res.status === 204 || instance.emailRequiredForSignup) {
os.alert({
type: 'success',
@ -295,6 +295,8 @@ async function onSubmit(): Promise<void> {
await login(resJson.token);
}
}
} else {
onSignupApiError();
}
submitting.value = false;

View file

@ -3,6 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* A typesafe enum of keys for localStorage.
*/
export type Keys =
'v' |
'lastVersion' |
@ -44,16 +47,45 @@ export type Keys =
// セッション毎に廃棄されるLocalStorage代替セーフモードなどで使用できそう
//const safeSessionStorage = new Map<Keys, string>();
/**
* A utility object for interacting with the browser's localStorage.
*
* It's mostly a small wrapper around window.localStorage, but it validates
* keys with a typesafe enum, and provides a few convenience methods for JSON.
*/
export const miLocalStorage = {
/**
* Retrieves an item from localStorage.
* @param {Keys} key - The key of the item to retrieve.
* @returns {string | null} The value of the item, or null if the item does not exist.
*/
getItem: (key: Keys): string | null => {
return window.localStorage.getItem(key);
},
/**
* Stores an item in localStorage.
* @param {Keys} key - The key of the item to store.
* @param {string} value - The value of the item to store.
*/
setItem: (key: Keys, value: string): void => {
window.localStorage.setItem(key, value);
},
/**
* Removes an item from localStorage.
* @param {Keys} key - The key of the item to remove.
*/
removeItem: (key: Keys): void => {
window.localStorage.removeItem(key);
},
/**
* Retrieves an item from localStorage and parses it as JSON.
* @param {Keys} key - The key of the item to retrieve.
* @returns {any | undefined} The parsed value of the item, or undefined if the item does not exist.
*/
getItemAsJson: (key: Keys): any | undefined => {
const item = miLocalStorage.getItem(key);
if (item === null) {
@ -61,6 +93,12 @@ export const miLocalStorage = {
}
return JSON.parse(item);
},
/**
* Stores an item in localStorage as a JSON string.
* @param {Keys} key - The key of the item to store.
* @param {any} value - The value of the item to store.
*/
setItemAsJson: (key: Keys, value: any): void => {
miLocalStorage.setItem(key, JSON.stringify(value));
},

View file

@ -136,7 +136,16 @@ export function promiseDialog<T extends Promise<any>>(
return promise;
}
/**
* Counter for generating unique popup IDs.
* @type {number}
*/
let popupIdCount = 0;
/**
* A reactive list of the currently opened popups. This is used in a Vue component
* in a v-for loop to render the popups.
*/
export const popups = ref<{
id: number;
component: Component;
@ -144,12 +153,23 @@ export const popups = ref<{
events: Record<string, any>;
}[]>([]);
/**
* An object containing z-index values for different priority levels.
*/
const zIndexes = {
veryLow: 500000,
low: 1000000,
middle: 2000000,
high: 3000000,
};
/**
* Claims a z-index value for a given priority level.
* Increments the z-index value for the specified priority by 100 and returns the new value.
*
* @param {keyof typeof zIndexes} [priority='low'] - The priority level for which to claim a z-index.
* @returns {number} The new z-index value for the specified priority.
*/
export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
zIndexes[priority] += 100;
return zIndexes[priority];
@ -177,6 +197,15 @@ type EmitsExtractor<T> = {
[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
};
/**
* Opens a popup with the specified component, props, and events.
*
* @template T - The type of the component.
* @param {T} component - The Vue component to display in the popup.
* @param {ComponentProps<T>} props - The props to pass to the component.
* @param {ComponentEmit<T>} [events={}] - The events to bind to the component.
* @returns {{ dispose: () => void }} An object containing a dispose function to close the popup.
*/
export function popup<T extends Component>(
component: T,
props: ComponentProps<T>,
@ -184,13 +213,18 @@ export function popup<T extends Component>(
): { dispose: () => void } {
markRaw(component);
// Generate a unique ID for this popup.
const id = ++popupIdCount;
// On disposal, remove this popup from the list of open popups.
const dispose = () => {
// このsetTimeoutが無いと挙動がおかしくなる(autocompleteが閉じなくなる)。Vueのバグ
window.setTimeout(() => {
popups.value = popups.value.filter(p => p.id !== id);
}, 0);
};
// Bundle the component, props, and events into a state object.
const state = {
component,
props,
@ -198,13 +232,19 @@ export function popup<T extends Component>(
id,
};
// Add the popup to the list of open popups.
popups.value.push(state);
// Return a function that can be called to close the popup.
return {
dispose,
};
}
/**
* Open the page with the given path in a pop-up window.
* @param path The path of the page to open.
*/
export function pageWindow(path: string) {
const { dispose } = popup(MkPageWindow, {
initialPath: path,
@ -213,6 +253,11 @@ export function pageWindow(path: string) {
});
}
/**
* Displays a toast message to the user.
*
* @param {string} message - The message to display in the toast.
*/
export function toast(message: string) {
const { dispose } = popup(MkToast, {
message,
@ -221,6 +266,15 @@ export function toast(message: string) {
});
}
/**
* Displays an alert dialog to the user.
*
* @param {Object} props - The properties for the alert dialog.
* @param {'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question'} [props.type] - The type of the alert.
* @param {string} [props.title] - The title of the alert dialog.
* @param {string} [props.text] - The text content of the alert dialog.
* @returns {Promise<void>} A promise that resolves when the alert dialog is closed.
*/
export function alert(props: {
type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
title?: string;

View file

@ -272,6 +272,9 @@ const patronsWithIcon = [{
}, {
name: 'Yatoigawa',
icon: 'https://assets.misskey-hub.net/patrons/505e3568885a4a488431a8f22b4553d0.jpg',
}, {
name: '秋瀬カヲル',
icon: 'https://assets.misskey-hub.net/patrons/0f22aeb866484f4fa51db6721e3f9847.jpg',
}];
const patrons = [
@ -380,6 +383,7 @@ const patrons = [
'ケモナーのケシン',
'こまつぶり',
'まゆつな空高',
'asata',
];
const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));

View file

@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton>
</div>
<div :class="$style.switchBox">
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton>
</div>
<div :class="$style.switchBox">

View file

@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch>
<MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch>
<MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch>
<MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch>
<MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch>
</div>
</FormSection>

View file

@ -26,6 +26,7 @@ if (window.Cypress) {
console.log('Cypress detected. It will use localStorage.');
}
// Check for the availability of indexedDB.
if (idbAvailable) {
await iset('idb-test', 'test')
.catch(err => {
@ -37,16 +38,36 @@ if (idbAvailable) {
console.error('indexedDB is unavailable. It will use localStorage.');
}
/**
* Get a value from indexedDB (or localStorage as a fallback).
*
* @param key The key of the item to retrieve.
*
* @returns The value of the item.
*/
export async function get(key: string) {
if (idbAvailable) return iget(key);
return miLocalStorage.getItemAsJson(`${PREFIX}${key}`);
}
/**
* Set a value in indexedDB (or localStorage as a fallback).
*
* @param {string} key - The key of the item to set.
* @param {any} val - The value of the item to set.
* @returns {Promise<void>} - A promise that resolves when the value has been set.`
*/
export async function set(key: string, val: any) {
if (idbAvailable) return iset(key, val);
return miLocalStorage.setItemAsJson(`${PREFIX}${key}`, val);
}
/**
* Delete a value from indexedDB (or localStorage as a fallback).
*
* @param {string} key - The key of the item to delete.
* @returns {Promise<void>} - A promise that resolves when the value has been deleted.
*/
export async function del(key: string) {
if (idbAvailable) return idel(key);
return miLocalStorage.removeItem(`${PREFIX}${key}`);