summaryrefslogtreecommitdiff
path: root/packages/frontend/src/scripts
diff options
context:
space:
mode:
authorMarie <marie@kaifa.ch>2024-01-09 02:57:57 +0100
committerMarie <marie@kaifa.ch>2024-01-09 02:57:57 +0100
commit7552cea69ae21b31799d54b246dcd45e96654926 (patch)
tree389031f25fee72180157869a9e60c31704e198a9 /packages/frontend/src/scripts
parentmerge: additional authorised fetch logging (#328) (diff)
parentfeat(ci): api.jsonのバリデーションチェックCIを追加 (#12950) (diff)
downloadsharkey-7552cea69ae21b31799d54b246dcd45e96654926.tar.gz
sharkey-7552cea69ae21b31799d54b246dcd45e96654926.tar.bz2
sharkey-7552cea69ae21b31799d54b246dcd45e96654926.zip
merge: upstream
Diffstat (limited to 'packages/frontend/src/scripts')
-rw-r--r--packages/frontend/src/scripts/achievements.ts16
-rw-r--r--packages/frontend/src/scripts/aiscript/api.ts13
-rw-r--r--packages/frontend/src/scripts/aiscript/ui.ts4
-rw-r--r--packages/frontend/src/scripts/clicker-game.ts6
-rw-r--r--packages/frontend/src/scripts/drop-and-fusion-engine.ts400
-rw-r--r--packages/frontend/src/scripts/emojilist.ts7
-rw-r--r--packages/frontend/src/scripts/form.ts18
-rw-r--r--packages/frontend/src/scripts/gen-search-query.ts2
-rw-r--r--packages/frontend/src/scripts/get-drive-file-menu.ts9
-rw-r--r--packages/frontend/src/scripts/get-note-menu.ts53
-rw-r--r--packages/frontend/src/scripts/get-note-versions-menu.ts3
-rw-r--r--packages/frontend/src/scripts/get-user-menu.ts9
-rw-r--r--packages/frontend/src/scripts/install-plugin.ts3
-rw-r--r--packages/frontend/src/scripts/lookup-user.ts5
-rw-r--r--packages/frontend/src/scripts/lookup.ts5
-rw-r--r--packages/frontend/src/scripts/misskey-api.ts (renamed from packages/frontend/src/scripts/api.ts)26
-rw-r--r--packages/frontend/src/scripts/select-file.ts3
-rw-r--r--packages/frontend/src/scripts/sound.ts78
-rw-r--r--packages/frontend/src/scripts/upload.ts2
-rw-r--r--packages/frontend/src/scripts/upload/compress-config.ts4
-rw-r--r--packages/frontend/src/scripts/use-note-capture.ts5
21 files changed, 579 insertions, 92 deletions
diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts
index e7585fcf81..67d997f09b 100644
--- a/packages/frontend/src/scripts/achievements.ts
+++ b/packages/frontend/src/scripts/achievements.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { $i } from '@/account.js';
export const ACHIEVEMENT_TYPES = [
@@ -83,6 +83,8 @@ export const ACHIEVEMENT_TYPES = [
'brainDiver',
'smashTestNotificationButton',
'tutorialCompleted',
+ 'bubbleGameExplodingHead',
+ 'bubbleGameDoubleExplodingHead',
] as const;
export const ACHIEVEMENT_BADGES = {
@@ -466,6 +468,16 @@ export const ACHIEVEMENT_BADGES = {
bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))',
frame: 'bronze',
},
+ 'bubbleGameExplodingHead': {
+ img: '/fluent-emoji/1f92f.png',
+ bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))',
+ frame: 'bronze',
+ },
+ 'bubbleGameDoubleExplodingHead': {
+ img: '/fluent-emoji/1f92f.png',
+ bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))',
+ frame: 'silver',
+ },
/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
} as const satisfies Record<typeof ACHIEVEMENT_TYPES[number], {
img: string;
@@ -489,7 +501,7 @@ export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) {
window.setTimeout(() => {
claimingQueue.delete(type);
}, 500);
- os.api('i/claim-achievement', { name: type });
+ misskeyApi('i/claim-achievement', { name: type });
}
if (_DEV_) {
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 038ae23109..c13849cc8f 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -5,12 +5,23 @@
import { utils, values } from '@syuilo/aiscript';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { $i } from '@/account.js';
import { miLocalStorage } from '@/local-storage.js';
import { customEmojis } from '@/custom-emojis.js';
import { url, lang } from '@/config.js';
import { nyaize } from '@/scripts/nyaize.js';
+export function aiScriptReadline(q: string): Promise<string> {
+ return new Promise(ok => {
+ os.inputText({
+ title: q,
+ }).then(({ result: a }) => {
+ ok(a ?? '');
+ });
+ });
+}
+
export function createAiScriptEnv(opts) {
return {
USER_ID: $i ? values.STR($i.id) : values.NULL,
@@ -44,7 +55,7 @@ export function createAiScriptEnv(opts) {
if (typeof token.value !== 'string') throw new Error('invalid token');
}
const actualToken: string|null = token?.value ?? opts.token ?? null;
- return os.api(ep.value, utils.valToJs(param), actualToken).then(res => {
+ return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => {
return utils.jsToVal(res);
}, err => {
return values.ERROR('request_failed', utils.jsToVal(err));
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index 08ba1e6d9b..215ac4cc69 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -218,7 +218,7 @@ function getTextOptions(def: values.Value | undefined): Omit<AsUiText, 'id' | 't
};
}
-function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'type'> {
+function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiMfm, 'id' | 'type'> {
utils.assertObject(def);
const text = def.value.get('text');
@@ -241,7 +241,7 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
color: color?.value,
font: font?.value,
onClickEv: (evId: string) => {
- if (onClickEv) call(onClickEv, values.STR(evId));
+ if (onClickEv) call(onClickEv, [values.STR(evId)]);
},
};
}
diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts
index 5ad076e5ef..360bea903c 100644
--- a/packages/frontend/src/scripts/clicker-game.ts
+++ b/packages/frontend/src/scripts/clicker-game.ts
@@ -4,7 +4,7 @@
*/
import { ref, computed } from 'vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
type SaveData = {
gameVersion: number;
@@ -23,7 +23,7 @@ let prev = '';
export async function load() {
try {
- saveData.value = await os.api('i/registry/get', {
+ saveData.value = await misskeyApi('i/registry/get', {
scope: ['clickerGame'],
key: 'saveData',
});
@@ -63,7 +63,7 @@ export async function save() {
const current = JSON.stringify(saveData.value);
if (current === prev) return;
- await os.api('i/registry/set', {
+ await misskeyApi('i/registry/set', {
scope: ['clickerGame'],
key: 'saveData',
value: saveData.value,
diff --git a/packages/frontend/src/scripts/drop-and-fusion-engine.ts b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
new file mode 100644
index 0000000000..b6e735ddf2
--- /dev/null
+++ b/packages/frontend/src/scripts/drop-and-fusion-engine.ts
@@ -0,0 +1,400 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { EventEmitter } from 'eventemitter3';
+import * as Matter from 'matter-js';
+import * as sound from '@/scripts/sound.js';
+
+export type Mono = {
+ id: string;
+ level: number;
+ size: number;
+ shape: 'circle' | 'rectangle';
+ score: number;
+ dropCandidate: boolean;
+ sfxPitch: number;
+ img: string;
+ imgSize: number;
+ spriteScale: number;
+};
+
+const PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
+
+export class DropAndFusionGame extends EventEmitter<{
+ changeScore: (newScore: number) => void;
+ changeCombo: (newCombo: number) => void;
+ changeStock: (newStock: { id: string; mono: Mono }[]) => void;
+ dropped: () => void;
+ fusioned: (x: number, y: number, scoreDelta: number) => void;
+ monoAdded: (mono: Mono) => void;
+ gameOver: () => void;
+}> {
+ private COMBO_INTERVAL = 1000;
+ public readonly DROP_INTERVAL = 500;
+ public readonly PLAYAREA_MARGIN = 25;
+ private STOCK_MAX = 4;
+ private loaded = false;
+ private engine: Matter.Engine;
+ private render: Matter.Render;
+ private runner: Matter.Runner;
+ private overflowCollider: Matter.Body;
+ private isGameOver = false;
+
+ private gameWidth: number;
+ private gameHeight: number;
+ private monoDefinitions: Mono[] = [];
+ private monoTextures: Record<string, Blob> = {};
+ private monoTextureUrls: Record<string, string> = {};
+
+ /**
+ * フィールドに出ていて、かつ合体の対象となるアイテム
+ */
+ private activeBodyIds: Matter.Body['id'][] = [];
+
+ private latestDroppedBodyId: Matter.Body['id'] | null = null;
+
+ private latestDroppedAt = 0;
+ private latestFusionedAt = 0;
+ private stock: { id: string; mono: Mono }[] = [];
+
+ private _combo = 0;
+ private get combo() {
+ return this._combo;
+ }
+ private set combo(value: number) {
+ this._combo = value;
+ this.emit('changeCombo', value);
+ }
+
+ private _score = 0;
+ private get score() {
+ return this._score;
+ }
+ private set score(value: number) {
+ this._score = value;
+ this.emit('changeScore', value);
+ }
+
+ private comboIntervalId: number | null = null;
+
+ constructor(opts: {
+ canvas: HTMLCanvasElement;
+ width: number;
+ height: number;
+ monoDefinitions: Mono[];
+ }) {
+ super();
+
+ this.gameWidth = opts.width;
+ this.gameHeight = opts.height;
+ this.monoDefinitions = opts.monoDefinitions;
+
+ this.engine = Matter.Engine.create({
+ constraintIterations: 2 * PHYSICS_QUALITY_FACTOR,
+ positionIterations: 6 * PHYSICS_QUALITY_FACTOR,
+ velocityIterations: 4 * PHYSICS_QUALITY_FACTOR,
+ gravity: {
+ x: 0,
+ y: 1,
+ },
+ timing: {
+ timeScale: 2,
+ },
+ enableSleeping: false,
+ });
+
+ this.render = Matter.Render.create({
+ engine: this.engine,
+ canvas: opts.canvas,
+ options: {
+ width: this.gameWidth,
+ height: this.gameHeight,
+ background: 'transparent', // transparent to hide
+ wireframeBackground: 'transparent', // transparent to hide
+ wireframes: false,
+ showSleeping: false,
+ pixelRatio: Math.max(2, window.devicePixelRatio),
+ },
+ });
+
+ Matter.Render.run(this.render);
+
+ this.runner = Matter.Runner.create();
+ Matter.Runner.run(this.runner, this.engine);
+
+ this.engine.world.bodies = [];
+
+ //#region walls
+ const WALL_OPTIONS: Matter.IChamferableBodyDefinition = {
+ isStatic: true,
+ friction: 0.7,
+ slop: 1.0,
+ render: {
+ strokeStyle: 'transparent',
+ fillStyle: 'transparent',
+ },
+ };
+
+ const thickness = 100;
+ Matter.Composite.add(this.engine.world, [
+ Matter.Bodies.rectangle(this.gameWidth / 2, this.gameHeight + (thickness / 2) - this.PLAYAREA_MARGIN, this.gameWidth, thickness, WALL_OPTIONS),
+ Matter.Bodies.rectangle(this.gameWidth + (thickness / 2) - this.PLAYAREA_MARGIN, this.gameHeight / 2, thickness, this.gameHeight, WALL_OPTIONS),
+ Matter.Bodies.rectangle(-((thickness / 2) - this.PLAYAREA_MARGIN), this.gameHeight / 2, thickness, this.gameHeight, WALL_OPTIONS),
+ ]);
+ //#endregion
+
+ this.overflowCollider = Matter.Bodies.rectangle(this.gameWidth / 2, 0, this.gameWidth, 200, {
+ isStatic: true,
+ isSensor: true,
+ render: {
+ strokeStyle: 'transparent',
+ fillStyle: 'transparent',
+ },
+ });
+ Matter.Composite.add(this.engine.world, this.overflowCollider);
+
+ // fit the render viewport to the scene
+ Matter.Render.lookAt(this.render, {
+ min: { x: 0, y: 0 },
+ max: { x: this.gameWidth, y: this.gameHeight },
+ });
+ }
+
+ private createBody(mono: Mono, x: number, y: number) {
+ const options: Matter.IBodyDefinition = {
+ label: mono.id,
+ //density: 0.0005,
+ density: mono.size / 1000,
+ restitution: 0.2,
+ frictionAir: 0.01,
+ friction: 0.7,
+ frictionStatic: 5,
+ slop: 1.0,
+ //mass: 0,
+ render: {
+ sprite: {
+ texture: mono.img,
+ xScale: (mono.size / mono.imgSize) * mono.spriteScale,
+ yScale: (mono.size / mono.imgSize) * mono.spriteScale,
+ },
+ },
+ };
+ if (mono.shape === 'circle') {
+ return Matter.Bodies.circle(x, y, mono.size / 2, options);
+ } else if (mono.shape === 'rectangle') {
+ return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options);
+ } else {
+ throw new Error('unrecognized shape');
+ }
+ }
+
+ private fusion(bodyA: Matter.Body, bodyB: Matter.Body) {
+ const now = Date.now();
+ if (this.latestFusionedAt > now - this.COMBO_INTERVAL) {
+ this.combo++;
+ } else {
+ this.combo = 1;
+ }
+ this.latestFusionedAt = now;
+
+ // TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する?
+ const newX = (bodyA.position.x + bodyB.position.x) / 2;
+ const newY = (bodyA.position.y + bodyB.position.y) / 2;
+
+ Matter.Composite.remove(this.engine.world, [bodyA, bodyB]);
+ this.activeBodyIds = this.activeBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
+
+ const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!;
+ const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1);
+
+ if (nextMono) {
+ const body = this.createBody(nextMono, newX, newY);
+ Matter.Composite.add(this.engine.world, body);
+
+ // 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする
+ window.setTimeout(() => {
+ this.activeBodyIds.push(body.id);
+ }, 100);
+
+ const comboBonus = 1 + ((this.combo - 1) / 5);
+ const additionalScore = Math.round(currentMono.score * comboBonus);
+ this.score += additionalScore;
+
+ // TODO: 効果音再生はコンポーネント側の責務なので移動する
+ const pan = ((newX / this.gameWidth) - 0.5) * 2;
+ sound.playUrl('/client-assets/drop-and-fusion/bubble2.mp3', 1, pan, nextMono.sfxPitch);
+
+ this.emit('monoAdded', nextMono);
+ this.emit('fusioned', newX, newY, additionalScore);
+ } else {
+ //const VELOCITY = 30;
+ //for (let i = 0; i < 10; i++) {
+ // const body = createBody(FRUITS.find(x => x.level === (1 + Math.floor(Math.random() * 3)))!, x + ((Math.random() * VELOCITY) - (VELOCITY / 2)), y + ((Math.random() * VELOCITY) - (VELOCITY / 2)));
+ // Matter.Composite.add(world, body);
+ // bodies.push(body);
+ //}
+ //sound.playUrl({
+ // type: 'syuilo/bubble2',
+ // volume: 1,
+ //});
+ }
+ }
+
+ private gameOver() {
+ this.isGameOver = true;
+ Matter.Runner.stop(this.runner);
+ this.emit('gameOver');
+ }
+
+ /** テクスチャをすべてキャッシュする */
+ private async loadMonoTextures() {
+ async function loadSingleMonoTexture(mono: Mono, game: DropAndFusionGame) {
+ // Matter-js内にキャッシュがある場合はスキップ
+ if (game.render.textures[mono.img]) return;
+ console.log('loading', mono.img);
+
+ let src = mono.img;
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (game.monoTextureUrls[mono.img]) {
+ src = game.monoTextureUrls[mono.img];
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ } else if (game.monoTextures[mono.img]) {
+ src = URL.createObjectURL(game.monoTextures[mono.img]);
+ game.monoTextureUrls[mono.img] = src;
+ } else {
+ const res = await fetch(mono.img);
+ const blob = await res.blob();
+ game.monoTextures[mono.img] = blob;
+ src = URL.createObjectURL(blob);
+ game.monoTextureUrls[mono.img] = src;
+ }
+
+ const image = new Image();
+ image.src = src;
+ game.render.textures[mono.img] = image;
+ }
+
+ return Promise.all(this.monoDefinitions.map(x => loadSingleMonoTexture(x, this)));
+ }
+
+ public start() {
+ if (!this.loaded) throw new Error('game is not loaded yet');
+
+ for (let i = 0; i < this.STOCK_MAX; i++) {
+ this.stock.push({
+ id: Math.random().toString(),
+ mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+ });
+ }
+ this.emit('changeStock', this.stock);
+
+ // TODO: fusion予約状態のアイテムは光らせるなどの演出をすると楽しそう
+ let fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = [];
+
+ const minCollisionEnergyForSound = 2.5;
+ const maxCollisionEnergyForSound = 9;
+ const soundPitchMax = 4;
+ const soundPitchMin = 0.5;
+
+ Matter.Events.on(this.engine, 'collisionStart', (event) => {
+ for (const pairs of event.pairs) {
+ const { bodyA, bodyB } = pairs;
+ if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) {
+ if (bodyA.id === this.latestDroppedBodyId || bodyB.id === this.latestDroppedBodyId) {
+ continue;
+ }
+ this.gameOver();
+ break;
+ }
+ const shouldFusion = (bodyA.label === bodyB.label) && !fusionReservedPairs.some(x => x.bodyA.id === bodyA.id || x.bodyA.id === bodyB.id || x.bodyB.id === bodyA.id || x.bodyB.id === bodyB.id);
+ if (shouldFusion) {
+ if (this.activeBodyIds.includes(bodyA.id) && this.activeBodyIds.includes(bodyB.id)) {
+ this.fusion(bodyA, bodyB);
+ } else {
+ fusionReservedPairs.push({ bodyA, bodyB });
+ window.setTimeout(() => {
+ fusionReservedPairs = fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id);
+ this.fusion(bodyA, bodyB);
+ }, 100);
+ }
+ } else {
+ const energy = pairs.collision.depth;
+ if (energy > minCollisionEnergyForSound) {
+ // TODO: 効果音再生はコンポーネント側の責務なので移動する
+ const vol = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4;
+ const pan = ((((bodyA.position.x + bodyB.position.x) / 2) / this.gameWidth) - 0.5) * 2;
+ const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10)));
+ sound.playUrl('/client-assets/drop-and-fusion/poi1.mp3', vol, pan, pitch);
+ }
+ }
+ }
+ });
+
+ this.comboIntervalId = window.setInterval(() => {
+ if (this.latestFusionedAt < Date.now() - this.COMBO_INTERVAL) {
+ this.combo = 0;
+ }
+ }, 500);
+ }
+
+ public async load() {
+ await this.loadMonoTextures();
+ this.loaded = true;
+ }
+
+ public getTextureImageUrl(mono: Mono) {
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ if (this.monoTextureUrls[mono.img]) {
+ return this.monoTextureUrls[mono.img];
+
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ } else if (this.monoTextures[mono.img]) {
+ // Gameクラス内にキャッシュがある場合はそれを使う
+ const out = URL.createObjectURL(this.monoTextures[mono.img]);
+ this.monoTextureUrls[mono.img] = out;
+ return out;
+ } else {
+ return mono.img;
+ }
+ }
+
+ public getActiveMonos() {
+ return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined);
+ }
+
+ public drop(_x: number) {
+ if (this.isGameOver) return;
+ if (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL) {
+ return;
+ }
+ const st = this.stock.shift()!;
+ this.stock.push({
+ id: Math.random().toString(),
+ mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(Math.random() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+ });
+ this.emit('changeStock', this.stock);
+
+ const x = Math.min(this.gameWidth - this.PLAYAREA_MARGIN - (st.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (st.mono.size / 2), _x));
+ const body = this.createBody(st.mono, x, 50 + st.mono.size / 2);
+ Matter.Composite.add(this.engine.world, body);
+ this.activeBodyIds.push(body.id);
+ this.latestDroppedBodyId = body.id;
+ this.latestDroppedAt = Date.now();
+ this.emit('dropped');
+ this.emit('monoAdded', st.mono);
+
+ // TODO: 効果音再生はコンポーネント側の責務なので移動する
+ const pan = ((x / this.gameWidth) - 0.5) * 2;
+ sound.playUrl('/client-assets/drop-and-fusion/poi2.mp3', 1, pan);
+ }
+
+ public dispose() {
+ if (this.comboIntervalId) window.clearInterval(this.comboIntervalId);
+ Matter.Render.stop(this.render);
+ Matter.Runner.stop(this.runner);
+ Matter.World.clear(this.engine.world, false);
+ Matter.Engine.clear(this.engine);
+ }
+}
diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend/src/scripts/emojilist.ts
index 8885bf4b7f..4bd8bf94be 100644
--- a/packages/frontend/src/scripts/emojilist.ts
+++ b/packages/frontend/src/scripts/emojilist.ts
@@ -36,7 +36,8 @@ for (let i = 0; i < emojilist.length; i++) {
export const emojiCharByCategory = _charGroupByCategory;
export function getEmojiName(char: string): string | null {
- const idx = _indexByChar.get(char);
+ // Colorize it because emojilist.json assumes that
+ const idx = _indexByChar.get(colorizeEmoji(char));
if (idx == null) {
return null;
} else {
@@ -44,6 +45,10 @@ export function getEmojiName(char: string): string | null {
}
}
+export function colorizeEmoji(char: string) {
+ return char.length === 1 ? `${char}\uFE0F` : char;
+}
+
export interface CustomEmojiFolderTree {
value: string;
category: string;
diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts
index 222fd9b0b7..f7e0369419 100644
--- a/packages/frontend/src/scripts/form.ts
+++ b/packages/frontend/src/scripts/form.ts
@@ -3,7 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-type EnumItem = string | {label: string; value: string;};
+type EnumItem = string | {
+ label: string;
+ value: string;
+};
+
export type FormItem = {
label?: string;
type: 'string';
@@ -38,14 +42,21 @@ export type FormItem = {
}[];
} | {
label?: string;
+ type: 'range';
+ default: number | null;
+ step: number;
+ min: number;
+ max: number;
+} | {
+ label?: string;
type: 'object';
default: Record<string, unknown> | null;
- hidden: true;
+ hidden: boolean;
} | {
label?: string;
type: 'array';
default: unknown[] | null;
- hidden: true;
+ hidden: boolean;
};
export type Form = Record<string, FormItem>;
@@ -55,6 +66,7 @@ type GetItemType<Item extends FormItem> =
Item['type'] extends 'number' ? number :
Item['type'] extends 'boolean' ? boolean :
Item['type'] extends 'radio' ? unknown :
+ Item['type'] extends 'range' ? number :
Item['type'] extends 'enum' ? string :
Item['type'] extends 'array' ? unknown[] :
Item['type'] extends 'object' ? Record<string, unknown>
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
index 54654980f2..068cd9cd93 100644
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ b/packages/frontend/src/scripts/gen-search-query.ts
@@ -18,7 +18,7 @@ export async function genSearchQuery(v: any, q: string) {
host = at;
}
} else {
- const user = await v.os.api('users/show', Misskey.acct.parse(at)).catch(x => null);
+ const user = await v.api('users/show', Misskey.acct.parse(at)).catch(x => null);
if (user) {
userId = user.id;
} else {
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index d6a5b00c0b..b30f87c913 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -8,6 +8,7 @@ import { defineAsyncComponent } from 'vue';
import { i18n } from '@/i18n.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { MenuItem } from '@/types/menu.js';
import { defaultStore } from '@/store.js';
@@ -18,7 +19,7 @@ function rename(file: Misskey.entities.DriveFile) {
default: file.name,
}).then(({ canceled, result: name }) => {
if (canceled) return;
- os.api('drive/files/update', {
+ misskeyApi('drive/files/update', {
fileId: file.id,
name: name,
});
@@ -31,7 +32,7 @@ function describe(file: Misskey.entities.DriveFile) {
file: file,
}, {
done: caption => {
- os.api('drive/files/update', {
+ misskeyApi('drive/files/update', {
fileId: file.id,
comment: caption.length === 0 ? null : caption,
});
@@ -40,7 +41,7 @@ function describe(file: Misskey.entities.DriveFile) {
}
function toggleSensitive(file: Misskey.entities.DriveFile) {
- os.api('drive/files/update', {
+ misskeyApi('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive,
}).catch(err => {
@@ -69,7 +70,7 @@ async function deleteFile(file: Misskey.entities.DriveFile) {
});
if (canceled) return;
- os.api('drive/files/delete', {
+ misskeyApi('drive/files/delete', {
fileId: file.id,
});
}
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index a409f1b775..f6db7c48a6 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -10,6 +10,7 @@ import { $i } from '@/account.js';
import { i18n } from '@/i18n.js';
import { instance } from '@/instance.js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { url } from '@/config.js';
import { defaultStore, noteActions } from '@/store.js';
@@ -40,7 +41,7 @@ export async function getNoteClipMenu(props: {
action: () => {
claimAchievement('noteClipped1');
os.promiseDialog(
- os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
+ misskeyApi('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
null,
async (err) => {
if (err.id === '734806c4-542c-463a-9311-15c512803965') {
@@ -156,7 +157,7 @@ export function getNoteMenu(props: {
}).then(({ canceled }) => {
if (canceled) return;
- os.api('notes/delete', {
+ misskeyApi('notes/delete', {
noteId: appearNote.id,
});
@@ -173,7 +174,7 @@ export function getNoteMenu(props: {
}).then(({ canceled }) => {
if (canceled) return;
- os.api('notes/delete', {
+ misskeyApi('notes/delete', {
noteId: appearNote.id,
});
@@ -265,7 +266,7 @@ export function getNoteMenu(props: {
async function translate(): Promise<void> {
if (props.translation.value != null) return;
props.translating.value = true;
- const res = await os.api('notes/translate', {
+ const res = await misskeyApi('notes/translate', {
noteId: appearNote.id,
targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
});
@@ -275,7 +276,7 @@ export function getNoteMenu(props: {
let menu: MenuItem[];
if ($i) {
- const statePromise = os.api('notes/state', {
+ const statePromise = misskeyApi('notes/state', {
noteId: appearNote.id,
});
@@ -355,7 +356,7 @@ export function getNoteMenu(props: {
icon: 'ph-user ph-bold ph-lg',
text: i18n.ts.user,
children: async () => {
- const user = appearNote.userId === $i?.id ? $i : await os.api('users/show', { userId: appearNote.userId });
+ const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
const { menu, cleanup } = getUserMenu(user);
cleanups.push(cleanup);
return menu;
@@ -377,6 +378,42 @@ export function getNoteMenu(props: {
]
: []
),
+ ...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [
+ { type: 'divider' },
+ {
+ type: 'parent' as const,
+ icon: 'ti ti-device-tv',
+ text: i18n.ts.channel,
+ children: async () => {
+ const channelChildMenu = [] as MenuItem[];
+
+ const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id });
+
+ if (channel.pinnedNoteIds.includes(appearNote.id)) {
+ channelChildMenu.push({
+ icon: 'ti ti-pinned-off',
+ text: i18n.ts.unpin,
+ action: () => os.apiWithDialog('channels/update', {
+ channelId: appearNote.channel!.id,
+ pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
+ }),
+ });
+ } else {
+ channelChildMenu.push({
+ icon: 'ti ti-pin',
+ text: i18n.ts.pin,
+ action: () => os.apiWithDialog('channels/update', {
+ channelId: appearNote.channel!.id,
+ pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
+ }),
+ });
+ }
+ return channelChildMenu;
+ },
+ },
+ ]
+ : []
+ ),
...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
{ type: 'divider' },
appearNote.userId === $i.id ? {
@@ -497,7 +534,7 @@ export function getRenoteMenu(props: {
}
if (!props.mock) {
- os.api('notes/create', {
+ misskeyApi('notes/create', {
renoteId: appearNote.id,
channelId: appearNote.channelId,
}).then(() => {
@@ -542,7 +579,7 @@ export function getRenoteMenu(props: {
}
if (!props.mock) {
- os.api('notes/create', {
+ misskeyApi('notes/create', {
localOnly,
visibility,
renoteId: appearNote.id,
diff --git a/packages/frontend/src/scripts/get-note-versions-menu.ts b/packages/frontend/src/scripts/get-note-versions-menu.ts
index 46e3bab3a7..e05e2e91be 100644
--- a/packages/frontend/src/scripts/get-note-versions-menu.ts
+++ b/packages/frontend/src/scripts/get-note-versions-menu.ts
@@ -2,6 +2,7 @@ import { Ref, defineAsyncComponent } from 'vue';
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
+import { misskeyApi } from './misskey-api.js';
import { MenuItem } from '@/types/menu.js';
import { dateTimeFormat } from './intl-const.js';
@@ -30,7 +31,7 @@ export async function getNoteVersionsMenu(props: {
}
const menu: MenuItem[] = [];
- const statePromise = os.api('notes/versions', {
+ const statePromise = misskeyApi('notes/versions', {
noteId: appearNote.id,
});
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 67bc781aef..35eeded7e7 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -10,13 +10,14 @@ import { i18n } from '@/i18n.js';
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
import { host, url } from '@/config.js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { defaultStore, userActions } from '@/store.js';
import { $i, iAmModerator } from '@/account.js';
-import { mainRouter } from '@/router.js';
-import { Router } from '@/nirax.js';
+import { IRouter } from '@/nirax.js';
import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
+import { mainRouter } from '@/global/router/main.js';
-export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router = mainRouter) {
+export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
const meId = $i ? $i.id : null;
const cleanups = [] as (() => void)[];
@@ -131,7 +132,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
}
async function editMemo(): Promise<void> {
- const userDetailed = await os.api('users/show', {
+ const userDetailed = await misskeyApi('users/show', {
userId: user.id,
});
const { canceled, result } = await os.form(i18n.ts.editMemo, {
diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts
index 1310a0dc73..ee218df018 100644
--- a/packages/frontend/src/scripts/install-plugin.ts
+++ b/packages/frontend/src/scripts/install-plugin.ts
@@ -10,6 +10,7 @@ import { Interpreter, Parser, utils } from '@syuilo/aiscript';
import type { Plugin } from '@/store.js';
import { ColdDeviceStorage } from '@/store.js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
export type AiScriptPluginMeta = {
@@ -110,7 +111,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
}, {
done: async result => {
const { name, permissions } = result;
- const { token } = await os.api('miauth/gen-token', {
+ const { token } = await misskeyApi('miauth/gen-token', {
session: null,
name: name,
permission: permissions,
diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/lookup-user.ts
index a35fe898e4..9ae5eccb7c 100644
--- a/packages/frontend/src/scripts/lookup-user.ts
+++ b/packages/frontend/src/scripts/lookup-user.ts
@@ -6,6 +6,7 @@
import * as Misskey from 'misskey-js';
import { i18n } from '@/i18n.js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
export async function lookupUser() {
const { canceled, result } = await os.inputText({
@@ -17,8 +18,8 @@ export async function lookupUser() {
os.pageWindow(`/admin/user/${user.id}`);
};
- const usernamePromise = os.api('users/show', Misskey.acct.parse(result));
- const idPromise = os.api('users/show', { userId: result });
+ const usernamePromise = misskeyApi('users/show', Misskey.acct.parse(result));
+ const idPromise = misskeyApi('users/show', { userId: result });
let _notFound = false;
const notFound = () => {
if (_notFound) {
diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts
index 979f40f038..ddcfd8852e 100644
--- a/packages/frontend/src/scripts/lookup.ts
+++ b/packages/frontend/src/scripts/lookup.ts
@@ -4,9 +4,10 @@
*/
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router.js';
import { Router } from '@/nirax.js';
+import { mainRouter } from '@/global/router/main.js';
export async function lookup(router?: Router) {
const _router = router ?? mainRouter;
@@ -28,7 +29,7 @@ export async function lookup(router?: Router) {
}
if (query.startsWith('https://')) {
- const promise = os.api('ap/show', {
+ const promise = misskeyApi('ap/show', {
uri: query,
});
diff --git a/packages/frontend/src/scripts/api.ts b/packages/frontend/src/scripts/misskey-api.ts
index 8f3a163938..337fa15113 100644
--- a/packages/frontend/src/scripts/api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -10,12 +10,17 @@ import { $i } from '@/account.js';
export const pendingApiRequestsCount = ref(0);
// Implements Misskey.api.ApiClient.request
-export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
+export function misskeyApi<
+ ResT = void,
+ E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+ P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+ _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
endpoint: E,
data: P = {} as any,
token?: string | null | undefined,
signal?: AbortSignal,
-): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
+): Promise<_ResT> {
if (endpoint.includes('://')) throw new Error('invalid endpoint');
pendingApiRequestsCount.value++;
@@ -23,7 +28,7 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
pendingApiRequestsCount.value--;
};
- const promise = new Promise<Misskey.Endpoints[E]['res'] | void>((resolve, reject) => {
+ const promise = new Promise<_ResT>((resolve, reject) => {
// Append a credential
if ($i) (data as any).i = $i.token;
if (token !== undefined) (data as any).i = token;
@@ -44,7 +49,7 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
- resolve();
+ resolve(undefined as _ResT); // void -> undefined
} else {
reject(body.error);
}
@@ -57,10 +62,15 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
}
// Implements Misskey.api.ApiClient.request
-export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
+export function misskeyApiGet<
+ ResT = void,
+ E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+ P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+ _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
endpoint: E,
data: P = {} as any,
-): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
+): Promise<_ResT> {
pendingApiRequestsCount.value++;
const onFinally = () => {
@@ -69,7 +79,7 @@ export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endp
const query = new URLSearchParams(data as any);
- const promise = new Promise<Misskey.Endpoints[E]['res'] | void>((resolve, reject) => {
+ const promise = new Promise<_ResT>((resolve, reject) => {
// Send request
window.fetch(`${apiUrl}/${endpoint}?${query}`, {
method: 'GET',
@@ -81,7 +91,7 @@ export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endp
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
- resolve();
+ resolve(undefined as _ResT); // void -> undefined
} else {
reject(body.error);
}
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index 674c762fac..7a2d5a9b9f 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -6,6 +6,7 @@
import { ref } from 'vue';
import * as Misskey from 'misskey-js';
import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
import { useStream } from '@/stream.js';
import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js';
@@ -65,7 +66,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
}
});
- os.api('drive/files/upload-from-url', {
+ misskeyApi('drive/files/upload-from-url', {
url: url,
folderId: defaultStore.state.uploadFolder,
marker,
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index 2f7545ef0d..690c342c85 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -5,7 +5,6 @@
import type { SoundStore } from '@/store.js';
import { defaultStore } from '@/store.js';
-import * as os from '@/os.js';
let ctx: AudioContext;
const cache = new Map<string, AudioBuffer>();
@@ -89,63 +88,35 @@ export type OperationType = typeof operationTypes[number];
/**
* 音声を読み込む
- * @param soundStore サウンド設定
+ * @param url url
* @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする
*/
-export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) {
+export async function loadAudio(url: string, options?: { useCache?: boolean; }) {
if (_DEV_) console.log('loading audio. opts:', options);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
- if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
- return;
- }
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (ctx == null) {
ctx = new AudioContext();
}
if (options?.useCache ?? true) {
- if (soundStore.type === '_driveFile_' && cache.has(soundStore.fileId)) {
+ if (cache.has(url)) {
if (_DEV_) console.log('use cache');
- return cache.get(soundStore.fileId) as AudioBuffer;
- } else if (cache.has(soundStore.type)) {
- if (_DEV_) console.log('use cache');
- return cache.get(soundStore.type) as AudioBuffer;
+ return cache.get(url) as AudioBuffer;
}
}
let response: Response;
- if (soundStore.type === '_driveFile_') {
- try {
- response = await fetch(soundStore.fileUrl);
- } catch (err) {
- try {
- // URLが変わっている可能性があるのでドライブ側からURLを取得するフォールバック
- const apiRes = await os.api('drive/files/show', {
- fileId: soundStore.fileId,
- });
- response = await fetch(apiRes.url);
- } catch (fbErr) {
- // それでも無理なら諦める
- return;
- }
- }
- } else {
- try {
- response = await fetch(`/client-assets/sounds/${soundStore.type}.mp3`);
- } catch (err) {
- return;
- }
+ try {
+ response = await fetch(url);
+ } catch (err) {
+ return;
}
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
if (options?.useCache ?? true) {
- if (soundStore.type === '_driveFile_') {
- cache.set(soundStore.fileId, audioBuffer);
- } else {
- cache.set(soundStore.type, audioBuffer);
- }
+ cache.set(url, audioBuffer);
}
return audioBuffer;
@@ -174,25 +145,46 @@ export function play(operationType: OperationType) {
* @param soundStore サウンド設定
*/
export async function playFile(soundStore: SoundStore) {
- const buffer = await loadAudio(soundStore);
+ if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
+ return;
+ }
+ const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
+ const buffer = await loadAudio(url);
if (!buffer) return;
- createSourceNode(buffer, soundStore.volume)?.start();
+ createSourceNode(buffer, soundStore.volume)?.soundSource.start();
}
-export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
+export async function playUrl(url: string, volume = 1, pan = 0, playbackRate = 1) {
+ const buffer = await loadAudio(url);
+ if (!buffer) return;
+ createSourceNode(buffer, volume, pan, playbackRate)?.soundSource.start();
+}
+
+export function createSourceNode(buffer: AudioBuffer, volume: number, pan = 0, playbackRate = 1): {
+ soundSource: AudioBufferSourceNode;
+ panNode: StereoPannerNode;
+ gainNode: GainNode;
+} | null {
const masterVolume = defaultStore.state.sound_masterVolume;
if (isMute() || masterVolume === 0 || volume === 0) {
return null;
}
+ const panNode = ctx.createStereoPanner();
+ panNode.pan.value = pan;
+
const gainNode = ctx.createGain();
gainNode.gain.value = masterVolume * volume;
const soundSource = ctx.createBufferSource();
soundSource.buffer = buffer;
- soundSource.connect(gainNode).connect(ctx.destination);
+ soundSource.playbackRate.value = playbackRate;
+ soundSource
+ .connect(panNode)
+ .connect(gainNode)
+ .connect(ctx.destination);
- return soundSource;
+ return { soundSource, panNode, gainNode };
}
/**
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index b896376ec8..14b667fd68 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -5,7 +5,7 @@
import { reactive, ref } from 'vue';
import * as Misskey from 'misskey-js';
-import { readAndCompressImage } from 'browser-image-resizer';
+import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
import { getCompressionConfig } from './upload/compress-config.js';
import { defaultStore } from '@/store.js';
import { apiUrl } from '@/config.js';
diff --git a/packages/frontend/src/scripts/upload/compress-config.ts b/packages/frontend/src/scripts/upload/compress-config.ts
index 2deb9cbb81..4775213c20 100644
--- a/packages/frontend/src/scripts/upload/compress-config.ts
+++ b/packages/frontend/src/scripts/upload/compress-config.ts
@@ -5,7 +5,7 @@
import isAnimated from 'is-file-animated';
import { isWebpSupported } from './isWebpSupported.js';
-import type { BrowserImageResizerConfig } from 'browser-image-resizer';
+import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
const compressTypeMap = {
'image/jpeg': { quality: 0.90, mimeType: 'image/webp' },
@@ -21,7 +21,7 @@ const compressTypeMapFallback = {
'image/svg+xml': { quality: 1, mimeType: 'image/png' },
} as const;
-export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfig | undefined> {
+export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfigWithConvertedOutput | undefined> {
const imgConfig = (isWebpSupported() ? compressTypeMap : compressTypeMapFallback)[file.type];
if (!imgConfig || await isAnimated(file)) {
return;
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index bcdba5455a..fb31fce1de 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -8,6 +8,7 @@ import * as Misskey from 'misskey-js';
import { useStream } from '@/stream.js';
import { $i } from '@/account.js';
import * as os from '@/os.js';
+import { misskeyApi } from './misskey-api.js';
export function useNoteCapture(props: {
rootEl: Ref<HTMLElement>;
@@ -32,7 +33,7 @@ export function useNoteCapture(props: {
// notes/show may throw if the current user can't see the note
try {
- const replyNote = await os.api('notes/show', {
+ const replyNote = await misskeyApi('notes/show', {
noteId: body.id,
});
@@ -100,7 +101,7 @@ export function useNoteCapture(props: {
case 'updated': {
try {
- const editedNote = await os.api('notes/show', {
+ const editedNote = await misskeyApi('notes/show', {
noteId: id,
});