Merge pull request MisskeyIO#355 from merge-upstream
|
@ -32,8 +32,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
- name: Install swagger-cli
|
- name: Install Redocly CLI
|
||||||
run: npm i -g swagger-cli
|
run: npm i -g @redocly/cli
|
||||||
- run: corepack enable
|
- run: corepack enable
|
||||||
- run: pnpm i --frozen-lockfile
|
- run: pnpm i --frozen-lockfile
|
||||||
- name: Check pnpm-lock.yaml
|
- name: Check pnpm-lock.yaml
|
||||||
|
@ -43,4 +43,4 @@ jobs:
|
||||||
- name: Build and generate
|
- name: Build and generate
|
||||||
run: pnpm build && pnpm --filter backend generate-api-json
|
run: pnpm build && pnpm --filter backend generate-api-json
|
||||||
- name: Validation
|
- name: Validation
|
||||||
run: swagger-cli validate ./packages/backend/built/api.json
|
run: npx @redocly/cli lint --extends=minimal ./packages/backend/built/api.json
|
||||||
|
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
### Client
|
### Client
|
||||||
- Feat: 新しいゲームを追加
|
- Feat: 新しいゲームを追加
|
||||||
|
- Feat: 絵文字の詳細ダイアログを追加
|
||||||
|
- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
|
||||||
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
|
||||||
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
|
||||||
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
|
- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
|
||||||
|
@ -35,7 +37,10 @@
|
||||||
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
- Enhance: 連合先のレートリミットに引っかかった際にリトライするようになりました
|
||||||
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
|
- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
|
||||||
- Enhance: クリップをエクスポートできるように
|
- Enhance: クリップをエクスポートできるように
|
||||||
|
- Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新
|
||||||
- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
|
- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
|
||||||
|
- Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更
|
||||||
|
- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
|
||||||
|
|
||||||
## 2023.12.2
|
## 2023.12.2
|
||||||
|
|
||||||
|
|
|
@ -1207,6 +1207,8 @@ export interface Locale {
|
||||||
"replay": string;
|
"replay": string;
|
||||||
"replaying": string;
|
"replaying": string;
|
||||||
"ranking": string;
|
"ranking": string;
|
||||||
|
"lastNDays": string;
|
||||||
|
"backToTitle": string;
|
||||||
"abuseReportCategory": string;
|
"abuseReportCategory": string;
|
||||||
"selectCategory": string;
|
"selectCategory": string;
|
||||||
"reportComplete": string;
|
"reportComplete": string;
|
||||||
|
|
|
@ -1204,6 +1204,8 @@ showReplay: "リプレイを見る"
|
||||||
replay: "リプレイ"
|
replay: "リプレイ"
|
||||||
replaying: "リプレイ中"
|
replaying: "リプレイ中"
|
||||||
ranking: "ランキング"
|
ranking: "ランキング"
|
||||||
|
lastNDays: "直近{n}日"
|
||||||
|
backToTitle: "タイトルへ"
|
||||||
abuseReportCategory: "通報の種類"
|
abuseReportCategory: "通報の種類"
|
||||||
selectCategory: "カテゴリを選択"
|
selectCategory: "カテゴリを選択"
|
||||||
reportComplete: "通報完了"
|
reportComplete: "通報完了"
|
||||||
|
|
|
@ -331,6 +331,9 @@ export class NoteCreateService implements OnApplicationShutdown {
|
||||||
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
|
||||||
}
|
}
|
||||||
data.text = data.text.trim();
|
data.text = data.text.trim();
|
||||||
|
if (data.text === '') {
|
||||||
|
data.text = null;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.text = null;
|
data.text = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const paramDef = {
|
||||||
untilId: { type: 'string', format: 'misskey:id' },
|
untilId: { type: 'string', format: 'misskey:id' },
|
||||||
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
|
||||||
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
|
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
|
||||||
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
|
sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
|
|
@ -60,6 +60,7 @@ export const paramDef = {
|
||||||
'-firstRetrievedAt',
|
'-firstRetrievedAt',
|
||||||
'+latestRequestReceivedAt',
|
'+latestRequestReceivedAt',
|
||||||
'-latestRequestReceivedAt',
|
'-latestRequestReceivedAt',
|
||||||
|
null,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -109,13 +109,13 @@ export const meta = {
|
||||||
items: {
|
items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: [
|
enum: [
|
||||||
"ble",
|
'ble',
|
||||||
"cable",
|
'cable',
|
||||||
"hybrid",
|
'hybrid',
|
||||||
"internal",
|
'internal',
|
||||||
"nfc",
|
'nfc',
|
||||||
"smart-card",
|
'smart-card',
|
||||||
"usb",
|
'usb',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -129,8 +129,8 @@ export const meta = {
|
||||||
authenticatorAttachment: {
|
authenticatorAttachment: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: [
|
enum: [
|
||||||
"cross-platform",
|
'cross-platform',
|
||||||
"platform",
|
'platform',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
requireResidentKey: {
|
requireResidentKey: {
|
||||||
|
@ -139,9 +139,9 @@ export const meta = {
|
||||||
userVerification: {
|
userVerification: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
enum: [
|
enum: [
|
||||||
"discouraged",
|
'discouraged',
|
||||||
"preferred",
|
'preferred',
|
||||||
"required",
|
'required',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -150,10 +150,11 @@ export const meta = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
enum: [
|
enum: [
|
||||||
"direct",
|
'direct',
|
||||||
"enterprise",
|
'enterprise',
|
||||||
"indirect",
|
'indirect',
|
||||||
"none",
|
'none',
|
||||||
|
null,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
extensions: {
|
extensions: {
|
||||||
|
|
|
@ -34,11 +34,10 @@ describe('api:notes/create', () => {
|
||||||
.toBe(VALID);
|
.toBe(VALID);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO
|
test('null post', () => {
|
||||||
//test('null post', () => {
|
expect(v({ text: null }))
|
||||||
// expect(v({ text: null }))
|
.toBe(INVALID);
|
||||||
// .toBe(INVALID);
|
});
|
||||||
//});
|
|
||||||
|
|
||||||
test('0 characters post', () => {
|
test('0 characters post', () => {
|
||||||
expect(v({ text: '' }))
|
expect(v({ text: '' }))
|
||||||
|
@ -49,6 +48,11 @@ describe('api:notes/create', () => {
|
||||||
expect(v({ text: await tooLong }))
|
expect(v({ text: await tooLong }))
|
||||||
.toBe(INVALID);
|
.toBe(INVALID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('whitespace-only post', () => {
|
||||||
|
expect(v({ text: ' ' }))
|
||||||
|
.toBe(INVALID);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('cw', () => {
|
describe('cw', () => {
|
||||||
|
|
|
@ -173,13 +173,33 @@ export const paramDef = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// (re)note with text, files and poll are optional
|
// (re)note with text, files and poll are optional
|
||||||
anyOf: [
|
if: {
|
||||||
{ required: ['text'] },
|
properties: {
|
||||||
{ required: ['renoteId'] },
|
renoteId: {
|
||||||
{ required: ['fileIds'] },
|
type: 'null',
|
||||||
{ required: ['mediaIds'] },
|
},
|
||||||
{ required: ['poll'] },
|
fileIds: {
|
||||||
],
|
type: 'null',
|
||||||
|
},
|
||||||
|
mediaIds: {
|
||||||
|
type: 'null',
|
||||||
|
},
|
||||||
|
poll: {
|
||||||
|
type: 'null',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
then: {
|
||||||
|
properties: {
|
||||||
|
text: {
|
||||||
|
type: 'string',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
|
pattern: '[^\\s]+',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['text'],
|
||||||
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
|
||||||
|
|
||||||
export function genOpenapiSpec(config: Config) {
|
export function genOpenapiSpec(config: Config) {
|
||||||
const spec = {
|
const spec = {
|
||||||
openapi: '3.0.0',
|
openapi: '3.1.0',
|
||||||
|
|
||||||
info: {
|
info: {
|
||||||
version: config.version,
|
version: config.version,
|
||||||
|
@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
|
const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res') : {};
|
||||||
|
|
||||||
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
|
let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
|
const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
|
||||||
const schema = { ...endpoint.params };
|
const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param') };
|
||||||
|
|
||||||
if (endpoint.meta.requireFile) {
|
if (endpoint.meta.requireFile) {
|
||||||
schema.properties = {
|
schema.properties = {
|
||||||
|
@ -210,7 +210,9 @@ export function genOpenapiSpec(config: Config) {
|
||||||
};
|
};
|
||||||
|
|
||||||
spec.paths['/' + endpoint.name] = {
|
spec.paths['/' + endpoint.name] = {
|
||||||
...(endpoint.meta.allowGet ? { get: info } : {}),
|
...(endpoint.meta.allowGet ? {
|
||||||
|
get: info,
|
||||||
|
} : {}),
|
||||||
post: info,
|
post: info,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,32 +6,35 @@
|
||||||
import type { Schema } from '@/misc/json-schema.js';
|
import type { Schema } from '@/misc/json-schema.js';
|
||||||
import { refs } from '@/misc/json-schema.js';
|
import { refs } from '@/misc/json-schema.js';
|
||||||
|
|
||||||
export function convertSchemaToOpenApiSchema(schema: Schema) {
|
export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res') {
|
||||||
// optional, refはスキーマ定義に含まれないので分離しておく
|
// optional, nullable, refはスキーマ定義に含まれないので分離しておく
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { optional, ref, ...res }: any = schema;
|
const { optional, nullable, ref, ...res }: any = schema;
|
||||||
|
|
||||||
if (schema.type === 'object' && schema.properties) {
|
if (schema.type === 'object' && schema.properties) {
|
||||||
const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
if (type === 'res') {
|
||||||
if (required.length > 0) {
|
const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
|
||||||
|
if (required.length > 0) {
|
||||||
// 空配列は許可されない
|
// 空配列は許可されない
|
||||||
res.required = required;
|
res.required = required;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const k of Object.keys(schema.properties)) {
|
for (const k of Object.keys(schema.properties)) {
|
||||||
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
|
res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.type === 'array' && schema.items) {
|
if (schema.type === 'array' && schema.items) {
|
||||||
res.items = convertSchemaToOpenApiSchema(schema.items);
|
res.items = convertSchemaToOpenApiSchema(schema.items, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema);
|
for (const o of ['anyOf', 'oneOf', 'allOf'] as const) {
|
||||||
if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema);
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema);
|
if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type));
|
||||||
|
}
|
||||||
|
|
||||||
if (schema.ref) {
|
if (type === 'res' && schema.ref) {
|
||||||
const $ref = `#/components/schemas/${schema.ref}`;
|
const $ref = `#/components/schemas/${schema.ref}`;
|
||||||
if (schema.nullable || schema.optional) {
|
if (schema.nullable || schema.optional) {
|
||||||
res.allOf = [{ $ref }];
|
res.allOf = [{ $ref }];
|
||||||
|
@ -40,6 +43,14 @@ export function convertSchemaToOpenApiSchema(schema: Schema) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (schema.nullable) {
|
||||||
|
if (Array.isArray(schema.type) && !schema.type.includes('null')) {
|
||||||
|
res.type.push('null');
|
||||||
|
} else if (typeof schema.type === 'string') {
|
||||||
|
res.type = [res.type, 'null'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +83,6 @@ export const schemas = {
|
||||||
},
|
},
|
||||||
|
|
||||||
...Object.fromEntries(
|
...Object.fromEntries(
|
||||||
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]),
|
Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res')]),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -43,7 +43,7 @@ html
|
||||||
}
|
}
|
||||||
|
|
||||||
body
|
body
|
||||||
a#a(href=`https://${host}` target="_blank")
|
a#a(href=`https://${host}` rel="noopener" target="_blank")
|
||||||
header#banner(style=`background-image: url(${meta.bannerUrl})`)
|
header#banner(style=`background-image: url(${meta.bannerUrl})`)
|
||||||
div#title= meta.name || host
|
div#title= meta.name || host
|
||||||
div#content
|
div#content
|
||||||
|
|
|
@ -136,6 +136,19 @@ describe('Note', () => {
|
||||||
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('引用renoteで空白文字のみで構成されたtextにするとレスポンスがtext: nullになる', async () => {
|
||||||
|
const bobPost = await post(bob, {
|
||||||
|
text: 'test',
|
||||||
|
});
|
||||||
|
const res = await api('/notes/create', {
|
||||||
|
text: ' ',
|
||||||
|
renoteId: bobPost.id,
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
assert.strictEqual(res.status, 200);
|
||||||
|
assert.strictEqual(res.body.createdNote.text, null);
|
||||||
|
});
|
||||||
|
|
||||||
test('visibility: followersでrenoteできる', async () => {
|
test('visibility: followersでrenoteできる', async () => {
|
||||||
const createRes = await api('/notes/create', {
|
const createRes = await api('/notes/create', {
|
||||||
text: 'test',
|
text: 'test',
|
||||||
|
|
After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 607 B |
After Width: | Height: | Size: 522 B |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 96 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 59 KiB |
|
@ -0,0 +1,102 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
|
||||||
|
<template #header>:{{ emoji.name }}:</template>
|
||||||
|
<template #default>
|
||||||
|
<MkSpacer>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 1em;">
|
||||||
|
<div :class="$style.emojiImgWrapper">
|
||||||
|
<MkCustomEmoji :name="emoji.name" :normal="true" style="height: 100%;"></MkCustomEmoji>
|
||||||
|
</div>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.name }}</template>
|
||||||
|
<template #value>{{ emoji.name }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.tags }}</template>
|
||||||
|
<template #value>
|
||||||
|
<div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
|
||||||
|
<div v-else :class="$style.aliases">
|
||||||
|
<span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
|
||||||
|
{{ alias }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.category }}</template>
|
||||||
|
<template #value>{{ emoji.category ?? i18n.ts.none }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.sensitive }}</template>
|
||||||
|
<template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.localOnly }}</template>
|
||||||
|
<template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue>
|
||||||
|
<template #key>{{ i18n.ts.license }}</template>
|
||||||
|
<template #value>{{ emoji.license ?? i18n.ts.none }}</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
<MkKeyValue :copy="emoji.url">
|
||||||
|
<template #key>{{ i18n.ts.emojiUrl }}</template>
|
||||||
|
<template #value>
|
||||||
|
<a :href="emoji.url" rel="nofollow noopener" target="_blank">{{ emoji.url }}</a>
|
||||||
|
</template>
|
||||||
|
</MkKeyValue>
|
||||||
|
</div>
|
||||||
|
</MkSpacer>
|
||||||
|
</template>
|
||||||
|
</MkModalWindow>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import { defineProps, shallowRef } from 'vue';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkModalWindow from '@/components/MkModalWindow.vue';
|
||||||
|
import MkKeyValue from '@/components/MkKeyValue.vue';
|
||||||
|
const props = defineProps<{
|
||||||
|
emoji: Misskey.entities.EmojiDetailed,
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
|
||||||
|
(ev: 'cancel'): void;
|
||||||
|
(ev: 'closed'): void;
|
||||||
|
}>();
|
||||||
|
const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
|
||||||
|
const cancel = () => {
|
||||||
|
emit('cancel');
|
||||||
|
dialogEl.value!.close();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.emojiImgWrapper {
|
||||||
|
max-width: 100%;
|
||||||
|
height: 40cqh;
|
||||||
|
background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aliases {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alias {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background-color: var(--X5);
|
||||||
|
border: solid 1px var(--divider);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
|
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
|
||||||
import { Chart } from 'chart.js';
|
import { Chart } from 'chart.js';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||||
|
@ -23,9 +24,16 @@ import { initChart } from '@/scripts/init-chart.js';
|
||||||
|
|
||||||
initChart();
|
initChart();
|
||||||
|
|
||||||
const props = defineProps<{
|
export type HeatmapSource = 'active-users' | 'notes' | 'ap-requests-inbox-received' | 'ap-requests-deliver-succeeded' | 'ap-requests-deliver-failed';
|
||||||
src: string;
|
|
||||||
}>();
|
const props = withDefaults(defineProps<{
|
||||||
|
src: HeatmapSource;
|
||||||
|
user?: Misskey.entities.User;
|
||||||
|
label?: string;
|
||||||
|
}>(), {
|
||||||
|
user: undefined,
|
||||||
|
label: '',
|
||||||
|
});
|
||||||
|
|
||||||
const rootEl = shallowRef<HTMLDivElement>(null);
|
const rootEl = shallowRef<HTMLDivElement>(null);
|
||||||
const chartEl = shallowRef<HTMLCanvasElement>(null);
|
const chartEl = shallowRef<HTMLCanvasElement>(null);
|
||||||
|
@ -75,8 +83,13 @@ async function renderChart() {
|
||||||
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
|
const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
|
||||||
values = raw.readWrite;
|
values = raw.readWrite;
|
||||||
} else if (props.src === 'notes') {
|
} else if (props.src === 'notes') {
|
||||||
const raw = await misskeyApi('charts/notes', { limit: chartLimit, span: 'day' });
|
if (props.user) {
|
||||||
values = raw.local.inc;
|
const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
||||||
|
values = raw.inc;
|
||||||
|
} else {
|
||||||
|
const raw = await misskeyApi('charts/notes', { limit: chartLimit, span: 'day' });
|
||||||
|
values = raw.local.inc;
|
||||||
|
}
|
||||||
} else if (props.src === 'ap-requests-inbox-received') {
|
} else if (props.src === 'ap-requests-inbox-received') {
|
||||||
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
|
const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
|
||||||
values = raw.inboxReceived;
|
values = raw.inboxReceived;
|
||||||
|
@ -105,7 +118,7 @@ async function renderChart() {
|
||||||
type: 'matrix',
|
type: 'matrix',
|
||||||
data: {
|
data: {
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'Read & Write',
|
label: props.label,
|
||||||
data: format(values),
|
data: format(values),
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
borderWidth: 0,
|
borderWidth: 0,
|
||||||
|
@ -128,6 +141,9 @@ async function renderChart() {
|
||||||
const a = c.chart.chartArea ?? {};
|
const a = c.chart.chartArea ?? {};
|
||||||
return (a.bottom - a.top) / 7 - marginEachCell;
|
return (a.bottom - a.top) / 7 - marginEachCell;
|
||||||
},
|
},
|
||||||
|
/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
|
||||||
|
}] satisfies ChartData[],
|
||||||
|
*/
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
@ -195,7 +211,7 @@ async function renderChart() {
|
||||||
},
|
},
|
||||||
label(context) {
|
label(context) {
|
||||||
const v = context.dataset.data[context.dataIndex];
|
const v = context.dataset.data[context.dataIndex];
|
||||||
return ['Active: ' + v.v];
|
return [v.v];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
//mode: 'index',
|
//mode: 'index',
|
||||||
|
|
|
@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
|
<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<div class="_panel" :class="$style.heatmap">
|
<div class="_panel" :class="$style.heatmap">
|
||||||
<MkHeatmap :src="heatmapSrc"/>
|
<MkHeatmap :src="heatmapSrc" :label="'Read & Write'"/>
|
||||||
</div>
|
</div>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import MkHeatmap from '@/components/MkHeatmap.vue';
|
import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
|
import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
|
||||||
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
|
import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
|
||||||
|
@ -103,7 +103,7 @@ initChart();
|
||||||
const chartLimit = 500;
|
const chartLimit = 500;
|
||||||
const chartSpan = ref<'hour' | 'day'>('hour');
|
const chartSpan = ref<'hour' | 'day'>('hour');
|
||||||
const chartSrc = ref('active-users');
|
const chartSrc = ref('active-users');
|
||||||
const heatmapSrc = ref('active-users');
|
const heatmapSrc = ref<HeatmapSource>('active-users');
|
||||||
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
const subDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ import * as os from '@/os.js';
|
||||||
const props = withDefaults(defineProps<{
|
const props = withDefaults(defineProps<{
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
value?: number;
|
value?: number | string;
|
||||||
}>(), {
|
}>(), {
|
||||||
value: 1,
|
value: 1,
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
class="_button"
|
class="_button"
|
||||||
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
|
||||||
@click="toggleReaction()"
|
@click="toggleReaction()"
|
||||||
|
@contextmenu.prevent.stop="menu"
|
||||||
>
|
>
|
||||||
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]"/>
|
||||||
<span :class="$style.count">{{ count }}</span>
|
<span :class="$style.count">{{ count }}</span>
|
||||||
|
@ -21,6 +22,7 @@ import { computed, inject, onMounted, shallowRef, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
import XDetails from '@/components/MkReactionsViewer.details.vue';
|
||||||
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
import MkReactionIcon from '@/components/MkReactionIcon.vue';
|
||||||
|
import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import { useTooltip } from '@/scripts/use-tooltip.js';
|
import { useTooltip } from '@/scripts/use-tooltip.js';
|
||||||
|
@ -98,6 +100,22 @@ async function toggleReaction() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function menu(ev) {
|
||||||
|
if (!canToggle.value) return;
|
||||||
|
if (!props.reaction.includes(":")) return;
|
||||||
|
os.popupMenu([{
|
||||||
|
text: i18n.ts.info,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
action: async () => {
|
||||||
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
|
emoji: await misskeyApiGet('emoji', {
|
||||||
|
name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
}
|
||||||
|
|
||||||
function anime() {
|
function anime() {
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
if (!defaultStore.state.animation) return;
|
if (!defaultStore.state.animation) return;
|
||||||
|
|
|
@ -34,8 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ tosPrivacyPolicyLabel }}</template>
|
<template #label>{{ tosPrivacyPolicyLabel }}</template>
|
||||||
<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--success)"></i></template>
|
<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--success)"></i></template>
|
||||||
<div class="_gaps_s">
|
<div class="_gaps_s">
|
||||||
<div v-if="availableTos"><a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a></div>
|
<div v-if="availableTos"><a :href="instance.tosUrl" class="_link" rel="nofollow noopener" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a></div>
|
||||||
<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ti ti-external-link"></i></a></div>
|
<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl" class="_link" rel="nofollow noopener" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ti ti-external-link"></i></a></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MkSwitch :modelValue="agreeTosAndPrivacyPolicy" style="margin-top: 16px;" @update:modelValue="updateAgreeTosAndPrivacyPolicy">{{ i18n.ts.agree }}</MkSwitch>
|
<MkSwitch :modelValue="agreeTosAndPrivacyPolicy" style="margin-top: 16px;" @update:modelValue="updateAgreeTosAndPrivacyPolicy">{{ i18n.ts.agree }}</MkSwitch>
|
||||||
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
|
<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
|
||||||
<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
|
<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
|
||||||
|
|
||||||
<a href="https://misskey-hub.net/docs/for-users/onboarding/warning/" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
|
<a href="https://misskey-hub.net/docs/for-users/onboarding/warning/" class="_link" rel="nofollow noopener" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
|
||||||
|
|
||||||
<MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch>
|
<MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.divider"></div>
|
<div :class="$style.divider"></div>
|
||||||
<I18n :src="i18n.ts._initialTutorial._timeline.description3" tag="div" style="padding: 0 16px;">
|
<I18n :src="i18n.ts._initialTutorial._timeline.description3" tag="div" style="padding: 0 16px;">
|
||||||
<template #link>
|
<template #link>
|
||||||
<a href="https://misskey-hub.net/docs/for-users/features/timeline/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
<a href="https://misskey-hub.net/docs/for-users/features/timeline/" rel="nofollow noopener" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
|
|
||||||
|
|
|
@ -130,7 +130,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div>
|
<div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div>
|
||||||
<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
|
<I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;">
|
||||||
<template #link>
|
<template #link>
|
||||||
<a href="https://misskey-hub.net/docs/for-users/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
<a href="https://misskey-hub.net/docs/for-users/" rel="nofollow noopener" target="_blank" class="_link">{{ i18n.ts.help }}</a>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
|
<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="[$style.root, { [$style.inline]: inline }]">
|
<div :class="[$style.root, { [$style.inline]: inline }]">
|
||||||
<a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank">
|
<a v-if="external" :class="$style.main" class="_button" :href="to" rel="nofollow noopener" target="_blank">
|
||||||
<span :class="$style.icon"><slot name="icon"></slot></span>
|
<span :class="$style.icon"><slot name="icon"></slot></span>
|
||||||
<span :class="$style.text"><slot></slot></span>
|
<span :class="$style.text"><slot></slot></span>
|
||||||
<span :class="$style.suffix">
|
<span :class="$style.suffix">
|
||||||
|
|
|
@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
[$style.form_vertical]: chosen.place === 'vertical',
|
[$style.form_vertical]: chosen.place === 'vertical',
|
||||||
}]"
|
}]"
|
||||||
>
|
>
|
||||||
<a :href="chosen.url" target="_blank" :class="$style.link">
|
<a :href="chosen.url" rel="noopener" target="_blank" :class="$style.link">
|
||||||
<img :src="chosen.imageUrl" :class="$style.img">
|
<img :src="chosen.imageUrl" :class="$style.img">
|
||||||
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
|
<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -24,9 +24,11 @@ import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import { customEmojisMap } from '@/custom-emojis.js';
|
import { customEmojisMap } from '@/custom-emojis.js';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import * as sound from '@/scripts/sound.js';
|
import * as sound from '@/scripts/sound.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -93,7 +95,19 @@ function onClick(ev: MouseEvent) {
|
||||||
react(`:${props.name}:`);
|
react(`:${props.name}:`);
|
||||||
sound.playMisskeySfx('reaction');
|
sound.playMisskeySfx('reaction');
|
||||||
},
|
},
|
||||||
}] : [])], ev.currentTarget ?? ev.target);
|
}] : []), {
|
||||||
|
text: i18n.ts.info,
|
||||||
|
icon: 'ti ti-info-circle',
|
||||||
|
action: async () => {
|
||||||
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
|
emoji: await misskeyApiGet('emoji', {
|
||||||
|
name: customEmojiName.value,
|
||||||
|
}),
|
||||||
|
}, {
|
||||||
|
anchor: ev.target,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}], ev.currentTarget ?? ev.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -59,7 +59,12 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
|
||||||
|
|
||||||
const validTime = (t: string | null | undefined) => {
|
const validTime = (t: string | null | undefined) => {
|
||||||
if (t == null) return null;
|
if (t == null) return null;
|
||||||
return t.match(/^[0-9.]+s$/) ? t : null;
|
return RegExp(/^[0-9.]+s$/).exec(t) ? t : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validColor = (c: string | null | undefined): string | null => {
|
||||||
|
if (c == null) return null;
|
||||||
|
return RegExp(/^[0-9a-f]{3,6}$/i).exec(c) ? c : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
|
const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
|
||||||
|
@ -240,17 +245,30 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'fg': {
|
case 'fg': {
|
||||||
let color = token.props.args.color;
|
let color = validColor(token.props.args.color);
|
||||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
color = color ?? 'f00';
|
||||||
style = `color: #${color}; overflow-wrap: anywhere;`;
|
style = `color: #${color}; overflow-wrap: anywhere;`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'bg': {
|
case 'bg': {
|
||||||
let color = token.props.args.color;
|
let color = validColor(token.props.args.color);
|
||||||
if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
|
color = color ?? 'f00';
|
||||||
style = `background-color: #${color}; overflow-wrap: anywhere;`;
|
style = `background-color: #${color}; overflow-wrap: anywhere;`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'border': {
|
||||||
|
let color = validColor(token.props.args.color);
|
||||||
|
color = color ? `#${color}` : 'var(--accent)';
|
||||||
|
let b_style = token.props.args.style;
|
||||||
|
if (
|
||||||
|
!['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset']
|
||||||
|
.includes(b_style)
|
||||||
|
) b_style = 'solid';
|
||||||
|
const width = parseFloat(token.props.args.width ?? '1');
|
||||||
|
const radius = parseFloat(token.props.args.radius ?? '0');
|
||||||
|
style = `border: ${width}px ${b_style} ${color}; border-radius: ${radius}px`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'ruby': {
|
case 'ruby': {
|
||||||
if (token.children.length === 1) {
|
if (token.children.length === 1) {
|
||||||
const child = token.children[0];
|
const child = token.children[0];
|
||||||
|
|
|
@ -112,4 +112,4 @@ export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error
|
||||||
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg';
|
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg';
|
||||||
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
|
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';
|
||||||
|
|
||||||
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
|
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
|
||||||
|
|
|
@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button>
|
<button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button>
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align: center;">
|
<div style="text-align: center;">
|
||||||
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/about-misskey/" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>
|
{{ i18n.ts._aboutMisskey.about }}<br><a href="https://misskey-hub.net/docs/about-misskey/" rel="nofollow noopener" target="_blank" class="_link">{{ i18n.ts.learnMore }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="$i != null" style="text-align: center;">
|
<div v-if="$i != null" style="text-align: center;">
|
||||||
<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
|
<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Misskey</MkButton>
|
||||||
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||||
<MkSpacer v-if="file" :contentMax="600" :marginMin="16" :marginMax="32">
|
<MkSpacer v-if="file" :contentMax="600" :marginMin="16" :marginMax="32">
|
||||||
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
|
<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
|
||||||
<a class="thumbnail" :href="file.url" target="_blank">
|
<a class="thumbnail" :href="file.url" rel="nofollow noopener" target="_blank">
|
||||||
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
|
<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
|
||||||
</a>
|
</a>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -4,16 +4,16 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :contentMax="800">
|
<Transition
|
||||||
<Transition
|
:enterActiveClass="$style.transition_zoom_enterActive"
|
||||||
:enterActiveClass="$style.transition_zoom_enterActive"
|
:leaveActiveClass="$style.transition_zoom_leaveActive"
|
||||||
:leaveActiveClass="$style.transition_zoom_leaveActive"
|
:enterFromClass="$style.transition_zoom_enterFrom"
|
||||||
:enterFromClass="$style.transition_zoom_enterFrom"
|
:leaveToClass="$style.transition_zoom_leaveTo"
|
||||||
:leaveToClass="$style.transition_zoom_leaveTo"
|
:moveClass="$style.transition_zoom_move"
|
||||||
:moveClass="$style.transition_zoom_move"
|
mode="out-in"
|
||||||
mode="out-in"
|
>
|
||||||
>
|
<MkSpacer v-if="!gameStarted" :contentMax="800">
|
||||||
<div v-if="!gameStarted" :class="$style.root">
|
<div :class="$style.root">
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<div :class="$style.frame" style="text-align: center;">
|
<div :class="$style.frame" style="text-align: center;">
|
||||||
<div :class="$style.frameInner">
|
<div :class="$style.frameInner">
|
||||||
|
@ -26,6 +26,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<MkSelect v-model="gameMode">
|
<MkSelect v-model="gameMode">
|
||||||
<option value="normal">NORMAL</option>
|
<option value="normal">NORMAL</option>
|
||||||
<option value="square">SQUARE</option>
|
<option value="square">SQUARE</option>
|
||||||
|
<option value="yen">YEN</option>
|
||||||
|
<option value="sweets">SWEETS (β)</option>
|
||||||
</MkSelect>
|
</MkSelect>
|
||||||
<MkButton primary gradate large rounded inline @click="start">{{ i18n.ts.start }}</MkButton>
|
<MkButton primary gradate large rounded inline @click="start">{{ i18n.ts.start }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,12 +44,12 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div :class="$style.frame">
|
<div :class="$style.frame">
|
||||||
<div :class="$style.frameInner">
|
<div :class="$style.frameInner">
|
||||||
<div class="_gaps_s" style="padding: 16px;">
|
<div class="_gaps_s" style="padding: 16px;">
|
||||||
<div><b>{{ i18n.ts.ranking }}</b> ({{ gameMode }})</div>
|
<div><b>{{ i18n.t('lastNDays', { n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode }})</div>
|
||||||
<div v-if="ranking" class="_gaps_s">
|
<div v-if="ranking" class="_gaps_s">
|
||||||
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
|
<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
|
||||||
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
|
||||||
<MkUserName :user="r.user" :nowrap="true"/>
|
<MkUserName :user="r.user" :nowrap="true"/>
|
||||||
<b style="margin-left: auto;">{{ r.score.toLocaleString() }} pt</b>
|
<b style="margin-left: auto;">{{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>{{ i18n.ts.loading }}</div>
|
<div v-else>{{ i18n.ts.loading }}</div>
|
||||||
|
@ -77,15 +79,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
</MkSpacer>
|
||||||
<XGame :gameMode="gameMode" :mute="mute" @end="onGameEnd"/>
|
<XGame v-else :gameMode="gameMode" :mute="mute" @end="onGameEnd"/>
|
||||||
</div>
|
</Transition>
|
||||||
</Transition>
|
|
||||||
</MkSpacer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { computed, ref, watch } from 'vue';
|
||||||
import XGame from './drop-and-fusion.game.vue';
|
import XGame from './drop-and-fusion.game.vue';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
||||||
import MkButton from '@/components/MkButton.vue';
|
import MkButton from '@/components/MkButton.vue';
|
||||||
|
@ -94,7 +94,7 @@ import MkSelect from '@/components/MkSelect.vue';
|
||||||
import MkSwitch from '@/components/MkSwitch.vue';
|
import MkSwitch from '@/components/MkSwitch.vue';
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
|
|
||||||
const gameMode = ref<'normal' | 'square'>('normal');
|
const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets'>('normal');
|
||||||
const gameStarted = ref(false);
|
const gameStarted = ref(false);
|
||||||
const mute = ref(false);
|
const mute = ref(false);
|
||||||
const ranking = ref(null);
|
const ranking = ref(null);
|
||||||
|
@ -103,6 +103,14 @@ watch(gameMode, async () => {
|
||||||
ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value });
|
ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value });
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
|
function getScoreUnit(gameMode: string) {
|
||||||
|
return gameMode === 'normal' ? 'pt' :
|
||||||
|
gameMode === 'square' ? 'pt' :
|
||||||
|
gameMode === 'yen' ? '円' :
|
||||||
|
gameMode === 'sweets' ? 'kcal' :
|
||||||
|
'' as never;
|
||||||
|
}
|
||||||
|
|
||||||
async function start() {
|
async function start() {
|
||||||
gameStarted.value = true;
|
gameStarted.value = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { } from 'vue';
|
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
import { misskeyApiGet } from '@/scripts/misskey-api.js';
|
||||||
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
import copyToClipboard from '@/scripts/copy-to-clipboard.js';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
|
import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
emoji: {
|
emoji: Misskey.entities.EmojiSimple;
|
||||||
name: string;
|
|
||||||
aliases: string[];
|
|
||||||
category: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
function menu(ev) {
|
function menu(ev) {
|
||||||
|
@ -43,12 +39,13 @@ function menu(ev) {
|
||||||
}, {
|
}, {
|
||||||
text: i18n.ts.info,
|
text: i18n.ts.info,
|
||||||
icon: 'ti ti-info-circle',
|
icon: 'ti ti-info-circle',
|
||||||
action: () => {
|
action: async () => {
|
||||||
misskeyApiGet('emoji', { name: props.emoji.name }).then(res => {
|
os.popup(MkCustomEmojiDetailedDialog, {
|
||||||
os.alert({
|
emoji: await misskeyApiGet('emoji', {
|
||||||
type: 'info',
|
name: props.emoji.name,
|
||||||
text: `Name: ${res.name}\nAliases: ${res.aliases.join(' ')}\nCategory: ${res.category}\nisSensitive: ${res.isSensitive}\nlocalOnly: ${res.localOnly}\nLicense: ${res.license}\nURL: ${res.url}`,
|
})
|
||||||
});
|
}, {
|
||||||
|
anchor: ev.target,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}], ev.currentTarget ?? ev.target);
|
}], ev.currentTarget ?? ev.target);
|
||||||
|
|
|
@ -27,10 +27,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<I18n :src="i18n.ts._2fa.step1" tag="div">
|
<I18n :src="i18n.ts._2fa.step1" tag="div">
|
||||||
<template #a>
|
<template #a>
|
||||||
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
|
<a href="https://authy.com/" rel="nofollow noopener" target="_blank" class="_link">Authy</a>
|
||||||
</template>
|
</template>
|
||||||
<template #b>
|
<template #b>
|
||||||
<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank" class="_link">Google Authenticator</a>
|
<a href="https://support.google.com/accounts/answer/1066447" rel="nofollow noopener" target="_blank" class="_link">Google Authenticator</a>
|
||||||
</template>
|
</template>
|
||||||
</I18n>
|
</I18n>
|
||||||
<div>{{ i18n.ts._2fa.step2 }}<br>{{ i18n.ts._2fa.step2Click }}</div>
|
<div>{{ i18n.ts._2fa.step2 }}<br>{{ i18n.ts._2fa.step2Click }}</div>
|
||||||
|
|
|
@ -1,219 +0,0 @@
|
||||||
<!--
|
|
||||||
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="rootEl">
|
|
||||||
<MkLoading v-if="fetching"/>
|
|
||||||
<div v-else :class="$style.root" class="_panel">
|
|
||||||
<canvas ref="chartEl"></canvas>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
|
|
||||||
import { Chart } from 'chart.js';
|
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
|
|
||||||
import { alpha } from '@/scripts/color.js';
|
|
||||||
import { initChart } from '@/scripts/init-chart.js';
|
|
||||||
|
|
||||||
initChart();
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
src: string;
|
|
||||||
user: Misskey.entities.User;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const rootEl = shallowRef<HTMLDivElement>(null);
|
|
||||||
const chartEl = shallowRef<HTMLCanvasElement>(null);
|
|
||||||
const now = new Date();
|
|
||||||
let chartInstance: Chart = null;
|
|
||||||
const fetching = ref(true);
|
|
||||||
|
|
||||||
const { handler: externalTooltipHandler } = useChartTooltip({
|
|
||||||
position: 'middle',
|
|
||||||
});
|
|
||||||
|
|
||||||
async function renderChart() {
|
|
||||||
if (chartInstance) {
|
|
||||||
chartInstance.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
const wide = rootEl.value.offsetWidth > 700;
|
|
||||||
const narrow = rootEl.value.offsetWidth < 400;
|
|
||||||
|
|
||||||
const weeks = wide ? 50 : narrow ? 10 : 25;
|
|
||||||
const chartLimit = 7 * weeks;
|
|
||||||
|
|
||||||
const getDate = (ago: number) => {
|
|
||||||
const y = now.getFullYear();
|
|
||||||
const m = now.getMonth();
|
|
||||||
const d = now.getDate();
|
|
||||||
|
|
||||||
return new Date(y, m, d - ago);
|
|
||||||
};
|
|
||||||
|
|
||||||
const format = (arr) => {
|
|
||||||
return arr.map((v, i) => {
|
|
||||||
const dt = getDate(i);
|
|
||||||
const iso = `${dt.getFullYear()}-${(dt.getMonth() + 1).toString().padStart(2, '0')}-${dt.getDate().toString().padStart(2, '0')}`;
|
|
||||||
return {
|
|
||||||
x: iso,
|
|
||||||
y: dt.getDay(),
|
|
||||||
d: iso,
|
|
||||||
v,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let values;
|
|
||||||
|
|
||||||
if (props.src === 'notes') {
|
|
||||||
const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
|
|
||||||
values = raw.inc;
|
|
||||||
}
|
|
||||||
|
|
||||||
fetching.value = false;
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
|
|
||||||
|
|
||||||
// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
|
|
||||||
const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
|
|
||||||
|
|
||||||
const min = Math.max(0, Math.min(...values) - 1);
|
|
||||||
|
|
||||||
const marginEachCell = 4;
|
|
||||||
|
|
||||||
chartInstance = new Chart(chartEl.value, {
|
|
||||||
type: 'matrix',
|
|
||||||
data: {
|
|
||||||
datasets: [{
|
|
||||||
label: '',
|
|
||||||
data: format(values),
|
|
||||||
pointRadius: 0,
|
|
||||||
borderWidth: 0,
|
|
||||||
borderJoinStyle: 'round',
|
|
||||||
borderRadius: 3,
|
|
||||||
backgroundColor(c) {
|
|
||||||
const value = c.dataset.data[c.dataIndex].v;
|
|
||||||
let a = (value - min) / max;
|
|
||||||
if (value !== 0) { // 0でない限りは完全に不可視にはしない
|
|
||||||
a = Math.max(a, 0.05);
|
|
||||||
}
|
|
||||||
return alpha(color, a);
|
|
||||||
},
|
|
||||||
fill: true,
|
|
||||||
width(c) {
|
|
||||||
const a = c.chart.chartArea ?? {};
|
|
||||||
return (a.right - a.left) / weeks - marginEachCell;
|
|
||||||
},
|
|
||||||
height(c) {
|
|
||||||
const a = c.chart.chartArea ?? {};
|
|
||||||
return (a.bottom - a.top) / 7 - marginEachCell;
|
|
||||||
},
|
|
||||||
/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
|
|
||||||
}] satisfies ChartData[],
|
|
||||||
*/
|
|
||||||
}],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
aspectRatio: wide ? 6 : narrow ? 1.8 : 3.2,
|
|
||||||
layout: {
|
|
||||||
padding: {
|
|
||||||
left: 8,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
type: 'time',
|
|
||||||
offset: true,
|
|
||||||
position: 'bottom',
|
|
||||||
time: {
|
|
||||||
unit: 'week',
|
|
||||||
round: 'week',
|
|
||||||
isoWeekday: 0,
|
|
||||||
displayFormats: {
|
|
||||||
day: 'M/d',
|
|
||||||
month: 'Y/M',
|
|
||||||
week: 'M/d',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
display: true,
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkipPadding: 8,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
offset: true,
|
|
||||||
reverse: true,
|
|
||||||
position: 'right',
|
|
||||||
grid: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
ticks: {
|
|
||||||
maxRotation: 0,
|
|
||||||
autoSkip: true,
|
|
||||||
padding: 1,
|
|
||||||
font: {
|
|
||||||
size: 9,
|
|
||||||
},
|
|
||||||
callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
enabled: false,
|
|
||||||
callbacks: {
|
|
||||||
title(context) {
|
|
||||||
const v = context[0].dataset.data[context[0].dataIndex];
|
|
||||||
return v.d;
|
|
||||||
},
|
|
||||||
label(context) {
|
|
||||||
const v = context.dataset.data[context.dataIndex];
|
|
||||||
return [v.v];
|
|
||||||
},
|
|
||||||
},
|
|
||||||
//mode: 'index',
|
|
||||||
animation: {
|
|
||||||
duration: 0,
|
|
||||||
},
|
|
||||||
external: externalTooltipHandler,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => props.src, () => {
|
|
||||||
fetching.value = true;
|
|
||||||
renderChart();
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
renderChart();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" module>
|
|
||||||
.root {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<div class="_gaps">
|
<div class="_gaps">
|
||||||
<MkFoldableSection class="item">
|
<MkFoldableSection class="item">
|
||||||
<template #header><i class="ti ti-activity"></i> Heatmap</template>
|
<template #header><i class="ti ti-activity"></i> Heatmap</template>
|
||||||
<XHeatmap :user="user" :src="'notes'"/>
|
<MkHeatmap :user="user" :src="'notes'"/>
|
||||||
</MkFoldableSection>
|
</MkFoldableSection>
|
||||||
<MkFoldableSection class="item">
|
<MkFoldableSection class="item">
|
||||||
<template #header><i class="ti ti-pencil"></i> Notes</template>
|
<template #header><i class="ti ti-pencil"></i> Notes</template>
|
||||||
|
@ -28,11 +28,11 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import XHeatmap from './activity.heatmap.vue';
|
|
||||||
import XPv from './activity.pv.vue';
|
import XPv from './activity.pv.vue';
|
||||||
import XNotes from './activity.notes.vue';
|
import XNotes from './activity.notes.vue';
|
||||||
import XFollowing from './activity.following.vue';
|
import XFollowing from './activity.following.vue';
|
||||||
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
import MkFoldableSection from '@/components/MkFoldableSection.vue';
|
||||||
|
import MkHeatmap from '@/components/MkHeatmap.vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
user: Misskey.entities.User;
|
user: Misskey.entities.User;
|
||||||
|
|
|
@ -10,14 +10,12 @@ import seedrandom from 'seedrandom';
|
||||||
export type Mono = {
|
export type Mono = {
|
||||||
id: string;
|
id: string;
|
||||||
level: number;
|
level: number;
|
||||||
size: number;
|
sizeX: number;
|
||||||
shape: 'circle' | 'rectangle';
|
sizeY: number;
|
||||||
|
shape: 'circle' | 'rectangle' | 'custom';
|
||||||
|
vertices?: Matter.Vector[][];
|
||||||
score: number;
|
score: number;
|
||||||
dropCandidate: boolean;
|
dropCandidate: boolean;
|
||||||
sfxPitch: number;
|
|
||||||
img: string;
|
|
||||||
imgSize: number;
|
|
||||||
spriteScale: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type Log = {
|
type Log = {
|
||||||
|
@ -32,23 +30,469 @@ type Log = {
|
||||||
operation: 'surrender';
|
operation: 'surrender';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const NORMAL_BASE_SIZE = 30;
|
||||||
|
const NORAML_MONOS: Mono[] = [{
|
||||||
|
id: '9377076d-c980-4d83-bdaf-175bc58275b7',
|
||||||
|
level: 10,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 512,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'be9f38d2-b267-4b1a-b420-904e22e80568',
|
||||||
|
level: 9,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 256,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'beb30459-b064-4888-926b-f572e4e72e0c',
|
||||||
|
level: 8,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 128,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'feab6426-d9d8-49ae-849c-048cdbb6cdf0',
|
||||||
|
level: 7,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 64,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'd6d8fed6-6d18-4726-81a1-6cf2c974df8a',
|
||||||
|
level: 6,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 32,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '249c728e-230f-4332-bbbf-281c271c75b2',
|
||||||
|
level: 5,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 16,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '23d67613-d484-4a93-b71e-3e81b19d6186',
|
||||||
|
level: 4,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 8,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '3cbd0add-ad7d-4685-bad0-29f6dddc0b99',
|
||||||
|
level: 3,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 4,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '8f86d4f4-ee02-41bf-ad38-1ce0ae457fb5',
|
||||||
|
level: 2,
|
||||||
|
sizeX: NORMAL_BASE_SIZE * 1.25,
|
||||||
|
sizeY: NORMAL_BASE_SIZE * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 2,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '64ec4add-ce39-42b4-96cb-33908f3f118d',
|
||||||
|
level: 1,
|
||||||
|
sizeX: NORMAL_BASE_SIZE,
|
||||||
|
sizeY: NORMAL_BASE_SIZE,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 1,
|
||||||
|
dropCandidate: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
const YEN_BASE_SIZE = 30;
|
||||||
|
const YEN_SATSU_BASE_SIZE = 70;
|
||||||
|
const YEN_MONOS: Mono[] = [{
|
||||||
|
id: '880f9bd9-802f-4135-a7e1-fd0e0331f726',
|
||||||
|
level: 10,
|
||||||
|
sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_SATSU_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 10000,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'e807beb6-374a-4314-9cc2-aa5f17d96b6b',
|
||||||
|
level: 9,
|
||||||
|
sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_SATSU_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 5000,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '033445b7-8f90-4fc9-beca-71a9e87cb530',
|
||||||
|
level: 8,
|
||||||
|
sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25,
|
||||||
|
sizeY: YEN_SATSU_BASE_SIZE * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 2000,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '410a09ec-5f7f-46f6-b26f-cbca4ccbd091',
|
||||||
|
level: 7,
|
||||||
|
sizeX: YEN_SATSU_BASE_SIZE * 2,
|
||||||
|
sizeY: YEN_SATSU_BASE_SIZE,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 1000,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '2aae82bc-3fa4-49ad-a6b5-94d888e809f5',
|
||||||
|
level: 6,
|
||||||
|
sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 500,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'a619bd67-d08f-4cc0-8c7e-c8072a4950cd',
|
||||||
|
level: 5,
|
||||||
|
sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 100,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: 'c1c5d8e4-17d6-4455-befd-12154d731faa',
|
||||||
|
level: 4,
|
||||||
|
sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 50,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '7082648c-e428-44c4-887a-25c07a8ebdd5',
|
||||||
|
level: 3,
|
||||||
|
sizeX: YEN_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
sizeY: YEN_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 10,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '0d8d40d5-e6e0-4d26-8a95-b8d842363379',
|
||||||
|
level: 2,
|
||||||
|
sizeX: YEN_BASE_SIZE * 1.25,
|
||||||
|
sizeY: YEN_BASE_SIZE * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 5,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '9dec1b38-d99d-40de-8288-37367b983d0d',
|
||||||
|
level: 1,
|
||||||
|
sizeX: YEN_BASE_SIZE,
|
||||||
|
sizeY: YEN_BASE_SIZE,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 1,
|
||||||
|
dropCandidate: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
const SQUARE_BASE_SIZE = 28;
|
||||||
|
const SQUARE_MONOS: Mono[] = [{
|
||||||
|
id: 'f75fd0ba-d3d4-40a4-9712-b470e45b0525',
|
||||||
|
level: 10,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 512,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '7b70f4af-1c01-45fd-af72-61b1f01e03d1',
|
||||||
|
level: 9,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 256,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '41607ef3-b6d6-4829-95b6-3737bf8bb956',
|
||||||
|
level: 8,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 128,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '8a8310d2-0374-460f-bb50-ca9cd3ee3416',
|
||||||
|
level: 7,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 64,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '1092e069-fe1a-450b-be97-b5d477ec398c',
|
||||||
|
level: 6,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 32,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '2294734d-7bb8-4781-bb7b-ef3820abf3d0',
|
||||||
|
level: 5,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 16,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: 'ea8a61af-e350-45f7-ba6a-366fcd65692a',
|
||||||
|
level: 4,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 8,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: 'd0c74815-fc1c-4fbe-9953-c92e4b20f919',
|
||||||
|
level: 3,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 4,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: 'd8fbd70e-611d-402d-87da-1a7fd8cd2c8d',
|
||||||
|
level: 2,
|
||||||
|
sizeX: SQUARE_BASE_SIZE * 1.25,
|
||||||
|
sizeY: SQUARE_BASE_SIZE * 1.25,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 2,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '35e476ee-44bd-4711-ad42-87be245d3efd',
|
||||||
|
level: 1,
|
||||||
|
sizeX: SQUARE_BASE_SIZE,
|
||||||
|
sizeY: SQUARE_BASE_SIZE,
|
||||||
|
shape: 'rectangle',
|
||||||
|
score: 1,
|
||||||
|
dropCandidate: true,
|
||||||
|
}];
|
||||||
|
|
||||||
|
const SWEETS_BASE_SIZE = 30;
|
||||||
|
// TODO: custom shape vertices
|
||||||
|
const SWEETS_MONOS: Mono[] = [{
|
||||||
|
id: '77f724c0-88be-4aeb-8e1a-a00ed18e3844',
|
||||||
|
level: 10,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 512,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'f3468ef4-2e1e-4906-8795-f147f39f7e1f',
|
||||||
|
level: 9,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 256,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'bcb41129-6f2d-44ee-89d3-86eb2df564ba',
|
||||||
|
level: 8,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 128,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'f058e1ad-1981-409b-b3a7-302de0a43744',
|
||||||
|
level: 7,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 64,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: 'd22cfe38-5a3b-4b9c-a1a6-907930a3d732',
|
||||||
|
level: 6,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 32,
|
||||||
|
dropCandidate: false,
|
||||||
|
}, {
|
||||||
|
id: '79867083-a073-427e-ae82-07a70d9f3b4f',
|
||||||
|
level: 5,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'custom',
|
||||||
|
vertices: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'x': 8,
|
||||||
|
'y': 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 24,
|
||||||
|
'y': 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 26,
|
||||||
|
'y': 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 30,
|
||||||
|
'y': 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 24.7,
|
||||||
|
'y': 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 7.34,
|
||||||
|
'y': 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 2,
|
||||||
|
'y': 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 6,
|
||||||
|
'y': 26,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
score: 16,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '2e152a12-a567-4100-b4d4-d15d81ba47b1',
|
||||||
|
level: 4,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 8,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '12250376-2258-4716-8eec-b3a7239461fc',
|
||||||
|
level: 3,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 4,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: '4d4f2668-4be7-44a3-aa3a-856df6e25aa6',
|
||||||
|
level: 2,
|
||||||
|
sizeX: SWEETS_BASE_SIZE * 1.25,
|
||||||
|
sizeY: SWEETS_BASE_SIZE * 1.25,
|
||||||
|
shape: 'custom',
|
||||||
|
vertices: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'x': 12,
|
||||||
|
'y': 1.9180000000000001,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 4,
|
||||||
|
'y': 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 2.016,
|
||||||
|
'y': 12,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 6,
|
||||||
|
'y': 13.375,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 6,
|
||||||
|
'y': 18,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 8,
|
||||||
|
'y': 22,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 12,
|
||||||
|
'y': 25.372,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 16.008,
|
||||||
|
'y': 26,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 19,
|
||||||
|
'y': 25.372,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 20,
|
||||||
|
'y': 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 28,
|
||||||
|
'y': 27,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 30,
|
||||||
|
'y': 20,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 25.473,
|
||||||
|
'y': 19,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 26,
|
||||||
|
'y': 15,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 24,
|
||||||
|
'y': 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 20,
|
||||||
|
'y': 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 16.008,
|
||||||
|
'y': 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'x': 13,
|
||||||
|
'y': 6,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
score: 2,
|
||||||
|
dropCandidate: true,
|
||||||
|
}, {
|
||||||
|
id: 'c9984b40-4045-44c3-b260-d47b7b4625b2',
|
||||||
|
level: 1,
|
||||||
|
sizeX: SWEETS_BASE_SIZE,
|
||||||
|
sizeY: SWEETS_BASE_SIZE,
|
||||||
|
shape: 'circle',
|
||||||
|
score: 1,
|
||||||
|
dropCandidate: true,
|
||||||
|
}];
|
||||||
|
|
||||||
export class DropAndFusionGame extends EventEmitter<{
|
export class DropAndFusionGame extends EventEmitter<{
|
||||||
changeScore: (newScore: number) => void;
|
changeScore: (newScore: number) => void;
|
||||||
changeCombo: (newCombo: number) => void;
|
changeCombo: (newCombo: number) => void;
|
||||||
changeStock: (newStock: { id: string; mono: Mono }[]) => void;
|
changeStock: (newStock: { id: string; mono: Mono }[]) => void;
|
||||||
changeHolding: (newHolding: { id: string; mono: Mono } | null) => void;
|
changeHolding: (newHolding: { id: string; mono: Mono } | null) => void;
|
||||||
dropped: (x: number) => void;
|
dropped: (x: number) => void;
|
||||||
fusioned: (x: number, y: number, scoreDelta: number) => void;
|
fusioned: (x: number, y: number, nextMono: Mono | null, scoreDelta: number) => void;
|
||||||
|
collision: (energy: number, bodyA: Matter.Body, bodyB: Matter.Body) => void;
|
||||||
monoAdded: (mono: Mono) => void;
|
monoAdded: (mono: Mono) => void;
|
||||||
gameOver: () => void;
|
gameOver: () => void;
|
||||||
sfx(type: string, params: { volume: number; pan: number; pitch: number; }): void;
|
|
||||||
}> {
|
}> {
|
||||||
private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
|
private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
|
||||||
private COMBO_INTERVAL = 60; // frame
|
private COMBO_INTERVAL = 60; // frame
|
||||||
public readonly GAME_VERSION = 1;
|
public readonly GAME_VERSION = 2;
|
||||||
public readonly GAME_WIDTH = 450;
|
public readonly GAME_WIDTH = 450;
|
||||||
public readonly GAME_HEIGHT = 600;
|
public readonly GAME_HEIGHT = 600;
|
||||||
public readonly DROP_INTERVAL = 500;
|
public readonly DROP_COOLTIME = 30; // frame
|
||||||
public readonly PLAYAREA_MARGIN = 25;
|
public readonly PLAYAREA_MARGIN = 25;
|
||||||
private STOCK_MAX = 4;
|
private STOCK_MAX = 4;
|
||||||
private TICK_DELTA = 1000 / 60; // 60fps
|
private TICK_DELTA = 1000 / 60; // 60fps
|
||||||
|
@ -58,15 +502,16 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
private tickCallbackQueue: { frame: number; callback: () => void; }[] = [];
|
private tickCallbackQueue: { frame: number; callback: () => void; }[] = [];
|
||||||
private overflowCollider: Matter.Body;
|
private overflowCollider: Matter.Body;
|
||||||
private isGameOver = false;
|
private isGameOver = false;
|
||||||
private monoDefinitions: Mono[] = [];
|
private gameMode: 'normal' | 'yen' | 'square' | 'sweets';
|
||||||
private rng: () => number;
|
private rng: () => number;
|
||||||
private logs: Log[] = [];
|
private logs: Log[] = [];
|
||||||
private replaying = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* フィールドに出ていて、かつ合体の対象となるアイテム
|
* フィールドに出ていて、かつ合体の対象となるアイテム
|
||||||
*/
|
*/
|
||||||
private activeBodyIds: Matter.Body['id'][] = [];
|
private fusionReadyBodyIds: Matter.Body['id'][] = [];
|
||||||
|
|
||||||
|
private gameOverReadyBodyIds: Matter.Body['id'][] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* fusion予約アイテムのペア
|
* fusion予約アイテムのペア
|
||||||
|
@ -74,13 +519,21 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
*/
|
*/
|
||||||
private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = [];
|
private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = [];
|
||||||
|
|
||||||
private latestDroppedBodyId: Matter.Body['id'] | null = null;
|
private latestDroppedAt = 0; // frame
|
||||||
|
|
||||||
private latestDroppedAt = 0;
|
|
||||||
private latestFusionedAt = 0; // frame
|
private latestFusionedAt = 0; // frame
|
||||||
private stock: { id: string; mono: Mono }[] = [];
|
private stock: { id: string; mono: Mono }[] = [];
|
||||||
private holding: { id: string; mono: Mono } | null = null;
|
private holding: { id: string; mono: Mono } | null = null;
|
||||||
|
|
||||||
|
private get monoDefinitions() {
|
||||||
|
switch (this.gameMode) {
|
||||||
|
case 'normal': return NORAML_MONOS;
|
||||||
|
case 'yen': return YEN_MONOS;
|
||||||
|
case 'square': return SQUARE_MONOS;
|
||||||
|
case 'sweets': return SWEETS_MONOS;
|
||||||
|
default: throw new Error('unknown game mode');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private _combo = 0;
|
private _combo = 0;
|
||||||
private get combo() {
|
private get combo() {
|
||||||
return this._combo;
|
return this._combo;
|
||||||
|
@ -99,16 +552,24 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
this.emit('changeScore', value);
|
this.emit('changeScore', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getMonoRenderOptions: null | ((mono: Mono) => Partial<Matter.IBodyRenderOptions>) = null;
|
||||||
|
|
||||||
public replayPlaybackRate = 1;
|
public replayPlaybackRate = 1;
|
||||||
|
|
||||||
constructor(env: { monoDefinitions: Mono[]; seed: string; replaying?: boolean }) {
|
constructor(env: {
|
||||||
|
seed: string;
|
||||||
|
gameMode: DropAndFusionGame['gameMode'];
|
||||||
|
getMonoRenderOptions?: (mono: Mono) => Partial<Matter.IBodyRenderOptions>;
|
||||||
|
}) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.replaying = !!env.replaying;
|
//#region BIND
|
||||||
this.monoDefinitions = env.monoDefinitions;
|
|
||||||
this.rng = seedrandom(env.seed);
|
|
||||||
|
|
||||||
this.tick = this.tick.bind(this);
|
this.tick = this.tick.bind(this);
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
this.gameMode = env.gameMode;
|
||||||
|
this.getMonoRenderOptions = env.getMonoRenderOptions ?? null;
|
||||||
|
this.rng = seedrandom(env.seed);
|
||||||
|
|
||||||
this.engine = Matter.Engine.create({
|
this.engine = Matter.Engine.create({
|
||||||
constraintIterations: 2 * this.PHYSICS_QUALITY_FACTOR,
|
constraintIterations: 2 * this.PHYSICS_QUALITY_FACTOR,
|
||||||
|
@ -147,6 +608,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, {
|
this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, {
|
||||||
|
label: '_overflow_',
|
||||||
isStatic: true,
|
isStatic: true,
|
||||||
isSensor: true,
|
isSensor: true,
|
||||||
render: {
|
render: {
|
||||||
|
@ -157,34 +619,37 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
Matter.Composite.add(this.engine.world, this.overflowCollider);
|
Matter.Composite.add(this.engine.world, this.overflowCollider);
|
||||||
}
|
}
|
||||||
|
|
||||||
private msToFrame(ms: number) {
|
public msToFrame(ms: number) {
|
||||||
return Math.round(ms / this.TICK_DELTA);
|
return Math.round(ms / this.TICK_DELTA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public frameToMs(frame: number) {
|
||||||
|
return frame * this.TICK_DELTA;
|
||||||
|
}
|
||||||
|
|
||||||
private createBody(mono: Mono, x: number, y: number) {
|
private createBody(mono: Mono, x: number, y: number) {
|
||||||
const options: Matter.IBodyDefinition = {
|
const options: Matter.IBodyDefinition = {
|
||||||
label: mono.id,
|
label: mono.id,
|
||||||
//density: 0.0005,
|
//density: 0.0005,
|
||||||
density: mono.size / 1000,
|
density: ((mono.sizeX + mono.sizeY) / 2) / 1000,
|
||||||
restitution: 0.2,
|
restitution: 0.2,
|
||||||
frictionAir: 0.01,
|
frictionAir: 0.01,
|
||||||
friction: 0.7,
|
friction: 0.7,
|
||||||
frictionStatic: 5,
|
frictionStatic: 5,
|
||||||
slop: 1.0,
|
slop: 1.0,
|
||||||
//mass: 0,
|
//mass: 0,
|
||||||
render: {
|
render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined,
|
||||||
sprite: {
|
|
||||||
texture: mono.img,
|
|
||||||
xScale: (mono.size / mono.imgSize) * mono.spriteScale,
|
|
||||||
yScale: (mono.size / mono.imgSize) * mono.spriteScale,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
if (mono.shape === 'circle') {
|
if (mono.shape === 'circle') {
|
||||||
return Matter.Bodies.circle(x, y, mono.size / 2, options);
|
return Matter.Bodies.circle(x, y, mono.sizeX / 2, options);
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
} else if (mono.shape === 'rectangle') {
|
} else if (mono.shape === 'rectangle') {
|
||||||
return Matter.Bodies.rectangle(x, y, mono.size, mono.size, options);
|
return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options);
|
||||||
|
} else if (mono.shape === 'custom') {
|
||||||
|
return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({
|
||||||
|
x: (j.x / 32) * mono.sizeX,
|
||||||
|
y: (j.y / 32) * mono.sizeY,
|
||||||
|
}))), options);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unrecognized shape');
|
throw new Error('unrecognized shape');
|
||||||
}
|
}
|
||||||
|
@ -198,15 +663,15 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
}
|
}
|
||||||
this.latestFusionedAt = this.frame;
|
this.latestFusionedAt = this.frame;
|
||||||
|
|
||||||
// TODO: 単に位置だけでなくそれぞれの動きベクトルも融合する?
|
|
||||||
const newX = (bodyA.position.x + bodyB.position.x) / 2;
|
const newX = (bodyA.position.x + bodyB.position.x) / 2;
|
||||||
const newY = (bodyA.position.y + bodyB.position.y) / 2;
|
const newY = (bodyA.position.y + bodyB.position.y) / 2;
|
||||||
|
|
||||||
|
this.fusionReadyBodyIds = this.fusionReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
|
||||||
|
this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
|
||||||
Matter.Composite.remove(this.engine.world, [bodyA, bodyB]);
|
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 currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!;
|
||||||
const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1);
|
const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null;
|
||||||
|
|
||||||
if (nextMono) {
|
if (nextMono) {
|
||||||
const body = this.createBody(nextMono, newX, newY);
|
const body = this.createBody(nextMono, newX, newY);
|
||||||
|
@ -216,43 +681,24 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
this.tickCallbackQueue.push({
|
this.tickCallbackQueue.push({
|
||||||
frame: this.frame + this.msToFrame(100),
|
frame: this.frame + this.msToFrame(100),
|
||||||
callback: () => {
|
callback: () => {
|
||||||
this.activeBodyIds.push(body.id);
|
this.fusionReadyBodyIds.push(body.id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const comboBonus = 1 + ((this.combo - 1) / 5);
|
|
||||||
const additionalScore = Math.round(currentMono.score * comboBonus);
|
|
||||||
this.score += additionalScore;
|
|
||||||
|
|
||||||
this.emit('monoAdded', nextMono);
|
this.emit('monoAdded', nextMono);
|
||||||
this.emit('fusioned', newX, newY, additionalScore);
|
|
||||||
|
|
||||||
const panV = newX - this.PLAYAREA_MARGIN;
|
|
||||||
const panW = this.GAME_WIDTH - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN;
|
|
||||||
const pan = ((panV / panW) - 0.5) * 2;
|
|
||||||
this.emit('sfx', 'fusion', { volume: 1, pan, pitch: nextMono.sfxPitch });
|
|
||||||
} else {
|
|
||||||
// nop
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const comboBonus = this.gameMode === 'yen' ? 1 : 1 + ((this.combo - 1) / 5);
|
||||||
|
const additionalScore = Math.round(currentMono.score * comboBonus);
|
||||||
|
this.score += additionalScore;
|
||||||
|
|
||||||
|
this.emit('fusioned', newX, newY, nextMono, additionalScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onCollision(event: Matter.IEventCollision<Matter.Engine>) {
|
private onCollision(event: Matter.IEventCollision<Matter.Engine>) {
|
||||||
const minCollisionEnergyForSound = 2.5;
|
|
||||||
const maxCollisionEnergyForSound = 9;
|
|
||||||
const soundPitchMax = 4;
|
|
||||||
const soundPitchMin = 0.5;
|
|
||||||
|
|
||||||
for (const pairs of event.pairs) {
|
for (const pairs of event.pairs) {
|
||||||
const { bodyA, bodyB } = 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) &&
|
const shouldFusion = (bodyA.label === bodyB.label) &&
|
||||||
!this.fusionReservedPairs.some(x =>
|
!this.fusionReservedPairs.some(x =>
|
||||||
x.bodyA.id === bodyA.id ||
|
x.bodyA.id === bodyA.id ||
|
||||||
|
@ -261,7 +707,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
x.bodyB.id === bodyB.id);
|
x.bodyB.id === bodyB.id);
|
||||||
|
|
||||||
if (shouldFusion) {
|
if (shouldFusion) {
|
||||||
if (this.activeBodyIds.includes(bodyA.id) && this.activeBodyIds.includes(bodyB.id)) {
|
if (this.fusionReadyBodyIds.includes(bodyA.id) && this.fusionReadyBodyIds.includes(bodyB.id)) {
|
||||||
this.fusion(bodyA, bodyB);
|
this.fusion(bodyA, bodyB);
|
||||||
} else {
|
} else {
|
||||||
this.fusionReservedPairs.push({ bodyA, bodyB });
|
this.fusionReservedPairs.push({ bodyA, bodyB });
|
||||||
|
@ -275,16 +721,28 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const energy = pairs.collision.depth;
|
const energy = pairs.collision.depth;
|
||||||
if (energy > minCollisionEnergyForSound) {
|
|
||||||
const volume = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4;
|
if (bodyA.label === '_overflow_' || bodyB.label === '_overflow_') continue;
|
||||||
const panV =
|
|
||||||
pairs.bodyA.label === '_wall_' ? bodyB.position.x - this.PLAYAREA_MARGIN :
|
if (bodyA.label !== '_wall_' && bodyB.label !== '_wall_') {
|
||||||
pairs.bodyB.label === '_wall_' ? bodyA.position.x - this.PLAYAREA_MARGIN :
|
if (!this.gameOverReadyBodyIds.includes(bodyA.id)) this.gameOverReadyBodyIds.push(bodyA.id);
|
||||||
((bodyA.position.x + bodyB.position.x) / 2) - this.PLAYAREA_MARGIN;
|
if (!this.gameOverReadyBodyIds.includes(bodyB.id)) this.gameOverReadyBodyIds.push(bodyB.id);
|
||||||
const panW = this.GAME_WIDTH - this.PLAYAREA_MARGIN - this.PLAYAREA_MARGIN;
|
}
|
||||||
const pan = ((panV / panW) - 0.5) * 2;
|
|
||||||
const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10)));
|
this.emit('collision', energy, bodyA, bodyB);
|
||||||
this.emit('sfx', 'collision', { volume, pan, pitch });
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onCollisionActive(event: Matter.IEventCollision<Matter.Engine>) {
|
||||||
|
for (const pairs of event.pairs) {
|
||||||
|
const { bodyA, bodyB } = pairs;
|
||||||
|
|
||||||
|
// ハコからあふれたかどうかの判定
|
||||||
|
if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) {
|
||||||
|
if (this.gameOverReadyBodyIds.includes(bodyA.id) || this.gameOverReadyBodyIds.includes(bodyB.id)) {
|
||||||
|
this.gameOver();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -314,6 +772,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
this.emit('changeStock', this.stock);
|
this.emit('changeStock', this.stock);
|
||||||
|
|
||||||
Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this));
|
Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this));
|
||||||
|
Matter.Events.on(this.engine, 'collisionActive', this.onCollisionActive.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLogs() {
|
public getLogs() {
|
||||||
|
@ -349,8 +808,7 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
|
|
||||||
public drop(_x: number) {
|
public drop(_x: number) {
|
||||||
if (this.isGameOver) return;
|
if (this.isGameOver) return;
|
||||||
// TODO: フレームで計算するようにすればリプレイかどうかのチェックは不要になる
|
if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return;
|
||||||
if (!this.replaying && (Date.now() - this.latestDroppedAt < this.DROP_INTERVAL)) return;
|
|
||||||
|
|
||||||
const head = this.stock.shift()!;
|
const head = this.stock.shift()!;
|
||||||
this.stock.push({
|
this.stock.push({
|
||||||
|
@ -360,17 +818,18 @@ export class DropAndFusionGame extends EventEmitter<{
|
||||||
this.emit('changeStock', this.stock);
|
this.emit('changeStock', this.stock);
|
||||||
|
|
||||||
const inputX = Math.round(_x);
|
const inputX = Math.round(_x);
|
||||||
const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.size / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.size / 2), inputX));
|
const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.sizeX / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.sizeX / 2), inputX));
|
||||||
const body = this.createBody(head.mono, x, 50 + head.mono.size / 2);
|
const body = this.createBody(head.mono, x, 50 + head.mono.sizeY / 2);
|
||||||
this.logs.push({
|
this.logs.push({
|
||||||
frame: this.frame,
|
frame: this.frame,
|
||||||
operation: 'drop',
|
operation: 'drop',
|
||||||
x: inputX,
|
x: inputX,
|
||||||
});
|
});
|
||||||
Matter.Composite.add(this.engine.world, body);
|
Matter.Composite.add(this.engine.world, body);
|
||||||
this.activeBodyIds.push(body.id);
|
|
||||||
this.latestDroppedBodyId = body.id;
|
this.fusionReadyBodyIds.push(body.id);
|
||||||
this.latestDroppedAt = Date.now();
|
this.latestDroppedAt = this.frame;
|
||||||
|
|
||||||
this.emit('dropped', x);
|
this.emit('dropped', x);
|
||||||
this.emit('monoAdded', head.mono);
|
this.emit('monoAdded', head.mono);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mk-app">
|
<div class="mk-app">
|
||||||
<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
|
<a v-if="root" href="https://github.com/misskey-dev/misskey" rel="nofollow noopener" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
|
||||||
|
|
||||||
<div v-if="!narrow && !root" class="side">
|
<div v-if="!narrow && !root" class="side">
|
||||||
<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
|
<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
|
||||||
|
|
|
@ -7,14 +7,14 @@
|
||||||
"generate": "tsx src/generator.ts && eslint ./built/**/* --ext .ts --fix"
|
"generate": "tsx src/generator.ts && eslint ./built/**/* --ext .ts --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.1.0",
|
|
||||||
"@misskey-dev/eslint-plugin": "^1.0.0",
|
"@misskey-dev/eslint-plugin": "^1.0.0",
|
||||||
|
"@readme/openapi-parser": "2.5.0",
|
||||||
"@types/node": "20.9.1",
|
"@types/node": "20.9.1",
|
||||||
"@typescript-eslint/eslint-plugin": "6.11.0",
|
"@typescript-eslint/eslint-plugin": "6.11.0",
|
||||||
"@typescript-eslint/parser": "6.11.0",
|
"@typescript-eslint/parser": "6.11.0",
|
||||||
"eslint": "8.53.0",
|
"eslint": "8.53.0",
|
||||||
"openapi-types": "12.1.3",
|
"openapi-types": "12.1.3",
|
||||||
"openapi-typescript": "6.7.1",
|
"openapi-typescript": "6.7.3",
|
||||||
"ts-case-convert": "2.0.2",
|
"ts-case-convert": "2.0.2",
|
||||||
"tsx": "4.4.0",
|
"tsx": "4.4.0",
|
||||||
"typescript": "5.3.3"
|
"typescript": "5.3.3"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { mkdir, writeFile } from 'fs/promises';
|
import { mkdir, writeFile } from 'fs/promises';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3_1 } from 'openapi-types';
|
||||||
import { toPascal } from 'ts-case-convert';
|
import { toPascal } from 'ts-case-convert';
|
||||||
import SwaggerParser from '@apidevtools/swagger-parser';
|
import OpenAPIParser from '@readme/openapi-parser';
|
||||||
import openapiTS from 'openapi-typescript';
|
import openapiTS from 'openapi-typescript';
|
||||||
|
|
||||||
function generateVersionHeaderComment(openApiDocs: OpenAPIV3.Document): string {
|
function generateVersionHeaderComment(openApiDocs: OpenAPIV3_1.Document): string {
|
||||||
const contents = {
|
const contents = {
|
||||||
version: openApiDocs.info.version,
|
version: openApiDocs.info.version,
|
||||||
generatedAt: new Date().toISOString(),
|
generatedAt: new Date().toISOString(),
|
||||||
|
@ -21,7 +21,7 @@ function generateVersionHeaderComment(openApiDocs: OpenAPIV3.Document): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateBaseTypes(
|
async function generateBaseTypes(
|
||||||
openApiDocs: OpenAPIV3.Document,
|
openApiDocs: OpenAPIV3_1.Document,
|
||||||
openApiJsonPath: string,
|
openApiJsonPath: string,
|
||||||
typeFileName: string,
|
typeFileName: string,
|
||||||
) {
|
) {
|
||||||
|
@ -47,7 +47,7 @@ async function generateBaseTypes(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateSchemaEntities(
|
async function generateSchemaEntities(
|
||||||
openApiDocs: OpenAPIV3.Document,
|
openApiDocs: OpenAPIV3_1.Document,
|
||||||
typeFileName: string,
|
typeFileName: string,
|
||||||
outputPath: string,
|
outputPath: string,
|
||||||
) {
|
) {
|
||||||
|
@ -71,7 +71,7 @@ async function generateSchemaEntities(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateEndpoints(
|
async function generateEndpoints(
|
||||||
openApiDocs: OpenAPIV3.Document,
|
openApiDocs: OpenAPIV3_1.Document,
|
||||||
typeFileName: string,
|
typeFileName: string,
|
||||||
entitiesOutputPath: string,
|
entitiesOutputPath: string,
|
||||||
endpointOutputPath: string,
|
endpointOutputPath: string,
|
||||||
|
@ -79,7 +79,7 @@ async function generateEndpoints(
|
||||||
const endpoints: Endpoint[] = [];
|
const endpoints: Endpoint[] = [];
|
||||||
|
|
||||||
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
|
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
|
||||||
const paths = openApiDocs.paths;
|
const paths = openApiDocs.paths ?? {};
|
||||||
const postPathItems = Object.keys(paths)
|
const postPathItems = Object.keys(paths)
|
||||||
.map(it => paths[it]?.post)
|
.map(it => paths[it]?.post)
|
||||||
.filter(filterUndefined);
|
.filter(filterUndefined);
|
||||||
|
@ -160,7 +160,7 @@ async function generateEndpoints(
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateApiClientJSDoc(
|
async function generateApiClientJSDoc(
|
||||||
openApiDocs: OpenAPIV3.Document,
|
openApiDocs: OpenAPIV3_1.Document,
|
||||||
apiClientFileName: string,
|
apiClientFileName: string,
|
||||||
endpointsFileName: string,
|
endpointsFileName: string,
|
||||||
warningsOutputPath: string,
|
warningsOutputPath: string,
|
||||||
|
@ -168,7 +168,7 @@ async function generateApiClientJSDoc(
|
||||||
const endpoints: { operationId: string; description: string; }[] = [];
|
const endpoints: { operationId: string; description: string; }[] = [];
|
||||||
|
|
||||||
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
|
// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
|
||||||
const paths = openApiDocs.paths;
|
const paths = openApiDocs.paths ?? {};
|
||||||
const postPathItems = Object.keys(paths)
|
const postPathItems = Object.keys(paths)
|
||||||
.map(it => paths[it]?.post)
|
.map(it => paths[it]?.post)
|
||||||
.filter(filterUndefined);
|
.filter(filterUndefined);
|
||||||
|
@ -221,21 +221,21 @@ async function generateApiClientJSDoc(
|
||||||
await writeFile(warningsOutputPath, endpointOutputLine.join('\n'));
|
await writeFile(warningsOutputPath, endpointOutputLine.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
function isRequestBodyObject(value: unknown): value is OpenAPIV3.RequestBodyObject {
|
function isRequestBodyObject(value: unknown): value is OpenAPIV3_1.RequestBodyObject {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { content } = value as Record<keyof OpenAPIV3.RequestBodyObject, unknown>;
|
const { content } = value as Record<keyof OpenAPIV3_1.RequestBodyObject, unknown>;
|
||||||
return content !== undefined;
|
return content !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isResponseObject(value: unknown): value is OpenAPIV3.ResponseObject {
|
function isResponseObject(value: unknown): value is OpenAPIV3_1.ResponseObject {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { description } = value as Record<keyof OpenAPIV3.ResponseObject, unknown>;
|
const { description } = value as Record<keyof OpenAPIV3_1.ResponseObject, unknown>;
|
||||||
return description !== undefined;
|
return description !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +330,7 @@ async function main() {
|
||||||
await mkdir(generatePath, { recursive: true });
|
await mkdir(generatePath, { recursive: true });
|
||||||
|
|
||||||
const openApiJsonPath = './api.json';
|
const openApiJsonPath = './api.json';
|
||||||
const openApiDocs = await SwaggerParser.validate(openApiJsonPath) as OpenAPIV3.Document;
|
const openApiDocs = await OpenAPIParser.parse(openApiJsonPath) as OpenAPIV3_1.Document;
|
||||||
|
|
||||||
const typeFileName = './built/autogen/types.ts';
|
const typeFileName = './built/autogen/types.ts';
|
||||||
await generateBaseTypes(openApiDocs, openApiJsonPath, typeFileName);
|
await generateBaseTypes(openApiDocs, openApiJsonPath, typeFileName);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2023.12.2
|
||||||
* generatedAt: 2024-01-11T14:29:04.817Z
|
* generatedAt: 2024-01-13T04:31:38.782Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { SwitchCaseResponseType } from '../api.js';
|
import type { SwitchCaseResponseType } from '../api.js';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2023.12.2
|
||||||
* generatedAt: 2024-01-11T14:29:04.814Z
|
* generatedAt: 2024-01-13T04:31:38.778Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2023.12.2
|
||||||
* generatedAt: 2024-01-11T14:29:04.811Z
|
* generatedAt: 2024-01-13T04:31:38.775Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { operations } from './types.js';
|
import { operations } from './types.js';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2023.12.2
|
||||||
* generatedAt: 2024-01-11T14:29:04.810Z
|
* generatedAt: 2024-01-13T04:31:38.773Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { components } from './types.js';
|
import { components } from './types.js';
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* version: 2023.12.2
|
* version: 2023.12.2
|
||||||
* generatedAt: 2024-01-11T14:29:04.681Z
|
* generatedAt: 2024-01-13T04:31:38.633Z
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -3880,7 +3880,7 @@ export type components = {
|
||||||
fileIds?: string[];
|
fileIds?: string[];
|
||||||
files?: components['schemas']['DriveFile'][];
|
files?: components['schemas']['DriveFile'][];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
poll?: Record<string, unknown> | null;
|
poll?: Record<string, never> | null;
|
||||||
/**
|
/**
|
||||||
* Format: id
|
* Format: id
|
||||||
* @example xxxxxxxxxx
|
* @example xxxxxxxxxx
|
||||||
|
@ -3903,7 +3903,7 @@ export type components = {
|
||||||
url?: string;
|
url?: string;
|
||||||
reactionAndUserPairCache?: string[];
|
reactionAndUserPairCache?: string[];
|
||||||
clippedCount?: number;
|
clippedCount?: number;
|
||||||
myReaction?: Record<string, unknown> | null;
|
myReaction?: Record<string, never> | null;
|
||||||
};
|
};
|
||||||
NoteReaction: {
|
NoteReaction: {
|
||||||
/**
|
/**
|
||||||
|
|
117
pnpm-lock.yaml
|
@ -1083,12 +1083,12 @@ importers:
|
||||||
|
|
||||||
packages/misskey-js/generator:
|
packages/misskey-js/generator:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@apidevtools/swagger-parser':
|
|
||||||
specifier: 10.1.0
|
|
||||||
version: 10.1.0(openapi-types@12.1.3)
|
|
||||||
'@misskey-dev/eslint-plugin':
|
'@misskey-dev/eslint-plugin':
|
||||||
specifier: ^1.0.0
|
specifier: ^1.0.0
|
||||||
version: 1.0.0(@typescript-eslint/eslint-plugin@6.11.0)(@typescript-eslint/parser@6.11.0)(eslint-plugin-import@2.29.1)(eslint@8.53.0)
|
version: 1.0.0(@typescript-eslint/eslint-plugin@6.11.0)(@typescript-eslint/parser@6.11.0)(eslint-plugin-import@2.29.1)(eslint@8.53.0)
|
||||||
|
'@readme/openapi-parser':
|
||||||
|
specifier: 2.5.0
|
||||||
|
version: 2.5.0(openapi-types@12.1.3)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 20.9.1
|
specifier: 20.9.1
|
||||||
version: 20.9.1
|
version: 20.9.1
|
||||||
|
@ -1105,8 +1105,8 @@ importers:
|
||||||
specifier: 12.1.3
|
specifier: 12.1.3
|
||||||
version: 12.1.3
|
version: 12.1.3
|
||||||
openapi-typescript:
|
openapi-typescript:
|
||||||
specifier: 6.7.1
|
specifier: 6.7.3
|
||||||
version: 6.7.1
|
version: 6.7.3
|
||||||
ts-case-convert:
|
ts-case-convert:
|
||||||
specifier: 2.0.2
|
specifier: 2.0.2
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
|
@ -1170,14 +1170,6 @@ packages:
|
||||||
'@jridgewell/trace-mapping': 0.3.18
|
'@jridgewell/trace-mapping': 0.3.18
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@apidevtools/json-schema-ref-parser@9.0.6:
|
|
||||||
resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==}
|
|
||||||
dependencies:
|
|
||||||
'@jsdevtools/ono': 7.1.3
|
|
||||||
call-me-maybe: 1.0.2
|
|
||||||
js-yaml: 3.14.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@apidevtools/openapi-schemas@2.1.0:
|
/@apidevtools/openapi-schemas@2.1.0:
|
||||||
resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
|
resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -1187,21 +1179,6 @@ packages:
|
||||||
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
|
resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@apidevtools/swagger-parser@10.1.0(openapi-types@12.1.3):
|
|
||||||
resolution: {integrity: sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==}
|
|
||||||
peerDependencies:
|
|
||||||
openapi-types: '>=7'
|
|
||||||
dependencies:
|
|
||||||
'@apidevtools/json-schema-ref-parser': 9.0.6
|
|
||||||
'@apidevtools/openapi-schemas': 2.1.0
|
|
||||||
'@apidevtools/swagger-methods': 3.0.2
|
|
||||||
'@jsdevtools/ono': 7.1.3
|
|
||||||
ajv: 8.12.0
|
|
||||||
ajv-draft-04: 1.0.0(ajv@8.12.0)
|
|
||||||
call-me-maybe: 1.0.2
|
|
||||||
openapi-types: 12.1.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@aw-web-design/x-default-browser@1.4.126:
|
/@aw-web-design/x-default-browser@1.4.126:
|
||||||
resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
|
resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -3331,7 +3308,6 @@ packages:
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime: 0.14.0
|
regenerator-runtime: 0.14.0
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@babel/template@7.22.15:
|
/@babel/template@7.22.15:
|
||||||
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
|
||||||
|
@ -4553,6 +4529,11 @@ packages:
|
||||||
engines: {node: '>=12.22'}
|
engines: {node: '>=12.22'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@humanwhocodes/momoa@2.0.4:
|
||||||
|
resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==}
|
||||||
|
engines: {node: '>=10.10.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@humanwhocodes/object-schema@2.0.1:
|
/@humanwhocodes/object-schema@2.0.1:
|
||||||
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -5866,6 +5847,48 @@ packages:
|
||||||
'@babel/runtime': 7.23.2
|
'@babel/runtime': 7.23.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@readme/better-ajv-errors@1.6.0(ajv@8.12.0):
|
||||||
|
resolution: {integrity: sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
ajv: 4.11.8 - 8
|
||||||
|
dependencies:
|
||||||
|
'@babel/code-frame': 7.23.5
|
||||||
|
'@babel/runtime': 7.23.4
|
||||||
|
'@humanwhocodes/momoa': 2.0.4
|
||||||
|
ajv: 8.12.0
|
||||||
|
chalk: 4.1.2
|
||||||
|
json-to-ast: 2.1.0
|
||||||
|
jsonpointer: 5.0.1
|
||||||
|
leven: 3.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@readme/json-schema-ref-parser@1.2.0:
|
||||||
|
resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==}
|
||||||
|
dependencies:
|
||||||
|
'@jsdevtools/ono': 7.1.3
|
||||||
|
'@types/json-schema': 7.0.12
|
||||||
|
call-me-maybe: 1.0.2
|
||||||
|
js-yaml: 4.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@readme/openapi-parser@2.5.0(openapi-types@12.1.3):
|
||||||
|
resolution: {integrity: sha512-IbymbOqRuUzoIgxfAAR7XJt2FWl6n2yqN09fF5adacGm7W03siA3bj1Emql0X9D2T+RpBYz3x9zDsMhuoMP62A==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
openapi-types: '>=7'
|
||||||
|
dependencies:
|
||||||
|
'@apidevtools/openapi-schemas': 2.1.0
|
||||||
|
'@apidevtools/swagger-methods': 3.0.2
|
||||||
|
'@jsdevtools/ono': 7.1.3
|
||||||
|
'@readme/better-ajv-errors': 1.6.0(ajv@8.12.0)
|
||||||
|
'@readme/json-schema-ref-parser': 1.2.0
|
||||||
|
ajv: 8.12.0
|
||||||
|
ajv-draft-04: 1.0.0(ajv@8.12.0)
|
||||||
|
call-me-maybe: 1.0.2
|
||||||
|
openapi-types: 12.1.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@rollup/plugin-json@6.1.0(rollup@4.9.1):
|
/@rollup/plugin-json@6.1.0(rollup@4.9.1):
|
||||||
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
|
resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
|
@ -10454,6 +10477,11 @@ packages:
|
||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/code-error-fragment@0.0.230:
|
||||||
|
resolution: {integrity: sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/collect-v8-coverage@1.0.1:
|
/collect-v8-coverage@1.0.1:
|
||||||
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
|
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -13075,6 +13103,10 @@ packages:
|
||||||
/graceful-fs@4.2.11:
|
/graceful-fs@4.2.11:
|
||||||
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
/grapheme-splitter@1.0.4:
|
||||||
|
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/graphemer@1.4.0:
|
/graphemer@1.4.0:
|
||||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||||
dev: true
|
dev: true
|
||||||
|
@ -14666,6 +14698,14 @@ packages:
|
||||||
/json-stringify-safe@5.0.1:
|
/json-stringify-safe@5.0.1:
|
||||||
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||||
|
|
||||||
|
/json-to-ast@2.1.0:
|
||||||
|
resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
dependencies:
|
||||||
|
code-error-fragment: 0.0.230
|
||||||
|
grapheme-splitter: 1.0.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/json5@1.0.2:
|
/json5@1.0.2:
|
||||||
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
@ -14713,6 +14753,11 @@ packages:
|
||||||
- web-streams-polyfill
|
- web-streams-polyfill
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/jsonpointer@5.0.1:
|
||||||
|
resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/jsprim@1.4.2:
|
/jsprim@1.4.2:
|
||||||
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
|
resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
|
||||||
engines: {node: '>=0.6.0'}
|
engines: {node: '>=0.6.0'}
|
||||||
|
@ -16060,15 +16105,15 @@ packages:
|
||||||
/openapi-types@12.1.3:
|
/openapi-types@12.1.3:
|
||||||
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||||
|
|
||||||
/openapi-typescript@6.7.1:
|
/openapi-typescript@6.7.3:
|
||||||
resolution: {integrity: sha512-Q3Ltt0KUm2smcPrsaR8qKmSwQ1KM4yGDJVoQdpYa0yvKPeN8huDx5utMT7DvwvJastHHzUxajjivK3WN2+fobg==}
|
resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-colors: 4.1.3
|
ansi-colors: 4.1.3
|
||||||
fast-glob: 3.3.2
|
fast-glob: 3.3.2
|
||||||
js-yaml: 4.1.0
|
js-yaml: 4.1.0
|
||||||
supports-color: 9.4.0
|
supports-color: 9.4.0
|
||||||
undici: 5.28.1
|
undici: 5.28.2
|
||||||
yargs-parser: 21.1.1
|
yargs-parser: 21.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
@ -19557,6 +19602,14 @@ packages:
|
||||||
engines: {node: '>=14.0'}
|
engines: {node: '>=14.0'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/busboy': 2.1.0
|
'@fastify/busboy': 2.1.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/undici@5.28.2:
|
||||||
|
resolution: {integrity: sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==}
|
||||||
|
engines: {node: '>=14.0'}
|
||||||
|
dependencies:
|
||||||
|
'@fastify/busboy': 2.1.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/unicode-canonical-property-names-ecmascript@2.0.0:
|
/unicode-canonical-property-names-ecmascript@2.0.0:
|
||||||
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
module.exports = {
|
||||||
|
parserOptions: {
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
project: ['./tsconfig.json'],
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'../../packages/shared/.eslintrc.js',
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules
|
||||||
|
coverage
|
||||||
|
.idea
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "changelog-checker",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"run": "vite-node src/index.ts",
|
||||||
|
"test": "vitest run",
|
||||||
|
"test:coverage": "vitest run --coverage"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/mdast": "4.0.3",
|
||||||
|
"@types/node": "20.10.7",
|
||||||
|
"@vitest/coverage-v8": "1.1.3",
|
||||||
|
"mdast-util-to-string": "4.0.0",
|
||||||
|
"remark": "15.0.1",
|
||||||
|
"remark-parse": "11.0.0",
|
||||||
|
"typescript": "5.3.3",
|
||||||
|
"unified": "11.0.4",
|
||||||
|
"vite": "5.0.11",
|
||||||
|
"vite-node": "1.1.3",
|
||||||
|
"vitest": "1.1.3"
|
||||||
|
}
|
||||||
|
}
|