Merge branch 'develop' into swn
This commit is contained in:
commit
1750a19e0c
224 changed files with 3675 additions and 10139 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { Ref, ref } from 'vue';
|
||||
import { nextTick, Ref, ref } from 'vue';
|
||||
import * as getCaretCoordinates from 'textarea-caret';
|
||||
import { toASCII } from 'punycode/';
|
||||
import { popup } from '@/os';
|
||||
|
|
@ -10,26 +10,23 @@ export class Autocomplete {
|
|||
q: Ref<string | null>;
|
||||
close: Function;
|
||||
} | null;
|
||||
private textarea: any;
|
||||
private vm: any;
|
||||
private textarea: HTMLInputElement | HTMLTextAreaElement;
|
||||
private currentType: string;
|
||||
private opts: {
|
||||
model: string;
|
||||
};
|
||||
private textRef: Ref<string>;
|
||||
private opening: boolean;
|
||||
|
||||
private get text(): string {
|
||||
return this.vm[this.opts.model];
|
||||
return this.textRef.value;
|
||||
}
|
||||
|
||||
private set text(text: string) {
|
||||
this.vm[this.opts.model] = text;
|
||||
this.textRef.value = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 対象のテキストエリアを与えてインスタンスを初期化します。
|
||||
*/
|
||||
constructor(textarea, vm, opts) {
|
||||
constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>) {
|
||||
//#region BIND
|
||||
this.onInput = this.onInput.bind(this);
|
||||
this.complete = this.complete.bind(this);
|
||||
|
|
@ -38,8 +35,7 @@ export class Autocomplete {
|
|||
|
||||
this.suggestion = null;
|
||||
this.textarea = textarea;
|
||||
this.vm = vm;
|
||||
this.opts = opts;
|
||||
this.textRef = textRef;
|
||||
this.opening = false;
|
||||
|
||||
this.attach();
|
||||
|
|
@ -218,7 +214,7 @@ export class Autocomplete {
|
|||
this.text = `${trimmedBefore}@${acct} ${after}`;
|
||||
|
||||
// キャレットを戻す
|
||||
this.vm.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.textarea.focus();
|
||||
const pos = trimmedBefore.length + (acct.length + 2);
|
||||
this.textarea.setSelectionRange(pos, pos);
|
||||
|
|
@ -234,7 +230,7 @@ export class Autocomplete {
|
|||
this.text = `${trimmedBefore}#${value} ${after}`;
|
||||
|
||||
// キャレットを戻す
|
||||
this.vm.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.textarea.focus();
|
||||
const pos = trimmedBefore.length + (value.length + 2);
|
||||
this.textarea.setSelectionRange(pos, pos);
|
||||
|
|
@ -250,7 +246,7 @@ export class Autocomplete {
|
|||
this.text = trimmedBefore + value + after;
|
||||
|
||||
// キャレットを戻す
|
||||
this.vm.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.textarea.focus();
|
||||
const pos = trimmedBefore.length + value.length;
|
||||
this.textarea.setSelectionRange(pos, pos);
|
||||
|
|
@ -266,7 +262,7 @@ export class Autocomplete {
|
|||
this.text = `${trimmedBefore}$[${value} ]${after}`;
|
||||
|
||||
// キャレットを戻す
|
||||
this.vm.$nextTick(() => {
|
||||
nextTick(() => {
|
||||
this.textarea.focus();
|
||||
const pos = trimmedBefore.length + (value.length + 3);
|
||||
this.textarea.setSelectionRange(pos, pos);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
export async function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): Promise<boolean> {
|
||||
export function checkWordMute(note: Record<string, any>, me: Record<string, any> | null | undefined, mutedWords: string[][]): boolean {
|
||||
// 自分自身
|
||||
if (me && (note.userId === me.id)) return false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,263 +0,0 @@
|
|||
import { count, concat } from '@/scripts/array';
|
||||
|
||||
// MISSKEY REVERSI ENGINE
|
||||
|
||||
/**
|
||||
* true ... 黒
|
||||
* false ... 白
|
||||
*/
|
||||
export type Color = boolean;
|
||||
const BLACK = true;
|
||||
const WHITE = false;
|
||||
|
||||
export type MapPixel = 'null' | 'empty';
|
||||
|
||||
export type Options = {
|
||||
isLlotheo: boolean;
|
||||
canPutEverywhere: boolean;
|
||||
loopedBoard: boolean;
|
||||
};
|
||||
|
||||
export type Undo = {
|
||||
/**
|
||||
* 色
|
||||
*/
|
||||
color: Color;
|
||||
|
||||
/**
|
||||
* どこに打ったか
|
||||
*/
|
||||
pos: number;
|
||||
|
||||
/**
|
||||
* 反転した石の位置の配列
|
||||
*/
|
||||
effects: number[];
|
||||
|
||||
/**
|
||||
* ターン
|
||||
*/
|
||||
turn: Color | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* リバーシエンジン
|
||||
*/
|
||||
export default class Reversi {
|
||||
public map: MapPixel[];
|
||||
public mapWidth: number;
|
||||
public mapHeight: number;
|
||||
public board: (Color | null | undefined)[];
|
||||
public turn: Color | null = BLACK;
|
||||
public opts: Options;
|
||||
|
||||
public prevPos = -1;
|
||||
public prevColor: Color | null = null;
|
||||
|
||||
private logs: Undo[] = [];
|
||||
|
||||
/**
|
||||
* ゲームを初期化します
|
||||
*/
|
||||
constructor(map: string[], opts: Options) {
|
||||
//#region binds
|
||||
this.put = this.put.bind(this);
|
||||
//#endregion
|
||||
|
||||
//#region Options
|
||||
this.opts = opts;
|
||||
if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
|
||||
if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
|
||||
if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
|
||||
//#endregion
|
||||
|
||||
//#region Parse map data
|
||||
this.mapWidth = map[0].length;
|
||||
this.mapHeight = map.length;
|
||||
const mapData = map.join('');
|
||||
|
||||
this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
|
||||
|
||||
this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
|
||||
//#endregion
|
||||
|
||||
// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
|
||||
if (!this.canPutSomewhere(BLACK))
|
||||
this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 黒石の数
|
||||
*/
|
||||
public get blackCount() {
|
||||
return count(BLACK, this.board);
|
||||
}
|
||||
|
||||
/**
|
||||
* 白石の数
|
||||
*/
|
||||
public get whiteCount() {
|
||||
return count(WHITE, this.board);
|
||||
}
|
||||
|
||||
public transformPosToXy(pos: number): number[] {
|
||||
const x = pos % this.mapWidth;
|
||||
const y = Math.floor(pos / this.mapWidth);
|
||||
return [x, y];
|
||||
}
|
||||
|
||||
public transformXyToPos(x: number, y: number): number {
|
||||
return x + (y * this.mapWidth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定のマスに石を打ちます
|
||||
* @param color 石の色
|
||||
* @param pos 位置
|
||||
*/
|
||||
public put(color: Color, pos: number) {
|
||||
this.prevPos = pos;
|
||||
this.prevColor = color;
|
||||
|
||||
this.board[pos] = color;
|
||||
|
||||
// 反転させられる石を取得
|
||||
const effects = this.effects(color, pos);
|
||||
|
||||
// 反転させる
|
||||
for (const pos of effects) {
|
||||
this.board[pos] = color;
|
||||
}
|
||||
|
||||
const turn = this.turn;
|
||||
|
||||
this.logs.push({
|
||||
color,
|
||||
pos,
|
||||
effects,
|
||||
turn
|
||||
});
|
||||
|
||||
this.calcTurn();
|
||||
}
|
||||
|
||||
private calcTurn() {
|
||||
// ターン計算
|
||||
this.turn =
|
||||
this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
|
||||
this.canPutSomewhere(this.prevColor!) ? this.prevColor :
|
||||
null;
|
||||
}
|
||||
|
||||
public undo() {
|
||||
const undo = this.logs.pop()!;
|
||||
this.prevColor = undo.color;
|
||||
this.prevPos = undo.pos;
|
||||
this.board[undo.pos] = null;
|
||||
for (const pos of undo.effects) {
|
||||
const color = this.board[pos];
|
||||
this.board[pos] = !color;
|
||||
}
|
||||
this.turn = undo.turn;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定した位置のマップデータのマスを取得します
|
||||
* @param pos 位置
|
||||
*/
|
||||
public mapDataGet(pos: number): MapPixel {
|
||||
const [x, y] = this.transformPosToXy(pos);
|
||||
return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* 打つことができる場所を取得します
|
||||
*/
|
||||
public puttablePlaces(color: Color): number[] {
|
||||
return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
|
||||
}
|
||||
|
||||
/**
|
||||
* 打つことができる場所があるかどうかを取得します
|
||||
*/
|
||||
public canPutSomewhere(color: Color): boolean {
|
||||
return this.puttablePlaces(color).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定のマスに石を打つことができるかどうかを取得します
|
||||
* @param color 自分の色
|
||||
* @param pos 位置
|
||||
*/
|
||||
public canPut(color: Color, pos: number): boolean {
|
||||
return (
|
||||
this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
|
||||
this.opts.canPutEverywhere ? this.mapDataGet(pos) == 'empty' : // 挟んでなくても置けるモード
|
||||
this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定のマスに石を置いた時の、反転させられる石を取得します
|
||||
* @param color 自分の色
|
||||
* @param initPos 位置
|
||||
*/
|
||||
public effects(color: Color, initPos: number): number[] {
|
||||
const enemyColor = !color;
|
||||
|
||||
const diffVectors: [number, number][] = [
|
||||
[ 0, -1], // 上
|
||||
[ +1, -1], // 右上
|
||||
[ +1, 0], // 右
|
||||
[ +1, +1], // 右下
|
||||
[ 0, +1], // 下
|
||||
[ -1, +1], // 左下
|
||||
[ -1, 0], // 左
|
||||
[ -1, -1] // 左上
|
||||
];
|
||||
|
||||
const effectsInLine = ([dx, dy]: [number, number]): number[] => {
|
||||
const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
|
||||
|
||||
const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
|
||||
let [x, y] = this.transformPosToXy(initPos);
|
||||
while (true) {
|
||||
[x, y] = nextPos(x, y);
|
||||
|
||||
// 座標が指し示す位置がボード外に出たとき
|
||||
if (this.opts.loopedBoard && this.transformXyToPos(
|
||||
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
|
||||
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos)
|
||||
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
|
||||
return found;
|
||||
else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight)
|
||||
return []; // 挟めないことが確定 (盤面外に到達)
|
||||
|
||||
const pos = this.transformXyToPos(x, y);
|
||||
if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
|
||||
const stone = this.board[pos];
|
||||
if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
|
||||
if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
|
||||
if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
|
||||
}
|
||||
};
|
||||
|
||||
return concat(diffVectors.map(effectsInLine));
|
||||
}
|
||||
|
||||
/**
|
||||
* ゲームが終了したか否か
|
||||
*/
|
||||
public get isEnded(): boolean {
|
||||
return this.turn === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* ゲームの勝者 (null = 引き分け)
|
||||
*/
|
||||
public get winner(): Color | null {
|
||||
return this.isEnded ?
|
||||
this.blackCount == this.whiteCount ? null :
|
||||
this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
|
||||
undefined as never;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,896 +0,0 @@
|
|||
/**
|
||||
* 組み込みマップ定義
|
||||
*
|
||||
* データ値:
|
||||
* (スペース) ... マス無し
|
||||
* - ... マス
|
||||
* b ... 初期配置される黒石
|
||||
* w ... 初期配置される白石
|
||||
*/
|
||||
|
||||
export type Map = {
|
||||
name?: string;
|
||||
category?: string;
|
||||
author?: string;
|
||||
data: string[];
|
||||
};
|
||||
|
||||
export const fourfour: Map = {
|
||||
name: '4x4',
|
||||
category: '4x4',
|
||||
data: [
|
||||
'----',
|
||||
'-wb-',
|
||||
'-bw-',
|
||||
'----'
|
||||
]
|
||||
};
|
||||
|
||||
export const sixsix: Map = {
|
||||
name: '6x6',
|
||||
category: '6x6',
|
||||
data: [
|
||||
'------',
|
||||
'------',
|
||||
'--wb--',
|
||||
'--bw--',
|
||||
'------',
|
||||
'------'
|
||||
]
|
||||
};
|
||||
|
||||
export const roundedSixsix: Map = {
|
||||
name: '6x6 rounded',
|
||||
category: '6x6',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ---- ',
|
||||
'------',
|
||||
'--wb--',
|
||||
'--bw--',
|
||||
'------',
|
||||
' ---- '
|
||||
]
|
||||
};
|
||||
|
||||
export const roundedSixsix2: Map = {
|
||||
name: '6x6 rounded 2',
|
||||
category: '6x6',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' -- ',
|
||||
' ---- ',
|
||||
'--wb--',
|
||||
'--bw--',
|
||||
' ---- ',
|
||||
' -- '
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteight: Map = {
|
||||
name: '8x8',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightH1: Map = {
|
||||
name: '8x8 handicap 1',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'b-------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightH2: Map = {
|
||||
name: '8x8 handicap 2',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'b-------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'-------b'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightH3: Map = {
|
||||
name: '8x8 handicap 3',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'b------b',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'-------b'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightH4: Map = {
|
||||
name: '8x8 handicap 4',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'b------b',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'b------b'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightH28: Map = {
|
||||
name: '8x8 handicap 28',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'bbbbbbbb',
|
||||
'b------b',
|
||||
'b------b',
|
||||
'b--wb--b',
|
||||
'b--bw--b',
|
||||
'b------b',
|
||||
'b------b',
|
||||
'bbbbbbbb'
|
||||
]
|
||||
};
|
||||
|
||||
export const roundedEighteight: Map = {
|
||||
name: '8x8 rounded',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ------ ',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
' ------ '
|
||||
]
|
||||
};
|
||||
|
||||
export const roundedEighteight2: Map = {
|
||||
name: '8x8 rounded 2',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ---- ',
|
||||
' ------ ',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
' ------ ',
|
||||
' ---- '
|
||||
]
|
||||
};
|
||||
|
||||
export const roundedEighteight3: Map = {
|
||||
name: '8x8 rounded 3',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' -- ',
|
||||
' ---- ',
|
||||
' ------ ',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
' ------ ',
|
||||
' ---- ',
|
||||
' -- '
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightWithNotch: Map = {
|
||||
name: '8x8 with notch',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--- ---',
|
||||
'--------',
|
||||
'--------',
|
||||
' --wb-- ',
|
||||
' --bw-- ',
|
||||
'--------',
|
||||
'--------',
|
||||
'--- ---'
|
||||
]
|
||||
};
|
||||
|
||||
export const eighteightWithSomeHoles: Map = {
|
||||
name: '8x8 with some holes',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--- ----',
|
||||
'----- --',
|
||||
'-- -----',
|
||||
'---wb---',
|
||||
'---bw- -',
|
||||
' -------',
|
||||
'--- ----',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const circle: Map = {
|
||||
name: 'Circle',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' -- ',
|
||||
' ------ ',
|
||||
' ------ ',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
' ------ ',
|
||||
' ------ ',
|
||||
' -- '
|
||||
]
|
||||
};
|
||||
|
||||
export const smile: Map = {
|
||||
name: 'Smile',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ------ ',
|
||||
'--------',
|
||||
'-- -- --',
|
||||
'---wb---',
|
||||
'-- bw --',
|
||||
'--- ---',
|
||||
'--------',
|
||||
' ------ '
|
||||
]
|
||||
};
|
||||
|
||||
export const window: Map = {
|
||||
name: 'Window',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--------',
|
||||
'- -- -',
|
||||
'- -- -',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'- -- -',
|
||||
'- -- -',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const reserved: Map = {
|
||||
name: 'Reserved',
|
||||
category: '8x8',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
'w------b',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'b------w'
|
||||
]
|
||||
};
|
||||
|
||||
export const x: Map = {
|
||||
name: 'X',
|
||||
category: '8x8',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
'w------b',
|
||||
'-w----b-',
|
||||
'--w--b--',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--b--w--',
|
||||
'-b----w-',
|
||||
'b------w'
|
||||
]
|
||||
};
|
||||
|
||||
export const parallel: Map = {
|
||||
name: 'Parallel',
|
||||
category: '8x8',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---bb---',
|
||||
'---ww---',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const lackOfBlack: Map = {
|
||||
name: 'Lack of Black',
|
||||
category: '8x8',
|
||||
data: [
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---w----',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const squareParty: Map = {
|
||||
name: 'Square Party',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--------',
|
||||
'-wwwbbb-',
|
||||
'-w-wb-b-',
|
||||
'-wwwbbb-',
|
||||
'-bbbwww-',
|
||||
'-b-bw-w-',
|
||||
'-bbbwww-',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const minesweeper: Map = {
|
||||
name: 'Minesweeper',
|
||||
category: '8x8',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'b-b--w-w',
|
||||
'-w-wb-b-',
|
||||
'w-b--w-b',
|
||||
'-b-wb-w-',
|
||||
'-w-bw-b-',
|
||||
'b-w--b-w',
|
||||
'-b-bw-w-',
|
||||
'w-w--b-b'
|
||||
]
|
||||
};
|
||||
|
||||
export const tenthtenth: Map = {
|
||||
name: '10x10',
|
||||
category: '10x10',
|
||||
data: [
|
||||
'----------',
|
||||
'----------',
|
||||
'----------',
|
||||
'----------',
|
||||
'----wb----',
|
||||
'----bw----',
|
||||
'----------',
|
||||
'----------',
|
||||
'----------',
|
||||
'----------'
|
||||
]
|
||||
};
|
||||
|
||||
export const hole: Map = {
|
||||
name: 'The Hole',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'----------',
|
||||
'----------',
|
||||
'--wb--wb--',
|
||||
'--bw--bw--',
|
||||
'---- ----',
|
||||
'---- ----',
|
||||
'--wb--wb--',
|
||||
'--bw--bw--',
|
||||
'----------',
|
||||
'----------'
|
||||
]
|
||||
};
|
||||
|
||||
export const grid: Map = {
|
||||
name: 'Grid',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'----------',
|
||||
'- - -- - -',
|
||||
'----------',
|
||||
'- - -- - -',
|
||||
'----wb----',
|
||||
'----bw----',
|
||||
'- - -- - -',
|
||||
'----------',
|
||||
'- - -- - -',
|
||||
'----------'
|
||||
]
|
||||
};
|
||||
|
||||
export const cross: Map = {
|
||||
name: 'Cross',
|
||||
category: '10x10',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
' ---- ',
|
||||
' ---- ',
|
||||
' ---- ',
|
||||
'----------',
|
||||
'----wb----',
|
||||
'----bw----',
|
||||
'----------',
|
||||
' ---- ',
|
||||
' ---- ',
|
||||
' ---- '
|
||||
]
|
||||
};
|
||||
|
||||
export const charX: Map = {
|
||||
name: 'Char X',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--- ---',
|
||||
'---- ----',
|
||||
'----------',
|
||||
' -------- ',
|
||||
' --wb-- ',
|
||||
' --bw-- ',
|
||||
' -------- ',
|
||||
'----------',
|
||||
'---- ----',
|
||||
'--- ---'
|
||||
]
|
||||
};
|
||||
|
||||
export const charY: Map = {
|
||||
name: 'Char Y',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'--- ---',
|
||||
'---- ----',
|
||||
'----------',
|
||||
' -------- ',
|
||||
' --wb-- ',
|
||||
' --bw-- ',
|
||||
' ------ ',
|
||||
' ------ ',
|
||||
' ------ ',
|
||||
' ------ '
|
||||
]
|
||||
};
|
||||
|
||||
export const walls: Map = {
|
||||
name: 'Walls',
|
||||
category: '10x10',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
' bbbbbbbb ',
|
||||
'w--------w',
|
||||
'w--------w',
|
||||
'w--------w',
|
||||
'w---wb---w',
|
||||
'w---bw---w',
|
||||
'w--------w',
|
||||
'w--------w',
|
||||
'w--------w',
|
||||
' bbbbbbbb '
|
||||
]
|
||||
};
|
||||
|
||||
export const cpu: Map = {
|
||||
name: 'CPU',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' b b b b ',
|
||||
'w--------w',
|
||||
' -------- ',
|
||||
'w--------w',
|
||||
' ---wb--- ',
|
||||
' ---bw--- ',
|
||||
'w--------w',
|
||||
' -------- ',
|
||||
'w--------w',
|
||||
' b b b b '
|
||||
]
|
||||
};
|
||||
|
||||
export const checker: Map = {
|
||||
name: 'Checker',
|
||||
category: '10x10',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
'----------',
|
||||
'----------',
|
||||
'----------',
|
||||
'---wbwb---',
|
||||
'---bwbw---',
|
||||
'---wbwb---',
|
||||
'---bwbw---',
|
||||
'----------',
|
||||
'----------',
|
||||
'----------'
|
||||
]
|
||||
};
|
||||
|
||||
export const japaneseCurry: Map = {
|
||||
name: 'Japanese curry',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'w-b-b-b-b-',
|
||||
'-w-b-b-b-b',
|
||||
'w-w-b-b-b-',
|
||||
'-w-w-b-b-b',
|
||||
'w-w-wwb-b-',
|
||||
'-w-wbb-b-b',
|
||||
'w-w-w-b-b-',
|
||||
'-w-w-w-b-b',
|
||||
'w-w-w-w-b-',
|
||||
'-w-w-w-w-b'
|
||||
]
|
||||
};
|
||||
|
||||
export const mosaic: Map = {
|
||||
name: 'Mosaic',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'- - - - - ',
|
||||
' - - - - -',
|
||||
'- - - - - ',
|
||||
' - w w - -',
|
||||
'- - b b - ',
|
||||
' - w w - -',
|
||||
'- - b b - ',
|
||||
' - - - - -',
|
||||
'- - - - - ',
|
||||
' - - - - -',
|
||||
]
|
||||
};
|
||||
|
||||
export const arena: Map = {
|
||||
name: 'Arena',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'- - -- - -',
|
||||
' - - - - ',
|
||||
'- ------ -',
|
||||
' -------- ',
|
||||
'- --wb-- -',
|
||||
'- --bw-- -',
|
||||
' -------- ',
|
||||
'- ------ -',
|
||||
' - - - - ',
|
||||
'- - -- - -'
|
||||
]
|
||||
};
|
||||
|
||||
export const reactor: Map = {
|
||||
name: 'Reactor',
|
||||
category: '10x10',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'-w------b-',
|
||||
'b- - - -w',
|
||||
'- --wb-- -',
|
||||
'---b w---',
|
||||
'- b wb w -',
|
||||
'- w bw b -',
|
||||
'---w b---',
|
||||
'- --bw-- -',
|
||||
'w- - - -b',
|
||||
'-b------w-'
|
||||
]
|
||||
};
|
||||
|
||||
export const sixeight: Map = {
|
||||
name: '6x8',
|
||||
category: 'Special',
|
||||
data: [
|
||||
'------',
|
||||
'------',
|
||||
'------',
|
||||
'--wb--',
|
||||
'--bw--',
|
||||
'------',
|
||||
'------',
|
||||
'------'
|
||||
]
|
||||
};
|
||||
|
||||
export const spark: Map = {
|
||||
name: 'Spark',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' - - ',
|
||||
'----------',
|
||||
' -------- ',
|
||||
' -------- ',
|
||||
' ---wb--- ',
|
||||
' ---bw--- ',
|
||||
' -------- ',
|
||||
' -------- ',
|
||||
'----------',
|
||||
' - - '
|
||||
]
|
||||
};
|
||||
|
||||
export const islands: Map = {
|
||||
name: 'Islands',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'-------- ',
|
||||
'---wb--- ',
|
||||
'---bw--- ',
|
||||
'-------- ',
|
||||
' - - ',
|
||||
' - - ',
|
||||
' --------',
|
||||
' --------',
|
||||
' --------',
|
||||
' --------'
|
||||
]
|
||||
};
|
||||
|
||||
export const galaxy: Map = {
|
||||
name: 'Galaxy',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ------ ',
|
||||
' --www--- ',
|
||||
' ------w--- ',
|
||||
'---bbb--w---',
|
||||
'--b---b-w-b-',
|
||||
'-b--wwb-w-b-',
|
||||
'-b-w-bww--b-',
|
||||
'-b-w-b---b--',
|
||||
'---w--bbb---',
|
||||
' ---w------ ',
|
||||
' ---www-- ',
|
||||
' ------ '
|
||||
]
|
||||
};
|
||||
|
||||
export const triangle: Map = {
|
||||
name: 'Triangle',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' -- ',
|
||||
' -- ',
|
||||
' ---- ',
|
||||
' ---- ',
|
||||
' --wb-- ',
|
||||
' --bw-- ',
|
||||
' -------- ',
|
||||
' -------- ',
|
||||
'----------',
|
||||
'----------'
|
||||
]
|
||||
};
|
||||
|
||||
export const iphonex: Map = {
|
||||
name: 'iPhone X',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' -- -- ',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
'--------',
|
||||
' ------ '
|
||||
]
|
||||
};
|
||||
|
||||
export const dealWithIt: Map = {
|
||||
name: 'Deal with it!',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
'------------',
|
||||
'--w-b-------',
|
||||
' --b-w------',
|
||||
' --w-b---- ',
|
||||
' ------- '
|
||||
]
|
||||
};
|
||||
|
||||
export const experiment: Map = {
|
||||
name: 'Let\'s experiment',
|
||||
category: 'Special',
|
||||
author: 'syuilo',
|
||||
data: [
|
||||
' ------------ ',
|
||||
'------wb------',
|
||||
'------bw------',
|
||||
'--------------',
|
||||
' - - ',
|
||||
'------ ------',
|
||||
'bbbbbb wwwwww',
|
||||
'bbbbbb wwwwww',
|
||||
'bbbbbb wwwwww',
|
||||
'bbbbbb wwwwww',
|
||||
'wwwwww bbbbbb'
|
||||
]
|
||||
};
|
||||
|
||||
export const bigBoard: Map = {
|
||||
name: 'Big board',
|
||||
category: 'Special',
|
||||
data: [
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'-------wb-------',
|
||||
'-------bw-------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------',
|
||||
'----------------'
|
||||
]
|
||||
};
|
||||
|
||||
export const twoBoard: Map = {
|
||||
name: 'Two board',
|
||||
category: 'Special',
|
||||
author: 'Aya',
|
||||
data: [
|
||||
'-------- --------',
|
||||
'-------- --------',
|
||||
'-------- --------',
|
||||
'---wb--- ---wb---',
|
||||
'---bw--- ---bw---',
|
||||
'-------- --------',
|
||||
'-------- --------',
|
||||
'-------- --------'
|
||||
]
|
||||
};
|
||||
|
||||
export const test1: Map = {
|
||||
name: 'Test1',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'--------',
|
||||
'---wb---',
|
||||
'---bw---',
|
||||
'--------'
|
||||
]
|
||||
};
|
||||
|
||||
export const test2: Map = {
|
||||
name: 'Test2',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'------',
|
||||
'------',
|
||||
'-b--w-',
|
||||
'-w--b-',
|
||||
'-w--b-'
|
||||
]
|
||||
};
|
||||
|
||||
export const test3: Map = {
|
||||
name: 'Test3',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'-w-',
|
||||
'--w',
|
||||
'w--',
|
||||
'-w-',
|
||||
'--w',
|
||||
'w--',
|
||||
'-w-',
|
||||
'--w',
|
||||
'w--',
|
||||
'-w-',
|
||||
'---',
|
||||
'b--',
|
||||
]
|
||||
};
|
||||
|
||||
export const test4: Map = {
|
||||
name: 'Test4',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'-w--b-',
|
||||
'-w--b-',
|
||||
'------',
|
||||
'-w--b-',
|
||||
'-w--b-'
|
||||
]
|
||||
};
|
||||
|
||||
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)A1に打ってしまう
|
||||
export const test6: Map = {
|
||||
name: 'Test6',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'--wwwww-',
|
||||
'wwwwwwww',
|
||||
'wbbbwbwb',
|
||||
'wbbbbwbb',
|
||||
'wbwbbwbb',
|
||||
'wwbwbbbb',
|
||||
'--wbbbbb',
|
||||
'-wwwww--',
|
||||
]
|
||||
};
|
||||
|
||||
// 検証用: この盤面で藍(lv3)が黒で始めると何故か(?)G7に打ってしまう
|
||||
export const test7: Map = {
|
||||
name: 'Test7',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'b--w----',
|
||||
'b-wwww--',
|
||||
'bwbwwwbb',
|
||||
'wbwwwwb-',
|
||||
'wwwwwww-',
|
||||
'-wwbbwwb',
|
||||
'--wwww--',
|
||||
'--wwww--',
|
||||
]
|
||||
};
|
||||
|
||||
// 検証用: この盤面で藍(lv5)が黒で始めると何故か(?)A1に打ってしまう
|
||||
export const test8: Map = {
|
||||
name: 'Test8',
|
||||
category: 'Test',
|
||||
data: [
|
||||
'--------',
|
||||
'-----w--',
|
||||
'w--www--',
|
||||
'wwwwww--',
|
||||
'bbbbwww-',
|
||||
'wwwwww--',
|
||||
'--www---',
|
||||
'--ww----',
|
||||
]
|
||||
};
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"name": "misskey-reversi",
|
||||
"version": "0.0.5",
|
||||
"description": "Misskey reversi engine",
|
||||
"keywords": [
|
||||
"misskey"
|
||||
],
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/misskey-dev/misskey.git",
|
||||
"bugs": "https://github.com/misskey-dev/misskey/issues",
|
||||
"main": "./built/core.js",
|
||||
"types": "./built/core.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"noEmitOnError": false,
|
||||
"noImplicitAny": false,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"experimentalDecorators": true,
|
||||
"declaration": true,
|
||||
"sourceMap": false,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"removeComments": false,
|
||||
"noLib": false,
|
||||
"outDir": "./built",
|
||||
"rootDir": "./"
|
||||
},
|
||||
"compileOnSave": false,
|
||||
"include": [
|
||||
"./core.ts"
|
||||
]
|
||||
}
|
||||
310
packages/client/src/scripts/get-note-menu.ts
Normal file
310
packages/client/src/scripts/get-note-menu.ts
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
import { Ref } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { $i } from '@/account';
|
||||
import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
import * as os from '@/os';
|
||||
import copyToClipboard from '@/scripts/copy-to-clipboard';
|
||||
import { url } from '@/config';
|
||||
import { noteActions } from '@/store';
|
||||
import { pleaseLogin } from './please-login';
|
||||
|
||||
export function getNoteMenu(props: {
|
||||
note: misskey.entities.Note;
|
||||
menuButton: Ref<HTMLElement>;
|
||||
translation: Ref<any>;
|
||||
translating: Ref<boolean>;
|
||||
}) {
|
||||
const isRenote = (
|
||||
props.note.renote != null &&
|
||||
props.note.text == null &&
|
||||
props.note.fileIds.length === 0 &&
|
||||
props.note.poll == null
|
||||
);
|
||||
|
||||
let appearNote = isRenote ? props.note.renote as misskey.entities.Note : props.note;
|
||||
|
||||
function del(): void {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.locale.noteDeleteConfirm,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
os.api('notes/delete', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function delEdit(): void {
|
||||
os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.locale.deleteAndEditConfirm,
|
||||
}).then(({ canceled }) => {
|
||||
if (canceled) return;
|
||||
|
||||
os.api('notes/delete', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
|
||||
os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel });
|
||||
});
|
||||
}
|
||||
|
||||
function toggleFavorite(favorite: boolean): void {
|
||||
os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
}
|
||||
|
||||
function toggleWatch(watch: boolean): void {
|
||||
os.apiWithDialog(watch ? 'notes/watching/create' : 'notes/watching/delete', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
}
|
||||
|
||||
function toggleThreadMute(mute: boolean): void {
|
||||
os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
}
|
||||
|
||||
function copyContent(): void {
|
||||
copyToClipboard(appearNote.text);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function copyLink(): void {
|
||||
copyToClipboard(`${url}/notes/${appearNote.id}`);
|
||||
os.success();
|
||||
}
|
||||
|
||||
function togglePin(pin: boolean): void {
|
||||
os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', {
|
||||
noteId: appearNote.id
|
||||
}, undefined, null, e => {
|
||||
if (e.id === '72dab508-c64d-498f-8740-a8eec1ba385a') {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.locale.pinLimitExceeded
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function clip(): Promise<void> {
|
||||
const clips = await os.api('clips/list');
|
||||
os.popupMenu([{
|
||||
icon: 'fas fa-plus',
|
||||
text: i18n.locale.createNew,
|
||||
action: async () => {
|
||||
const { canceled, result } = await os.form(i18n.locale.createNewClip, {
|
||||
name: {
|
||||
type: 'string',
|
||||
label: i18n.locale.name
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
multiline: true,
|
||||
label: i18n.locale.description
|
||||
},
|
||||
isPublic: {
|
||||
type: 'boolean',
|
||||
label: i18n.locale.public,
|
||||
default: false
|
||||
}
|
||||
});
|
||||
if (canceled) return;
|
||||
|
||||
const clip = await os.apiWithDialog('clips/create', result);
|
||||
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
}
|
||||
}, null, ...clips.map(clip => ({
|
||||
text: clip.name,
|
||||
action: () => {
|
||||
os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
|
||||
}
|
||||
}))], props.menuButton.value, {
|
||||
}).then(focus);
|
||||
}
|
||||
|
||||
async function promote(): Promise<void> {
|
||||
const { canceled, result: days } = await os.inputNumber({
|
||||
title: i18n.locale.numberOfDays,
|
||||
});
|
||||
|
||||
if (canceled) return;
|
||||
|
||||
os.apiWithDialog('admin/promo/create', {
|
||||
noteId: appearNote.id,
|
||||
expiresAt: Date.now() + (86400000 * days),
|
||||
});
|
||||
}
|
||||
|
||||
function share(): void {
|
||||
navigator.share({
|
||||
title: i18n.t('noteOf', { user: appearNote.user.name }),
|
||||
text: appearNote.text,
|
||||
url: `${url}/notes/${appearNote.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
async function translate(): Promise<void> {
|
||||
if (props.translation.value != null) return;
|
||||
props.translating.value = true;
|
||||
const res = await os.api('notes/translate', {
|
||||
noteId: appearNote.id,
|
||||
targetLang: localStorage.getItem('lang') || navigator.language,
|
||||
});
|
||||
props.translating.value = false;
|
||||
props.translation.value = res;
|
||||
}
|
||||
|
||||
let menu;
|
||||
if ($i) {
|
||||
const statePromise = os.api('notes/state', {
|
||||
noteId: appearNote.id
|
||||
});
|
||||
|
||||
menu = [{
|
||||
icon: 'fas fa-copy',
|
||||
text: i18n.locale.copyContent,
|
||||
action: copyContent
|
||||
}, {
|
||||
icon: 'fas fa-link',
|
||||
text: i18n.locale.copyLink,
|
||||
action: copyLink
|
||||
}, (appearNote.url || appearNote.uri) ? {
|
||||
icon: 'fas fa-external-link-square-alt',
|
||||
text: i18n.locale.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url || appearNote.uri, '_blank');
|
||||
}
|
||||
} : undefined,
|
||||
{
|
||||
icon: 'fas fa-share-alt',
|
||||
text: i18n.locale.share,
|
||||
action: share
|
||||
},
|
||||
instance.translatorAvailable ? {
|
||||
icon: 'fas fa-language',
|
||||
text: i18n.locale.translate,
|
||||
action: translate
|
||||
} : undefined,
|
||||
null,
|
||||
statePromise.then(state => state.isFavorited ? {
|
||||
icon: 'fas fa-star',
|
||||
text: i18n.locale.unfavorite,
|
||||
action: () => toggleFavorite(false)
|
||||
} : {
|
||||
icon: 'fas fa-star',
|
||||
text: i18n.locale.favorite,
|
||||
action: () => toggleFavorite(true)
|
||||
}),
|
||||
{
|
||||
icon: 'fas fa-paperclip',
|
||||
text: i18n.locale.clip,
|
||||
action: () => clip()
|
||||
},
|
||||
(appearNote.userId != $i.id) ? statePromise.then(state => state.isWatching ? {
|
||||
icon: 'fas fa-eye-slash',
|
||||
text: i18n.locale.unwatch,
|
||||
action: () => toggleWatch(false)
|
||||
} : {
|
||||
icon: 'fas fa-eye',
|
||||
text: i18n.locale.watch,
|
||||
action: () => toggleWatch(true)
|
||||
}) : undefined,
|
||||
statePromise.then(state => state.isMutedThread ? {
|
||||
icon: 'fas fa-comment-slash',
|
||||
text: i18n.locale.unmuteThread,
|
||||
action: () => toggleThreadMute(false)
|
||||
} : {
|
||||
icon: 'fas fa-comment-slash',
|
||||
text: i18n.locale.muteThread,
|
||||
action: () => toggleThreadMute(true)
|
||||
}),
|
||||
appearNote.userId == $i.id ? ($i.pinnedNoteIds || []).includes(appearNote.id) ? {
|
||||
icon: 'fas fa-thumbtack',
|
||||
text: i18n.locale.unpin,
|
||||
action: () => togglePin(false)
|
||||
} : {
|
||||
icon: 'fas fa-thumbtack',
|
||||
text: i18n.locale.pin,
|
||||
action: () => togglePin(true)
|
||||
} : undefined,
|
||||
/*
|
||||
...($i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
{
|
||||
icon: 'fas fa-bullhorn',
|
||||
text: i18n.locale.promote,
|
||||
action: promote
|
||||
}]
|
||||
: []
|
||||
),*/
|
||||
...(appearNote.userId != $i.id ? [
|
||||
null,
|
||||
{
|
||||
icon: 'fas fa-exclamation-circle',
|
||||
text: i18n.locale.reportAbuse,
|
||||
action: () => {
|
||||
const u = `${url}/notes/${appearNote.id}`;
|
||||
os.popup(import('@/components/abuse-report-window.vue'), {
|
||||
user: appearNote.user,
|
||||
initialComment: `Note: ${u}\n-----\n`
|
||||
}, {}, 'closed');
|
||||
}
|
||||
}]
|
||||
: []
|
||||
),
|
||||
...(appearNote.userId == $i.id || $i.isModerator || $i.isAdmin ? [
|
||||
null,
|
||||
appearNote.userId == $i.id ? {
|
||||
icon: 'fas fa-edit',
|
||||
text: i18n.locale.deleteAndEdit,
|
||||
action: delEdit
|
||||
} : undefined,
|
||||
{
|
||||
icon: 'fas fa-trash-alt',
|
||||
text: i18n.locale.delete,
|
||||
danger: true,
|
||||
action: del
|
||||
}]
|
||||
: []
|
||||
)]
|
||||
.filter(x => x !== undefined);
|
||||
} else {
|
||||
menu = [{
|
||||
icon: 'fas fa-copy',
|
||||
text: i18n.locale.copyContent,
|
||||
action: copyContent
|
||||
}, {
|
||||
icon: 'fas fa-link',
|
||||
text: i18n.locale.copyLink,
|
||||
action: copyLink
|
||||
}, (appearNote.url || appearNote.uri) ? {
|
||||
icon: 'fas fa-external-link-square-alt',
|
||||
text: i18n.locale.showOnRemote,
|
||||
action: () => {
|
||||
window.open(appearNote.url || appearNote.uri, '_blank');
|
||||
}
|
||||
} : undefined]
|
||||
.filter(x => x !== undefined);
|
||||
}
|
||||
|
||||
if (noteActions.length > 0) {
|
||||
menu = menu.concat([null, ...noteActions.map(action => ({
|
||||
icon: 'fas fa-plug',
|
||||
text: action.title,
|
||||
action: () => {
|
||||
action.handler(appearNote);
|
||||
}
|
||||
}))]);
|
||||
}
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
|
@ -136,7 +136,7 @@ export function physics(container: HTMLElement) {
|
|||
}
|
||||
|
||||
// 奈落に落ちたオブジェクトは消す
|
||||
const intervalId = setInterval(() => {
|
||||
const intervalId = window.setInterval(() => {
|
||||
for (const obj of objs) {
|
||||
if (obj.position.y > (containerHeight + 1024)) Matter.World.remove(world, obj);
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ export function physics(container: HTMLElement) {
|
|||
stop: () => {
|
||||
stop = true;
|
||||
Matter.Runner.stop(runner);
|
||||
clearInterval(intervalId);
|
||||
window.clearInterval(intervalId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,11 +34,11 @@ export const builtinThemes = [
|
|||
let timeout = null;
|
||||
|
||||
export function applyTheme(theme: Theme, persist = true) {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
if (timeout) window.clearTimeout(timeout);
|
||||
|
||||
document.documentElement.classList.add('_themeChanging_');
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
timeout = window.setTimeout(() => {
|
||||
document.documentElement.classList.remove('_themeChanging_');
|
||||
}, 1000);
|
||||
|
||||
|
|
|
|||
34
packages/client/src/scripts/use-leave-guard.ts
Normal file
34
packages/client/src/scripts/use-leave-guard.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { inject, onUnmounted, Ref } from 'vue';
|
||||
import { i18n } from '@/i18n';
|
||||
import * as os from '@/os';
|
||||
|
||||
export function useLeaveGuard(enabled: Ref<boolean>) {
|
||||
const setLeaveGuard = inject('setLeaveGuard');
|
||||
|
||||
if (setLeaveGuard) {
|
||||
setLeaveGuard(async () => {
|
||||
if (!enabled.value) return false;
|
||||
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
text: i18n.locale.leaveConfirm,
|
||||
});
|
||||
|
||||
return canceled;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
function onBeforeLeave(ev: BeforeUnloadEvent) {
|
||||
if (enabled.value) {
|
||||
ev.preventDefault();
|
||||
ev.returnValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', onBeforeLeave);
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('beforeunload', onBeforeLeave);
|
||||
});
|
||||
*/
|
||||
}
|
||||
123
packages/client/src/scripts/use-note-capture.ts
Normal file
123
packages/client/src/scripts/use-note-capture.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { onUnmounted, Ref } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { stream } from '@/stream';
|
||||
import { $i } from '@/account';
|
||||
|
||||
export function useNoteCapture(props: {
|
||||
rootEl: Ref<HTMLElement>;
|
||||
appearNote: Ref<misskey.entities.Note>;
|
||||
}) {
|
||||
const appearNote = props.appearNote;
|
||||
const connection = $i ? stream : null;
|
||||
|
||||
function onStreamNoteUpdated(data): void {
|
||||
const { type, id, body } = data;
|
||||
|
||||
if (id !== appearNote.value.id) return;
|
||||
|
||||
switch (type) {
|
||||
case 'reacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
const updated = JSON.parse(JSON.stringify(appearNote.value));
|
||||
|
||||
if (body.emoji) {
|
||||
const emojis = appearNote.value.emojis || [];
|
||||
if (!emojis.includes(body.emoji)) {
|
||||
updated.emojis = [...emojis, body.emoji];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
|
||||
|
||||
updated.reactions[reaction] = currentCount + 1;
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
updated.myReaction = reaction;
|
||||
}
|
||||
|
||||
appearNote.value = updated;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unreacted': {
|
||||
const reaction = body.reaction;
|
||||
|
||||
const updated = JSON.parse(JSON.stringify(appearNote.value));
|
||||
|
||||
// TODO: reactionsプロパティがない場合ってあったっけ? なければ || {} は消せる
|
||||
const currentCount = (appearNote.value.reactions || {})[reaction] || 0;
|
||||
|
||||
updated.reactions[reaction] = Math.max(0, currentCount - 1);
|
||||
|
||||
if ($i && (body.userId === $i.id)) {
|
||||
updated.myReaction = null;
|
||||
}
|
||||
|
||||
appearNote.value = updated;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pollVoted': {
|
||||
const choice = body.choice;
|
||||
|
||||
const updated = JSON.parse(JSON.stringify(appearNote.value));
|
||||
|
||||
const choices = [...appearNote.value.poll.choices];
|
||||
choices[choice] = {
|
||||
...choices[choice],
|
||||
votes: choices[choice].votes + 1,
|
||||
...($i && (body.userId === $i.id) ? {
|
||||
isVoted: true
|
||||
} : {})
|
||||
};
|
||||
|
||||
updated.poll.choices = choices;
|
||||
|
||||
appearNote.value = updated;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'deleted': {
|
||||
const updated = JSON.parse(JSON.stringify(appearNote.value));
|
||||
updated.value = true;
|
||||
appearNote.value = updated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function capture(withHandler = false): void {
|
||||
if (connection) {
|
||||
// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
|
||||
connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: appearNote.value.id });
|
||||
if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
function decapture(withHandler = false): void {
|
||||
if (connection) {
|
||||
connection.send('un', {
|
||||
id: appearNote.value.id,
|
||||
});
|
||||
if (withHandler) connection.off('noteUpdated', onStreamNoteUpdated);
|
||||
}
|
||||
}
|
||||
|
||||
function onStreamConnected() {
|
||||
capture(false);
|
||||
}
|
||||
|
||||
capture(true);
|
||||
if (connection) {
|
||||
connection.on('_connected_', onStreamConnected);
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
decapture(true);
|
||||
if (connection) {
|
||||
connection.off('_connected_', onStreamConnected);
|
||||
}
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue