Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
f7959c073f | |||
6953970be7 | |||
1496fdaf80 | |||
0fc034b1ac | |||
c3312c918e | |||
5a13964ced | |||
fe07b1cb7f | |||
d805a70508 | |||
0f0009e0db | |||
4c4cb2bb17 | |||
fe319a529f | |||
91bea1f6c7 | |||
01745f7c65 | |||
5d3943ffa8 | |||
e66d7babc5 | |||
80e5645a84 | |||
a766faeae9 | |||
4d2d226446 | |||
61e83b10c3 | |||
ed675f0956 | |||
9cce8ab214 | |||
daa409cd82 | |||
9d65415fdc | |||
8c40917cc2 | |||
871f886702 | |||
f19075c50a | |||
71da205ab7 | |||
a34cc47a11 | |||
cbddaf1d19 | |||
1f1ed2da4c | |||
8d81bd0dc0 | |||
5773a5bfa6 | |||
7275a48102 | |||
8f84dd610c | |||
f1f466ed23 | |||
0ca5237139 | |||
20549bfdf0 | |||
d692bb3c52 | |||
44cd1e9223 | |||
f0fec654ff | |||
4e04e5e0c0 | |||
4991fb2769 | |||
4d90d554f8 | |||
e5468713ac | |||
77013f982d | |||
0460cdedd7 | |||
73f5bf69e8 | |||
750c0d7df2 | |||
2fcebdd281 | |||
e4e65a4cd5 | |||
e010ecb03f | |||
fc74db668d | |||
1bac3418b4 | |||
53df8c48b7 | |||
92702fe47e | |||
017c4c12cd | |||
830d246ba4 | |||
6b33afa916 | |||
69a3efd534 | |||
2d0adb8f4c | |||
da9d8cb138 | |||
2acaca8582 | |||
11cf82c6a4 | |||
1ef66c962a | |||
03f20599ba | |||
d150b10b3e | |||
c4f323aae3 | |||
8297f8ccd0 | |||
f336241576 | |||
f6d9a7e7c3 | |||
80d1ee7543 | |||
e55a254353 | |||
555a0f276c | |||
cdce7aa5e2 | |||
82cea185b2 | |||
f92a4bb195 | |||
9f4f88df9c | |||
e69803cbd1 | |||
a9885be09e | |||
7b011f4a91 | |||
41c404abe6 | |||
2089a761cf | |||
0ee2df010d | |||
466844c016 | |||
bbf9a08649 | |||
c985c66652 | |||
f9dc96320e | |||
42552789fe | |||
1a2ffeb0b5 | |||
4f75493249 | |||
42193695fb | |||
02af0de21e | |||
5f8e10e524 | |||
cee93d746c | |||
08704a383f | |||
acdf7c244f | |||
a72b6745aa | |||
24086e9023 | |||
c3d4b5ad38 | |||
cc618a83e5 | |||
9eaa0b27db | |||
a8835a679e | |||
656bc6df84 | |||
019aaf7d82 | |||
76bafbf398 | |||
030bcb99b1 |
17
CHANGELOG.md
@ -1,6 +1,23 @@
|
||||
ChangeLog
|
||||
=========
|
||||
|
||||
破壊的変更のみ記載。
|
||||
|
||||
This document describes breaking changes only.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
オセロがリバーシに変更されました。
|
||||
|
||||
Othello is now Reversi.
|
||||
|
||||
### Migration
|
||||
|
||||
MongoDBの、`othelloGames`と`othelloMatchings`コレクションをそれぞれ`reversiGames`と`reversiMatchings`にリネームしてください。
|
||||
|
||||
You need to rename `othelloGames` and `othelloMatchings` MongoDB collections to `reversiGames` and `reversiMatchings`.
|
||||
|
||||
3.0.0
|
||||
-----
|
||||
|
||||
|
BIN
assets/icons/128.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/icons/16.png
Normal file
After Width: | Height: | Size: 446 B |
BIN
assets/icons/192.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/icons/256.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
BIN
assets/icons/32.png
Normal file
After Width: | Height: | Size: 774 B |
BIN
assets/icons/64.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
@ -8,12 +8,12 @@ import * as gutil from 'gulp-util';
|
||||
import * as ts from 'gulp-typescript';
|
||||
const sourcemaps = require('gulp-sourcemaps');
|
||||
import tslint from 'gulp-tslint';
|
||||
import cssnano = require('gulp-cssnano');
|
||||
const cssnano = require('gulp-cssnano');
|
||||
import * as uglifyComposer from 'gulp-uglify/composer';
|
||||
import pug = require('gulp-pug');
|
||||
import * as rimraf from 'rimraf';
|
||||
import chalk from 'chalk';
|
||||
import imagemin = require('gulp-imagemin');
|
||||
const imagemin = require('gulp-imagemin');
|
||||
import * as rename from 'gulp-rename';
|
||||
import * as mocha from 'gulp-mocha';
|
||||
import * as replace from 'gulp-replace';
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "Deutsch"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "Serverinformationen"
|
||||
toggle: "Sicht umschalten"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "Mehr"
|
||||
close: "Schließen"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Othello"
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Verbunden"
|
||||
add-widget: "Widget hinzufügen:"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "English"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Thanks for finding Misskey. Misskey is a <b>decentralized microblogging platform</b> born on Earth. Since it exists within Fediverse (a universe where various social media platforms are organized) it is mutually linked with other social media platforms. Why don't you take a short break from the hustle and bustle of the city, and dive into a new Internet?"
|
||||
time:
|
||||
@ -47,6 +47,7 @@ common:
|
||||
ok: "OK"
|
||||
update-available: "A new version of Misskey is now available({newer}, the current version is {current}). Reload the page to apply updates."
|
||||
my-token-regenerated: "Your token has been renewed so you will be signed out."
|
||||
i-like-sushi: "I like sushi rather than pudding"
|
||||
widgets:
|
||||
analog-clock: "Analog clock"
|
||||
profile: "Profile"
|
||||
@ -228,6 +229,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "Hashtags"
|
||||
count: "{} users mentioned"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "Server info"
|
||||
toggle: "Toggle views"
|
||||
@ -333,7 +335,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "More"
|
||||
close: "Close"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Othello"
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Submit"
|
||||
add-widget: "Add widget:"
|
||||
@ -549,7 +551,7 @@ desktop/views/components/ui.header.nav.vue:
|
||||
home: "Home"
|
||||
deck: "Deck"
|
||||
messaging: "Messages"
|
||||
game: "Play Othello"
|
||||
game: "Play Reversi"
|
||||
desktop/views/components/ui.header.notifications.vue:
|
||||
title: "Notifications"
|
||||
desktop/views/components/ui.header.post.vue:
|
||||
|
563
locales/es.yml
@ -1,223 +1,223 @@
|
||||
---
|
||||
meta:
|
||||
lang: "日本語"
|
||||
lang: "Español"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
misskey: "Una ⭐️ del fediverso"
|
||||
about-title: "Una ⭐️ del fediverso"
|
||||
about: "Gracias por encontrae Misskey. Misskey es una <b>plataforma descentralizada de microblogging</b> nacida en la Tierra. Gracias a existir dentro del Fediverso (un universo donde se organizan varias plataformas sociales) se encuentra enlazada mutuamente con otras plataformas sociales. ¿Por què no te tomas un respiro del caos de la ciudad y te sumerges es una nueva manera de entender Internet?"
|
||||
time:
|
||||
unknown: "なぞのじかん"
|
||||
future: "未来"
|
||||
just_now: "たった今"
|
||||
seconds_ago: "{}秒前"
|
||||
minutes_ago: "{}分前"
|
||||
hours_ago: "{}時間前"
|
||||
days_ago: "{}日前"
|
||||
weeks_ago: "{}週間前"
|
||||
months_ago: "{}ヶ月前"
|
||||
years_ago: "{}年前"
|
||||
unknown: "Desconocido"
|
||||
future: "Futuro"
|
||||
just_now: "Ahora mismo"
|
||||
seconds_ago: "Hace {}"
|
||||
minutes_ago: "Hace {} minuto(s)"
|
||||
hours_ago: "Hace {} hora(s)"
|
||||
days_ago: "Hace {} dia(s)"
|
||||
weeks_ago: "Hace {} semana(s)"
|
||||
months_ago: "Hace {} mes(es)"
|
||||
years_ago: "Hace {} año(s)"
|
||||
weekday-short:
|
||||
sunday: "日"
|
||||
monday: "月"
|
||||
tuesday: "火"
|
||||
wednesday: "水"
|
||||
thursday: "木"
|
||||
friday: "金"
|
||||
saturday: "土"
|
||||
sunday: "domingo"
|
||||
monday: "lunes"
|
||||
tuesday: "martes"
|
||||
wednesday: "miércoles"
|
||||
thursday: "jueves"
|
||||
friday: "viernes"
|
||||
saturday: "sábado"
|
||||
reactions:
|
||||
like: "いいね"
|
||||
love: "しゅき"
|
||||
laugh: "笑"
|
||||
hmm: "ふぅ~む"
|
||||
surprise: "わお"
|
||||
congrats: "おめでとう"
|
||||
angry: "おこ"
|
||||
confused: "こまこまのこまり"
|
||||
pudding: "Pudding"
|
||||
like: "me gusta"
|
||||
love: "amor"
|
||||
laugh: "risa"
|
||||
hmm: "hmm"
|
||||
surprise: "sorpresa"
|
||||
congrats: "felicidades"
|
||||
angry: "enfadado"
|
||||
confused: "confundido"
|
||||
pudding: "Chafado"
|
||||
note-placeholders:
|
||||
a: "今どうしてる?"
|
||||
b: "何かありましたか?"
|
||||
c: "何をお考えですか?"
|
||||
d: "言いたいことは?"
|
||||
e: "ここに書いてください"
|
||||
f: "あなたが書くのを待っています..."
|
||||
delete: "削除"
|
||||
loading: "読み込み中"
|
||||
ok: "わかった"
|
||||
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
||||
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
||||
a: "¿Qué haces?"
|
||||
b: "¿Qué está pasando?"
|
||||
c: "¿Qué te pasa por la cabeza?"
|
||||
d: "¿Quieres decir algo?"
|
||||
e: "¡Escribe aquí!"
|
||||
f: "Esperando a que escribas algo..."
|
||||
delete: "eliminar"
|
||||
loading: "cargando"
|
||||
ok: "OK"
|
||||
update-available: "Hay disponible una nueva versión de Misskey ({newer}, la versión actual es {current}). Refresca la página para aplicar las actualizaciones."
|
||||
my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado."
|
||||
widgets:
|
||||
analog-clock: "アナログ時計"
|
||||
profile: "プロフィール"
|
||||
calendar: "カレンダー"
|
||||
timemachine: "カレンダー(タイムマシン)"
|
||||
activity: "アクティビティ"
|
||||
rss: "RSSリーダー"
|
||||
memo: "付箋"
|
||||
trends: "トレンド"
|
||||
photo-stream: "フォトストリーム"
|
||||
posts-monitor: "投稿チャート"
|
||||
slideshow: "スライドショー"
|
||||
version: "バージョン"
|
||||
broadcast: "ブロードキャスト"
|
||||
notifications: "通知"
|
||||
users: "おすすめユーザー"
|
||||
polls: "アンケート"
|
||||
post-form: "投稿フォーム"
|
||||
messaging: "メッセージ"
|
||||
server: "サーバー情報"
|
||||
donation: "寄付のお願い"
|
||||
nav: "ナビゲーション"
|
||||
tips: "ヒント"
|
||||
hashtags: "ハッシュタグ"
|
||||
analog-clock: "Reloj analógico"
|
||||
profile: "Perfil"
|
||||
calendar: "Calendario"
|
||||
timemachine: "Calendario (máquina del tiempo)"
|
||||
activity: "Actividad"
|
||||
rss: "Lector RSS"
|
||||
memo: "Notas adhesivas"
|
||||
trends: "Tendencias"
|
||||
photo-stream: "Secuencia de fotos"
|
||||
posts-monitor: "Gráfico de publicaciones"
|
||||
slideshow: "Diapositivas"
|
||||
version: "Versión"
|
||||
broadcast: "Transmisión"
|
||||
notifications: "Notificaciones"
|
||||
users: "Usuarios destacados"
|
||||
polls: "Encuestas"
|
||||
post-form: "Formulario"
|
||||
messaging: "Mensajes"
|
||||
server: "Información del servidor"
|
||||
donation: "Donaciones"
|
||||
nav: "Navegación"
|
||||
tips: "Consejos"
|
||||
hashtags: "Etiquetas"
|
||||
deck:
|
||||
widgets: "ウィジェット"
|
||||
home: "ホーム"
|
||||
local: "ローカル"
|
||||
global: "グローバル"
|
||||
notifications: "通知"
|
||||
list: "リスト"
|
||||
swap-left: "左に移動"
|
||||
swap-right: "右に移動"
|
||||
swap-up: "上に移動"
|
||||
swap-down: "下に移動"
|
||||
remove: "カラムを削除"
|
||||
add-column: "カラムを追加"
|
||||
rename: "名前を変更"
|
||||
stack-left: "左に重ねる"
|
||||
pop-right: "右に出す"
|
||||
widgets: "Accesorios"
|
||||
home: "Inicio"
|
||||
local: "Local"
|
||||
global: "Global"
|
||||
notifications: "Notificaciones"
|
||||
list: "Listado"
|
||||
swap-left: "Desplazar a la izq."
|
||||
swap-right: "Desplazar a la dcha."
|
||||
swap-up: "Desplazar arriba"
|
||||
swap-down: "Desplazar abajo"
|
||||
remove: "Borrar"
|
||||
add-column: "Añadir columna"
|
||||
rename: "Renombrar"
|
||||
stack-left: "A la izqda."
|
||||
pop-right: "A la dcha."
|
||||
common/views/components/connect-failed.vue:
|
||||
title: "サーバーに接続できません"
|
||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。"
|
||||
thanks: "いつもMisskeyをご利用いただきありがとうございます。"
|
||||
troubleshoot: "トラブルシュート"
|
||||
title: "Imposible conectar al servidor"
|
||||
description: "Hay un problema en tu conexió o puede que el servidor esté caido o en mantenimiento. Por favor {try again} más tarde."
|
||||
thanks: "Gracias por usar Misskey."
|
||||
troubleshoot: "Problemas más frecuentes"
|
||||
common/views/components/connect-failed.troubleshooter.vue:
|
||||
title: "トラブルシューティング"
|
||||
network: "ネットワーク接続"
|
||||
checking-network: "ネットワーク接続を確認中"
|
||||
internet: "インターネット接続"
|
||||
checking-internet: "インターネット接続を確認中"
|
||||
server: "サーバー接続"
|
||||
checking-server: "サーバー接続を確認中"
|
||||
finding: "問題を調べています"
|
||||
no-network: "ネットワークに接続されていません"
|
||||
no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。"
|
||||
no-internet: "インターネットに接続されていません"
|
||||
no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。"
|
||||
no-server: "Misskeyのサーバーに接続できません"
|
||||
no-server-desc: "お使いのPCのインターネット接続は正常ですが、Misskeyのサーバーには接続できませんでした。サーバーがダウンまたはメンテナンスしている可能性があるので、しばらくしてから再度御アクセスください。"
|
||||
success: "Misskeyのサーバーに接続できました"
|
||||
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。"
|
||||
flush: "キャッシュの削除"
|
||||
set-version: "バージョン指定"
|
||||
title: "Resolución de problemas"
|
||||
network: "Conexión de red"
|
||||
checking-network: "Verificar la conexión a la red"
|
||||
internet: "Conexión a Internet"
|
||||
checking-internet: "Comprobando la conexión a Internet"
|
||||
server: "Conexión al servidor"
|
||||
checking-server: "Probando la conexión al servidor"
|
||||
finding: "Buscando cualquier problema"
|
||||
no-network: "Sin conexión"
|
||||
no-network-desc: "Por favor, asegurate que estás conectado a una red"
|
||||
no-internet: "Sin conexión a Internet"
|
||||
no-internet-desc: "Por favor, asegurate de estar conectado a Internet."
|
||||
no-server: "Imposible conectarse al servidor de Misskey"
|
||||
no-server-desc: "La conexión de red de tu PC es correcta, aún así no puedes conectarte al servidor de Misskey. Es posible que el servidor esté caido o en mantenimiento. Por favor vuelve a intentarlo más tarde."
|
||||
success: "Conectado al servidor de Misskey de manera correcta"
|
||||
success-desc: "Parece que la conexión ha sido posible. Por favor refresca la página."
|
||||
flush: "Limpiar la memoria caché"
|
||||
set-version: "Escoge la versión"
|
||||
common/views/components/messaging.vue:
|
||||
search-user: "ユーザーを探す"
|
||||
you: "あなた"
|
||||
no-history: "履歴はありません"
|
||||
search-user: "Encuentra un usuario"
|
||||
you: "Tu"
|
||||
no-history: "Sin historial"
|
||||
common/views/components/messaging-room.vue:
|
||||
empty: "このユーザーと話したことはありません"
|
||||
more: "もっと読む"
|
||||
no-history: "これより過去の履歴はありません"
|
||||
resize-form: "ドラッグしてフォームの広さを調整"
|
||||
new-message: "新しいメッセージがあります"
|
||||
empty: "Sin conversaciones"
|
||||
more: "Leer más"
|
||||
no-history: "El historial se ha acabado"
|
||||
resize-form: "Arrastra para redimensionar"
|
||||
new-message: "Nuevo mensaje"
|
||||
common/views/components/messaging-room.form.vue:
|
||||
input-message-here: "ここにメッセージを入力"
|
||||
send: "送信"
|
||||
attach-from-local: "PCからファイルを添付する"
|
||||
attach-from-drive: "ドライブからファイルを添付する"
|
||||
input-message-here: "Escribe el mensaje aquí"
|
||||
send: "Enviar"
|
||||
attach-from-local: "Adjunta ficheros desde tu PC"
|
||||
attach-from-drive: "Adjunta ficheros desde tu disco"
|
||||
common/views/components/messaging-room.message.vue:
|
||||
is-read: "既読"
|
||||
deleted: "このメッセージは削除されました"
|
||||
is-read: "Leer"
|
||||
deleted: "El mensaje se ha borrado"
|
||||
common/views/components/nav.vue:
|
||||
about: "Misskeyについて"
|
||||
stats: "統計"
|
||||
status: "ステータス"
|
||||
about: "Sobre"
|
||||
stats: "Estadísticas"
|
||||
status: "Estado"
|
||||
wiki: "Wiki"
|
||||
donors: "ドナー"
|
||||
repository: "リポジトリ"
|
||||
develop: "開発者"
|
||||
feedback: "フィードバック"
|
||||
donors: "Donantes"
|
||||
repository: "Repositorio"
|
||||
develop: "Desarrolladores"
|
||||
feedback: "Opiniones"
|
||||
common/views/components/note-menu.vue:
|
||||
favorite: "お気に入り"
|
||||
pin: "ピン留め"
|
||||
delete: "削除"
|
||||
delete-confirm: "この投稿を削除しますか?"
|
||||
remote: "投稿元で見る"
|
||||
favorite: "Me gusta esta nota"
|
||||
pin: "Fijar en el perfil"
|
||||
delete: "Borrar"
|
||||
delete-confirm: "¿Seguro que quieres borrar la publicación?"
|
||||
remote: "Ver el original"
|
||||
common/views/components/poll.vue:
|
||||
vote-to: "「{}」に投票する"
|
||||
vote-count: "{}票"
|
||||
total-users: "{}人が投票"
|
||||
vote: "投票する"
|
||||
show-result: "結果を見る"
|
||||
voted: "投票済み"
|
||||
vote-to: "'{}' para votar"
|
||||
vote-count: "{} votos"
|
||||
total-users: "{} usuario(s) que ha(n) votado"
|
||||
vote: "Vota"
|
||||
show-result: "Mostrar resultados"
|
||||
voted: "Votado"
|
||||
common/views/components/poll-editor.vue:
|
||||
no-only-one-choice: "アンケートには、選択肢が最低2つ必要です"
|
||||
choice-n: "選択肢{}"
|
||||
remove: "この選択肢を削除"
|
||||
add: "+選択肢を追加"
|
||||
destroy: "アンケートを破棄"
|
||||
no-only-one-choice: "Selecciona dos o más opciones."
|
||||
choice-n: "{} opcion(es)"
|
||||
remove: "Borra la opción"
|
||||
add: "+ Añade una opción"
|
||||
destroy: "Cancelar la encuesta"
|
||||
common/views/components/reaction-picker.vue:
|
||||
choose-reaction: "リアクションを選択"
|
||||
choose-reaction: "Escoge una reacción"
|
||||
common/views/components/signin.vue:
|
||||
username: "ユーザー名"
|
||||
password: "パスワード"
|
||||
token: "トークン"
|
||||
signing-in: "やってます..."
|
||||
signin: "サインイン"
|
||||
username: "Usuario"
|
||||
password: "Contraseña"
|
||||
token: "Identificador"
|
||||
signing-in: "Entrando..."
|
||||
signin: "Entra"
|
||||
common/views/components/signup.vue:
|
||||
username: "ユーザー名"
|
||||
checking: "確認しています..."
|
||||
available: "利用できます"
|
||||
unavailable: "既に利用されています"
|
||||
error: "通信エラー"
|
||||
invalid-format: "a~z、A~Z、0~9、_が使えます"
|
||||
too-short: "1文字以上でお願いします!"
|
||||
too-long: "20文字以内でお願いします"
|
||||
password: "パスワード"
|
||||
password-placeholder: "8文字以上を推奨します"
|
||||
weak-password: "弱いパスワード"
|
||||
normal-password: "まあまあのパスワード"
|
||||
strong-password: "強いパスワード"
|
||||
retype: "再入力"
|
||||
retype-placeholder: "確認のため再入力してください"
|
||||
password-matched: "確認されました"
|
||||
password-not-matched: "一致していません"
|
||||
recaptcha: "認証"
|
||||
create: "アカウント作成"
|
||||
some-error: "何らかの原因によりアカウントの作成に失敗しました。再度お試しください。"
|
||||
username: "Usuario"
|
||||
checking: "Comprobando..."
|
||||
available: "Disponible"
|
||||
unavailable: "Utilizado"
|
||||
error: "Error de conexión"
|
||||
invalid-format: "utiliza letras, números y/o -."
|
||||
too-short: "¡Mínimo tienes que introducir un caracter!"
|
||||
too-long: "No puedes usar más de 20 caracteres."
|
||||
password: "Contraseña"
|
||||
password-placeholder: "Te recomendamos más de 8 caracteres"
|
||||
weak-password: "Contraseña débil"
|
||||
normal-password: "No está mal"
|
||||
strong-password: "Muy buena contraseña"
|
||||
retype: "Inténtalo otra vez"
|
||||
retype-placeholder: "Confirma la contraseña"
|
||||
password-matched: "OK"
|
||||
password-not-matched: "Las contraseñas no son las mismas"
|
||||
recaptcha: "Verificar"
|
||||
create: "Crea una cuenta"
|
||||
some-error: "Por algún motivo no se ha podido crear la cuenta. Por favor inténtalo de nuevo."
|
||||
common/views/components/special-message.vue:
|
||||
new-year: "Happy New Year!"
|
||||
christmas: "Merry Christmas!"
|
||||
new-year: "¡Feliz Año Nuevo!"
|
||||
christmas: "¡Feliz Navidad!"
|
||||
common/views/components/stream-indicator.vue:
|
||||
connecting: "接続中"
|
||||
reconnecting: "再接続中"
|
||||
connected: "接続完了"
|
||||
connecting: "Conectando"
|
||||
reconnecting: "Reconectando"
|
||||
connected: "Conectado"
|
||||
common/views/components/twitter-setting.vue:
|
||||
description: "お使いのTwitterアカウントをお使いのMisskeyアカウントに接続しておくと、プロフィールでTwitterアカウント情報が表示されるようになったり、Twitterを用いた便利なサインインを利用できるようになります。"
|
||||
connected-to: "次のTwitterアカウントに接続されています"
|
||||
detail: "詳細..."
|
||||
reconnect: "再接続する"
|
||||
connect: "Twitterと接続する"
|
||||
disconnect: "切断する"
|
||||
description: "Si comectas tu cuenta de Twitter con tu cuenta de Misskey podrás ver la información de tu cuemta de Twitter en tu perfil y además podrás entrar usando Twitter."
|
||||
connected-to: "Estas comectado con las siguientes cuentas de Twitter"
|
||||
detail: "Detalles..."
|
||||
reconnect: "Conectar de nuevo"
|
||||
connect: "Conectate usando Twitter"
|
||||
disconnect: "Desconectado"
|
||||
common/views/components/uploader.vue:
|
||||
waiting: "待機中"
|
||||
waiting: "Un momento"
|
||||
common/views/components/visibility-chooser.vue:
|
||||
public: "公開"
|
||||
home: "ホーム"
|
||||
home-desc: "ホームタイムラインにのみ公開"
|
||||
followers: "フォロワー"
|
||||
followers-desc: "自分のフォロワーにのみ公開"
|
||||
specified: "ダイレクト"
|
||||
specified-desc: "指定したユーザーにのみ公開"
|
||||
private: "非公開"
|
||||
public: "Público"
|
||||
home: "Inicio"
|
||||
home-desc: "Publica solo en la página de inicio"
|
||||
followers: "Seguidores"
|
||||
followers-desc: "Piblica solo para tus seguidores"
|
||||
specified: "Directo"
|
||||
specified-desc: "Publica solo para los seguidores que quieras"
|
||||
private: "Privada"
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "確認中"
|
||||
no-broadcasts: "お知らせはありません"
|
||||
have-a-nice-day: "良い一日を!"
|
||||
next: "次"
|
||||
fetching: "Recuperando"
|
||||
no-broadcasts: "Sin emisión"
|
||||
have-a-nice-day: "¡Buenos dias!"
|
||||
next: "Siguiente"
|
||||
common/views/widgets/donation.vue:
|
||||
title: "寄付のお願い"
|
||||
title: "Donaciones"
|
||||
text: "Misskeyの運営にはドメイン、サーバー等のコストが掛かります。Misskeyは広告を掲載したりしないため、収入を皆様からの寄付に頼っています。もしご興味があれば、{}までご連絡ください。ご協力ありがとうございます。"
|
||||
common/views/widgets/photo-stream.vue:
|
||||
title: "フォトストリーム"
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -257,66 +258,66 @@ desktop/views/components/choose-file-from-drive-window.vue:
|
||||
desktop/views/components/choose-folder-from-drive-window.vue:
|
||||
cancel: "キャンセル"
|
||||
ok: "決定"
|
||||
choose-prompt: "フォルダを選択"
|
||||
choose-prompt: "Escoge una Carpeta"
|
||||
desktop/views/components/crop-window.vue:
|
||||
skip: "クロップをスキップ"
|
||||
cancel: "キャンセル"
|
||||
ok: "決定"
|
||||
cancel: "Cancelar"
|
||||
ok: "OK"
|
||||
desktop/views/components/drive-window.vue:
|
||||
used: "使用中"
|
||||
drive: "ドライブ"
|
||||
used: "usado"
|
||||
drive: "Disco"
|
||||
desktop/views/components/drive.file.vue:
|
||||
avatar: "アイコン"
|
||||
banner: "バナー"
|
||||
avatar: "Avatar"
|
||||
banner: "Banner"
|
||||
contextmenu:
|
||||
rename: "名前を変更"
|
||||
copy-url: "URLをコピー"
|
||||
download: "ダウンロード"
|
||||
else-files: "その他..."
|
||||
set-as-avatar: "アイコンに設定"
|
||||
set-as-banner: "バナーに設定"
|
||||
open-in-app: "アプリで開く"
|
||||
add-app: "アプリを追加"
|
||||
rename-file: "ファイル名の変更"
|
||||
input-new-file-name: "新しいファイル名を入力してください"
|
||||
copied: "コピー完了"
|
||||
copied-url-to-clipboard: "URLをクリップボードにコピーしました"
|
||||
rename: "Renombrar"
|
||||
copy-url: "Copia la URL"
|
||||
download: "Descargar"
|
||||
else-files: "Otros"
|
||||
set-as-avatar: "Utilizar como avatar"
|
||||
set-as-banner: "Utilizar como banner"
|
||||
open-in-app: "Abrir en la aplicación"
|
||||
add-app: "Añadir aplicación"
|
||||
rename-file: "Renombra el fichero"
|
||||
input-new-file-name: "Escribe el nombre nuevo"
|
||||
copied: "Copiado"
|
||||
copied-url-to-clipboard: "URL copiada al porta papeles"
|
||||
desktop/views/components/drive.folder.vue:
|
||||
unable-to-process: "操作を完了できません"
|
||||
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
|
||||
unhandled-error: "不明なエラー"
|
||||
unable-to-process: "La operación no se puede llevar a cabo"
|
||||
circular-reference-detected: "La carpeta de destino es una sub-carpeta de la carpeta que quieres mover."
|
||||
unhandled-error: "Error desconocido"
|
||||
contextmenu:
|
||||
move-to-this-folder: "このフォルダへ移動"
|
||||
show-in-new-window: "新しいウィンドウで表示"
|
||||
rename: "名前を変更"
|
||||
rename-folder: "フォルダ名の変更"
|
||||
input-new-folder-name: "新しいフォルダ名を入力してください"
|
||||
move-to-this-folder: "Mover a esta carpeta"
|
||||
show-in-new-window: "Abrir en una ventana nueva"
|
||||
rename: "Renombrar"
|
||||
rename-folder: "Renombrar carpeta"
|
||||
input-new-folder-name: "Escribe el nombre nuevo"
|
||||
desktop/views/components/drive.nav-folder.vue:
|
||||
drive: "ドライブ"
|
||||
drive: "Disco"
|
||||
desktop/views/components/drive.vue:
|
||||
search: "検索"
|
||||
load-more: "もっと読み込む"
|
||||
empty-draghover: "ドロップですか?いいですよ、ボクはカワイイですからね"
|
||||
empty-drive: "ドライブには何もありません。"
|
||||
empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。"
|
||||
empty-folder: "このフォルダーは空です"
|
||||
unable-to-process: "操作を完了できません"
|
||||
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
|
||||
unhandled-error: "不明なエラー"
|
||||
url-upload: "URLアップロード"
|
||||
url-of-file: "アップロードしたいファイルのURL"
|
||||
url-upload-requested: "アップロードをリクエストしました"
|
||||
may-take-time: "アップロードが完了するまで時間がかかる場合があります。"
|
||||
create-folder: "フォルダー作成"
|
||||
folder-name: "フォルダー名"
|
||||
search: "Buscar"
|
||||
load-more: "Cargar más"
|
||||
empty-draghover: "¡Saluda!"
|
||||
empty-drive: "Tu disco está vacio"
|
||||
empty-drive-description: "También puedes subir archivos seleccionándolos y con el botón derecho selecciona \"Subir fichero\" o puedes arrastrarlo hasta la ventana."
|
||||
empty-folder: "La carpeta está vacia"
|
||||
unable-to-process: "La operación no se puede llevar a cabo."
|
||||
circular-reference-detected: "La carpeta de destino es una sub-carpeta de la carpeta que quieres mover."
|
||||
unhandled-error: "Errer desconocido"
|
||||
url-upload: "Subir desde una URL"
|
||||
url-of-file: "URL del fichero que quieres subir"
|
||||
url-upload-requested: "Subida solicitada"
|
||||
may-take-time: "Subir el fichero puede tardar un tiempo."
|
||||
create-folder: "Crear una carpeta"
|
||||
folder-name: "Nombre de la carpeta"
|
||||
contextmenu:
|
||||
create-folder: "フォルダーを作成"
|
||||
upload: "ファイルをアップロード"
|
||||
url-upload: "URLからアップロード"
|
||||
create-folder: "Crear una carpeta"
|
||||
upload: "Subir fichero"
|
||||
url-upload: "Subir desde una URL"
|
||||
desktop/views/components/follow-button.vue:
|
||||
following: "フォロー中"
|
||||
follow: "フォロー"
|
||||
request-pending: "フォロー許可待ち"
|
||||
following: "Siguiendo"
|
||||
follow: "Sigue"
|
||||
request-pending: "Pendiente de aprobación"
|
||||
follow-request: "フォロー申請"
|
||||
desktop/views/components/followers-window.vue:
|
||||
followers: "{} のフォロワー"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
@ -381,55 +382,55 @@ desktop/views/components/post-form.vue:
|
||||
renote-failed: "Renoteに失敗しました"
|
||||
posting: "投稿中"
|
||||
attach-media-from-local: "PCからメディアを添付"
|
||||
attach-media-from-drive: "ドライブからメディアを添付"
|
||||
attach-cancel: "添付取り消し"
|
||||
attach-media-from-drive: "Adjunta multimedia desde tu Disco"
|
||||
attach-cancel: "Quitar el archivo adjunto"
|
||||
insert-a-kao: "v(‘ω’)v"
|
||||
create-poll: "アンケートを作成"
|
||||
text-remain: "残り{}文字"
|
||||
create-poll: "Crea una encuesta"
|
||||
text-remain: "quedan {} caracteres"
|
||||
desktop/views/components/post-form-window.vue:
|
||||
note: "新規投稿"
|
||||
reply: "返信"
|
||||
attaches: "添付: {}メディア"
|
||||
uploading-media: "{}個のメディアをアップロード中"
|
||||
note: "Nota nueva"
|
||||
reply: "Responder"
|
||||
attaches: "{} archivo(s) multimedia adjuntados"
|
||||
uploading-media: "Subiendo {} archivo(s) multimedia"
|
||||
desktop/views/components/progress-dialog.vue:
|
||||
waiting: "待機中"
|
||||
waiting: "Un momento"
|
||||
desktop/views/components/renote-form.vue:
|
||||
quote: "引用する..."
|
||||
cancel: "キャンセル"
|
||||
renote: "Renote"
|
||||
reposting: "しています..."
|
||||
success: "Renoteしました!"
|
||||
failure: "Renoteに失敗しました"
|
||||
quote: "Cita..."
|
||||
cancel: "Cancelar"
|
||||
renote: "Volver a publicar"
|
||||
reposting: "Publicando de nuevo..."
|
||||
success: "¡Publicado!"
|
||||
failure: "La publicación ha fallado"
|
||||
desktop/views/components/renote-form-window.vue:
|
||||
title: "この投稿をRenoteしますか?"
|
||||
title: "¿Seguro qué quieres volver a publicarlo?"
|
||||
desktop/views/components/settings-window.vue:
|
||||
settings: "設定"
|
||||
settings: "Configuración"
|
||||
desktop/views/components/settings.vue:
|
||||
profile: "プロフィール"
|
||||
notification: "通知"
|
||||
apps: "アプリ"
|
||||
mute: "ミュート"
|
||||
drive: "ドライブ"
|
||||
security: "セキュリティ"
|
||||
signin: "サインイン履歴"
|
||||
password: "パスワード"
|
||||
2fa: "二段階認証"
|
||||
other: "その他"
|
||||
license: "ライセンス"
|
||||
behaviour: "動作"
|
||||
fetch-on-scroll: "スクロールで自動読み込み"
|
||||
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。"
|
||||
auto-popout: "ウィンドウの自動ポップアウト"
|
||||
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。"
|
||||
advanced: "詳細設定"
|
||||
api-via-stream: "ストリームを経由したAPIリクエスト"
|
||||
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。"
|
||||
display: "デザインと表示"
|
||||
customize: "ホームをカスタマイズ"
|
||||
dark-mode: "ダークモード"
|
||||
circle-icons: "円形のアイコンを使用"
|
||||
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用"
|
||||
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する"
|
||||
profile: "Perfil"
|
||||
notification: "Notificación"
|
||||
apps: "Aplicaciones"
|
||||
mute: "Silenciar"
|
||||
drive: "Disco"
|
||||
security: "Seguridad"
|
||||
signin: "Historial de inicios de sesión"
|
||||
password: "Contraseña"
|
||||
2fa: "Autenticación de Doble-Factor"
|
||||
other: "Otros"
|
||||
license: "Licencia"
|
||||
behaviour: "Acciones"
|
||||
fetch-on-scroll: "Desplazamiento infinito"
|
||||
fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
|
||||
auto-popout: "Ventana emergente automática"
|
||||
auto-popout-desc: "Muestra una ventana emergente si es posible. Esta configuración depende del navegador."
|
||||
advanced: "Configuración avanzada"
|
||||
api-via-stream: "Solicitar API por medio de un stream"
|
||||
api-via-stream-desc: "Las peticiones de las API se realizan por conexiones WebSocket en lugar de las tradicionales (para una mejora en el rendimiento). Esta función depende del navegador."
|
||||
display: "Diseño y pantalla"
|
||||
customize: "Personaliza la página principal"
|
||||
dark-mode: "Modo Nocturno"
|
||||
circle-icons: "Usar iconos circulares"
|
||||
gradient-window-header: "Usar degradados en las cabeceras de las páginas"
|
||||
post-form-on-timeline: "Mostrar el formulario de las entradas encima de la línea de tiempo"
|
||||
show-reply-target: "リプライ先を表示する"
|
||||
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
|
||||
show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する"
|
||||
|
@ -3,20 +3,20 @@ meta:
|
||||
lang: "Français"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "Une planète du fédiverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "Une ⭐ du fédiverse."
|
||||
about: "Merci d'avoir découvert Misskey. Misskey est une <b>plateforme de micro-blogging distribuée</b> née sur Terre. Parce qu'il fait partie du Fédiverse (un univers composé de diverses plateformes de réseaux sociaux organisées), il est mutuellement connecté avec d'autres plateformes de réseaux sociaux. Désirez-vous prendre une pause, pendant un instant, loin de l'agitation de la ville et plonger dans un nouvel Internet ?"
|
||||
time:
|
||||
unknown: "inconnu"
|
||||
future: "future"
|
||||
just_now: "maintenant"
|
||||
seconds_ago: "Il y a {}seconde(s)"
|
||||
minutes_ago: "Il y a {}minute(s)"
|
||||
hours_ago: "Il y a {}heure(s)"
|
||||
days_ago: "Il y a {}jour(s)"
|
||||
weeks_ago: "Il y a{}semaines(s)"
|
||||
months_ago: "Il y a {}mois"
|
||||
years_ago: "Il y a {}an(s)"
|
||||
just_now: "à l'instant"
|
||||
seconds_ago: "Il y a {} seconde·s"
|
||||
minutes_ago: "Il y a {} minute·s"
|
||||
hours_ago: "Il y a {} heure·s"
|
||||
days_ago: "Il y a {} jour·s"
|
||||
weeks_ago: "Il y a {} semaines·s"
|
||||
months_ago: "Il y a {} mois"
|
||||
years_ago: "Il y a {} an·s"
|
||||
weekday-short:
|
||||
sunday: "D"
|
||||
monday: "L"
|
||||
@ -29,14 +29,14 @@ common:
|
||||
like: "Aime"
|
||||
love: "Adore"
|
||||
laugh: "Rire"
|
||||
hmm: "Hmm...?"
|
||||
hmm: "Hmm ... ?"
|
||||
surprise: "Wow"
|
||||
congrats: "Félicitations!"
|
||||
angry: "En Colère"
|
||||
congrats: "Félicitations !"
|
||||
angry: "En colère"
|
||||
confused: "Confus"
|
||||
pudding: "Pudding"
|
||||
note-placeholders:
|
||||
a: "Que faîtes vous à cet instant ?"
|
||||
a: "Que faîtes vous maintenant ?"
|
||||
b: "Quoi de neuf ?"
|
||||
c: "Qu'avez-vous en tête ?"
|
||||
d: "Voulez-vous exprimer quelque chose ?"
|
||||
@ -45,7 +45,7 @@ common:
|
||||
delete: "Supprimer"
|
||||
loading: "Chargement"
|
||||
ok: "OK"
|
||||
update-available: "Une nouvelle version de Misskey est disponible({newer}, version actuelle: {current}). Recharger la page pour appliquer la mise à jour."
|
||||
update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour."
|
||||
my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté."
|
||||
widgets:
|
||||
analog-clock: "Horloge analogique"
|
||||
@ -70,7 +70,7 @@ common:
|
||||
donation: "Dons"
|
||||
nav: "Navigation"
|
||||
tips: "Conseils"
|
||||
hashtags: "ハッシュタグ"
|
||||
hashtags: "Étiquettes"
|
||||
deck:
|
||||
widgets: "Widgets"
|
||||
home: "Accueil"
|
||||
@ -226,8 +226,9 @@ common/views/widgets/posts-monitor.vue:
|
||||
title: "Graph des publications"
|
||||
toggle: "表示を切り替え"
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
title: "Étiquettes"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "Info sur le serveur"
|
||||
toggle: "Afficher les vues"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "Plus"
|
||||
close: "Fermer"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Othello"
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Envoyer"
|
||||
add-widget: "Ajouter un widget"
|
||||
@ -463,7 +464,7 @@ desktop/views/components/settings.vue:
|
||||
update-checking: "Recherche de mises à jour"
|
||||
do-update: "Rechercher des mises à jour"
|
||||
update-settings: "Paramètres avancés"
|
||||
prevent-update: "アップデートを延期する(非推奨)"
|
||||
prevent-update: "Reporter les mises à jour (non recommandé)"
|
||||
prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
|
||||
no-updates: "Aucune mise à jour disponible"
|
||||
no-updates-desc: "Votre Misskey est à jour."
|
||||
@ -578,7 +579,7 @@ desktop/views/components/window.vue:
|
||||
popout: "ポップアウト"
|
||||
close: "Fermer"
|
||||
desktop/views/pages/deck/deck.tl-column.vue:
|
||||
is-media-only: "メディア投稿のみ"
|
||||
is-media-only: "Les publications médias uniquement"
|
||||
is-media-view: "メディアビュー"
|
||||
desktop/views/pages/deck/deck.note.vue:
|
||||
reposted-by: "Reposté par {}"
|
||||
@ -678,7 +679,7 @@ mobile/views/components/drive.vue:
|
||||
folder-name: "Nom du dossier"
|
||||
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
|
||||
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
|
||||
url-prompt: "アップロードしたいファイルのURL"
|
||||
url-prompt: "URL du fichier que vous souhaitez téléverser"
|
||||
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
||||
mobile/views/components/drive-file-detail.vue:
|
||||
rename: "Renommer"
|
||||
@ -693,9 +694,9 @@ mobile/views/components/drive.file-detail.vue:
|
||||
hash: "Hash (md5)"
|
||||
exif: "EXIF"
|
||||
mobile/views/components/follow-button.vue:
|
||||
following: "フォロー中"
|
||||
following: "Abonnements"
|
||||
follow: "Suivre"
|
||||
request-pending: "フォロー許可待ち"
|
||||
request-pending: "En attente d'approbation"
|
||||
follow-request: "Demande d'abonnement"
|
||||
mobile/views/components/friends-maker.vue:
|
||||
title: "Abonnez-vous aux utilisateurs"
|
||||
@ -746,7 +747,7 @@ mobile/views/components/sub-note-content.vue:
|
||||
private: "cette publication est privée"
|
||||
deleted: "cette publication a été supprimée"
|
||||
media-count: "{} médias attachés"
|
||||
poll: "アンケート"
|
||||
poll: "Sondage"
|
||||
mobile/views/components/timeline.vue:
|
||||
empty: "Pas de notes"
|
||||
load-more: "Afficher plus"
|
||||
|
@ -5,12 +5,15 @@
|
||||
import * as fs from 'fs';
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
const loadLang = lang => yaml.safeLoad(
|
||||
fs.readFileSync(`./locales/${lang}.yml`, 'utf-8'));
|
||||
export type LangKey = 'de' | 'en' | 'fr' | 'ja' | 'pl';
|
||||
export type LocaleObject = { [key: string]: any };
|
||||
|
||||
const loadLang = (lang: LangKey) => yaml.safeLoad(
|
||||
fs.readFileSync(`./locales/${lang}.yml`, 'utf-8')) as LocaleObject;
|
||||
|
||||
const native = loadLang('ja');
|
||||
|
||||
const langs = {
|
||||
const langs: { [key: string]: LocaleObject } = {
|
||||
'de': loadLang('de'),
|
||||
'en': loadLang('en'),
|
||||
'fr': loadLang('fr'),
|
||||
@ -23,4 +26,8 @@ Object.entries(langs).map(([, locale]) => {
|
||||
locale = Object.assign({}, native, locale);
|
||||
});
|
||||
|
||||
export function isAvailableLanguage(lang: string): lang is LangKey {
|
||||
return lang in langs;
|
||||
}
|
||||
|
||||
export default langs;
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "日本語"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
|
@ -52,6 +52,7 @@ common:
|
||||
ok: "わかった"
|
||||
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
|
||||
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
|
||||
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
|
||||
|
||||
widgets:
|
||||
analog-clock: "アナログ時計"
|
||||
@ -384,7 +385,7 @@ desktop/views/components/friends-maker.vue:
|
||||
close: "閉じる"
|
||||
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "日本語"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "język polski"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "Planeta Fediwersum"
|
||||
misskey: "⭐ Fediwersum"
|
||||
about-title: "⭐ Fediwersum"
|
||||
about: "Dziękujemy za znalezienie Misskey. Misskey jest <b>zdecentralizowaną platformą mikroblogową</b> powstałą na Ziemi. Ponieważ działa ona w Fediwersum (uniwersum, w którego skład wchodzi wiele sieci społecznościowych), jest ona połączona z innymi platformami społecznościowymi. Spróbujesz odpocząć od zatłoczoneo miasta i zanurzyć się w nowym Internecie?"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "Hashtagi"
|
||||
count: "Wspomniany przez {} użytkowników"
|
||||
empty: "Brak popularnych hashtagów"
|
||||
common/views/widgets/server.vue:
|
||||
title: "Informacje o serwerze"
|
||||
toggle: "Przełącz widok"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "Więcej"
|
||||
close: "Zamknij"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "Othello"
|
||||
game: "Reversi"
|
||||
desktop/views/components/home.vue:
|
||||
done: "Wyślij"
|
||||
add-widget: "Dodaj widżet:"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "Português"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "Русский язык"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
|
@ -3,7 +3,7 @@ meta:
|
||||
lang: "中文(简体)"
|
||||
divider: ""
|
||||
common:
|
||||
misskey: "A planet of fediverse"
|
||||
misskey: "A ⭐ of fediverse"
|
||||
about-title: "A ⭐ of fediverse."
|
||||
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
|
||||
time:
|
||||
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
|
||||
common/views/widgets/hashtags.vue:
|
||||
title: "ハッシュタグ"
|
||||
count: "{}人が投稿"
|
||||
empty: "トレンドなし"
|
||||
common/views/widgets/server.vue:
|
||||
title: "サーバー情報"
|
||||
toggle: "表示を切り替え"
|
||||
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
|
||||
refresh: "もっと見る"
|
||||
close: "閉じる"
|
||||
desktop/views/components/game-window.vue:
|
||||
game: "オセロ"
|
||||
game: "リバーシ"
|
||||
desktop/views/components/home.vue:
|
||||
done: "完了"
|
||||
add-widget: "ウィジェットを追加:"
|
||||
|
133
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "3.0.1",
|
||||
"clientVersion": "1.0.6517",
|
||||
"version": "4.3.0",
|
||||
"clientVersion": "1.0.6630",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -23,71 +23,12 @@
|
||||
"format": "gulp format"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome": "1.0.1",
|
||||
"@fortawesome/fontawesome-free-brands": "5.0.2",
|
||||
"@fortawesome/fontawesome-free-regular": "5.0.2",
|
||||
"@fortawesome/fontawesome-free-solid": "5.0.2",
|
||||
"@fortawesome/fontawesome": "1.1.8",
|
||||
"@fortawesome/fontawesome-free-brands": "5.0.13",
|
||||
"@fortawesome/fontawesome-free-regular": "5.0.13",
|
||||
"@fortawesome/fontawesome-free-solid": "5.0.13",
|
||||
"@koa/cors": "2.2.1",
|
||||
"@prezzemolo/rap": "0.1.2",
|
||||
"autwh": "0.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"cafy": "8.0.0",
|
||||
"chalk": "2.4.1",
|
||||
"crc-32": "1.2.0",
|
||||
"debug": "3.1.0",
|
||||
"deepcopy": "0.6.3",
|
||||
"diskusage": "0.2.4",
|
||||
"elasticsearch": "15.0.0",
|
||||
"emojilib": "2.2.12",
|
||||
"escape-regexp": "0.0.1",
|
||||
"file-type": "8.0.0",
|
||||
"gm": "1.23.1",
|
||||
"http-signature": "1.2.0",
|
||||
"is-root": "2.0.0",
|
||||
"is-url": "1.2.4",
|
||||
"js-yaml": "3.11.0",
|
||||
"jsdom": "11.11.0",
|
||||
"koa": "2.5.1",
|
||||
"koa-bodyparser": "4.2.1",
|
||||
"koa-compress": "3.0.0",
|
||||
"koa-favicon": "2.0.1",
|
||||
"koa-json-body": "5.3.0",
|
||||
"koa-logger": "3.2.0",
|
||||
"koa-mount": "3.0.0",
|
||||
"koa-multer": "1.0.2",
|
||||
"koa-router": "7.4.0",
|
||||
"koa-send": "4.1.3",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.1.4",
|
||||
"kue": "0.11.6",
|
||||
"mongodb": "3.0.10",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nopt": "4.0.1",
|
||||
"os-utils": "0.0.14",
|
||||
"parse5": "5.0.0",
|
||||
"prominence": "0.2.0",
|
||||
"promise-sequential": "1.1.1",
|
||||
"punycode": "2.1.1",
|
||||
"qrcode": "1.2.0",
|
||||
"ratelimiter": "3.0.3",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "3.2.2",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.87.0",
|
||||
"request-promise-native": "1.0.5",
|
||||
"rndstr": "1.0.0",
|
||||
"speakeasy": "2.0.0",
|
||||
"summaly": "2.0.6",
|
||||
"tcp-port-used": "0.1.2",
|
||||
"tmp": "0.0.33",
|
||||
"uuid": "3.2.1",
|
||||
"web-push": "3.3.1",
|
||||
"webfinger.js": "2.6.6",
|
||||
"websocket": "1.0.26",
|
||||
"ws": "5.2.0",
|
||||
"xev": "2.0.1",
|
||||
|
||||
"@prezzemolo/zip": "0.0.3",
|
||||
"@types/bcryptjs": "2.4.1",
|
||||
"@types/debug": "0.0.30",
|
||||
@ -143,17 +84,30 @@
|
||||
"@types/ws": "5.1.1",
|
||||
"animejs": "2.2.0",
|
||||
"autosize": "4.0.2",
|
||||
"autwh": "0.1.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bootstrap-vue": "2.0.0-rc.6",
|
||||
"cafy": "8.0.0",
|
||||
"chalk": "2.4.1",
|
||||
"crc-32": "1.2.0",
|
||||
"css-loader": "0.28.11",
|
||||
"debug": "3.1.0",
|
||||
"deep-equal": "1.0.1",
|
||||
"deepcopy": "0.6.3",
|
||||
"diskusage": "0.2.4",
|
||||
"dompurify": "1.0.4",
|
||||
"elasticsearch": "15.0.0",
|
||||
"element-ui": "2.3.9",
|
||||
"emojilib": "2.2.12",
|
||||
"escape-regexp": "0.0.1",
|
||||
"eslint": "4.19.1",
|
||||
"eslint-plugin-vue": "4.5.0",
|
||||
"eventemitter3": "3.1.0",
|
||||
"exif-js": "2.3.0",
|
||||
"file-loader": "1.1.11",
|
||||
"file-type": "8.0.0",
|
||||
"fuckadblock": "3.2.1",
|
||||
"gm": "1.23.1",
|
||||
"gulp": "3.9.1",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
"gulp-htmlmin": "4.0.0",
|
||||
@ -171,32 +125,71 @@
|
||||
"hard-source-webpack-plugin": "0.6.10",
|
||||
"highlight.js": "9.12.0",
|
||||
"html-minifier": "3.5.16",
|
||||
"http-signature": "1.2.0",
|
||||
"inquirer": "5.2.0",
|
||||
"is-root": "2.0.0",
|
||||
"is-url": "1.2.4",
|
||||
"js-yaml": "3.11.0",
|
||||
"jsdom": "11.11.0",
|
||||
"koa": "2.5.1",
|
||||
"koa-bodyparser": "4.2.1",
|
||||
"koa-compress": "3.0.0",
|
||||
"koa-favicon": "2.0.1",
|
||||
"koa-json-body": "5.3.0",
|
||||
"koa-logger": "3.2.0",
|
||||
"koa-mount": "3.0.0",
|
||||
"koa-multer": "1.0.2",
|
||||
"koa-router": "7.4.0",
|
||||
"koa-send": "4.1.3",
|
||||
"koa-slow": "2.1.0",
|
||||
"koa-views": "6.1.4",
|
||||
"kue": "0.11.6",
|
||||
"license-checker": "20.0.0",
|
||||
"loader-utils": "1.1.0",
|
||||
"mecab-async": "0.1.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"mocha": "5.2.0",
|
||||
"moji": "0.5.1",
|
||||
"mongodb": "3.0.10",
|
||||
"monk": "6.0.6",
|
||||
"ms": "2.1.1",
|
||||
"nan": "2.10.0",
|
||||
"node-sass": "4.9.0",
|
||||
"node-sass-json-importer": "3.2.0",
|
||||
"nopt": "4.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"object-assign-deep": "0.4.0",
|
||||
"on-build-webpack": "0.1.0",
|
||||
"os-utils": "0.0.14",
|
||||
"parse5": "5.0.0",
|
||||
"progress-bar-webpack-plugin": "1.11.0",
|
||||
"prominence": "0.2.0",
|
||||
"promise-sequential": "1.1.1",
|
||||
"pug": "2.0.3",
|
||||
"punycode": "2.1.1",
|
||||
"qrcode": "1.2.0",
|
||||
"ratelimiter": "3.0.3",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "3.2.2",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.87.0",
|
||||
"request-promise-native": "1.0.5",
|
||||
"rimraf": "2.6.2",
|
||||
"rndstr": "1.0.0",
|
||||
"s-age": "1.1.2",
|
||||
"sass-loader": "7.0.1",
|
||||
"seedrandom": "2.4.3",
|
||||
"single-line-log": "1.1.2",
|
||||
"speakeasy": "2.0.0",
|
||||
"style-loader": "0.21.0",
|
||||
"stylus": "0.54.5",
|
||||
"stylus-loader": "3.0.2",
|
||||
"summaly": "2.0.6",
|
||||
"swagger-jsdoc": "1.9.7",
|
||||
"syuilo-password-strength": "0.0.1",
|
||||
"tcp-port-used": "0.1.2",
|
||||
"textarea-caret": "3.1.0",
|
||||
"tmp": "0.0.33",
|
||||
"ts-loader": "4.3.0",
|
||||
"ts-node": "6.0.4",
|
||||
"tslint": "5.10.0",
|
||||
@ -204,6 +197,7 @@
|
||||
"typescript-eslint-parser": "15.0.0",
|
||||
"uglify-es": "3.3.9",
|
||||
"url-loader": "1.0.1",
|
||||
"uuid": "3.2.1",
|
||||
"v-animate-css": "0.0.2",
|
||||
"vue": "2.5.16",
|
||||
"vue-cropperjs": "2.2.0",
|
||||
@ -215,7 +209,14 @@
|
||||
"vuedraggable": "2.16.0",
|
||||
"vuex": "3.0.1",
|
||||
"vuex-persistedstate": "^2.5.4",
|
||||
"web-push": "3.3.1",
|
||||
"webfinger.js": "2.6.6",
|
||||
"webpack": "4.9.1",
|
||||
"webpack-cli": "2.1.4"
|
||||
"webpack-cli": "2.1.4",
|
||||
"websocket": "1.0.26",
|
||||
"ws": "5.2.0",
|
||||
"xev": "2.0.1",
|
||||
"@types/file-type": "5.2.1",
|
||||
"@types/jsdom": "11.0.5"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
export default acct => {
|
||||
export default (acct: string) => {
|
||||
const splitted = acct.split('@', 2);
|
||||
return { username: splitted[0], host: splitted[1] || null };
|
||||
};
|
||||
|
@ -1,3 +1,5 @@
|
||||
export default user => {
|
||||
import { IUser } from '../models/user';
|
||||
|
||||
export default (user: IUser) => {
|
||||
return user.host === null ? user.username : `${user.username}@${user.host}`;
|
||||
};
|
||||
|
@ -3,18 +3,18 @@
|
||||
*/
|
||||
|
||||
import * as fontawesome from '@fortawesome/fontawesome';
|
||||
import * as regular from '@fortawesome/fontawesome-free-regular';
|
||||
import * as solid from '@fortawesome/fontawesome-free-solid';
|
||||
import * as brands from '@fortawesome/fontawesome-free-brands';
|
||||
import regular from '@fortawesome/fontawesome-free-regular';
|
||||
import solid from '@fortawesome/fontawesome-free-solid';
|
||||
import brands from '@fortawesome/fontawesome-free-brands';
|
||||
|
||||
fontawesome.library.add(regular, solid, brands);
|
||||
|
||||
export const pattern = /%fa:(.+?)%/g;
|
||||
|
||||
export const replacement = (match, key) => {
|
||||
export const replacement = (match: string, key: string) => {
|
||||
const args = key.split(' ');
|
||||
let prefix = 'fas';
|
||||
const classes = [];
|
||||
const classes: string[] = [];
|
||||
let transform = '';
|
||||
let name;
|
||||
|
||||
@ -34,12 +34,12 @@ export const replacement = (match, key) => {
|
||||
}
|
||||
});
|
||||
|
||||
const icon = fontawesome.icon({ prefix, iconName: name }, {
|
||||
classes: classes
|
||||
const icon = fontawesome.icon({ prefix, iconName: name } as fontawesome.IconLookup, {
|
||||
classes: classes,
|
||||
transform: fontawesome.parse.transform(transform)
|
||||
});
|
||||
|
||||
if (icon) {
|
||||
icon.transform = fontawesome.parse.transform(transform);
|
||||
return `<i data-fa class="${name}">${icon.html[0]}</i>`;
|
||||
} else {
|
||||
console.warn(`'${name}' not found in fa`);
|
||||
|
@ -2,7 +2,7 @@
|
||||
* Replace i18n texts
|
||||
*/
|
||||
|
||||
import locale from '../../locales';
|
||||
import locale, { isAvailableLanguage, LocaleObject } from '../../locales';
|
||||
|
||||
export default class Replacer {
|
||||
private lang: string;
|
||||
@ -16,19 +16,19 @@ export default class Replacer {
|
||||
this.replacement = this.replacement.bind(this);
|
||||
}
|
||||
|
||||
private get(path: string, key: string) {
|
||||
const texts = locale[this.lang];
|
||||
|
||||
if (texts == null) {
|
||||
private get(path: string, key: string): string {
|
||||
if (!isAvailableLanguage(this.lang)) {
|
||||
console.warn(`lang '${this.lang}' is not supported`);
|
||||
return key; // Fallback
|
||||
}
|
||||
|
||||
const texts = locale[this.lang];
|
||||
|
||||
let text = texts;
|
||||
|
||||
if (path) {
|
||||
if (text.hasOwnProperty(path)) {
|
||||
text = text[path];
|
||||
text = text[path] as LocaleObject;
|
||||
} else {
|
||||
console.warn(`path '${path}' not found in '${this.lang}'`);
|
||||
return key; // Fallback
|
||||
@ -38,7 +38,7 @@ export default class Replacer {
|
||||
// Check the key existance
|
||||
const error = key.split('.').some(k => {
|
||||
if (text.hasOwnProperty(k)) {
|
||||
text = text[k];
|
||||
text = (text as LocaleObject)[k];
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
@ -48,12 +48,15 @@ export default class Replacer {
|
||||
if (error) {
|
||||
console.warn(`key '${key}' not found in '${path}' of '${this.lang}'`);
|
||||
return key; // Fallback
|
||||
} else if (typeof text !== 'string') {
|
||||
console.warn(`key '${key}' is not string in '${path}' of '${this.lang}'`);
|
||||
return key; // Fallback
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public replacement(match, key) {
|
||||
public replacement(match: string, key: string) {
|
||||
let path = null;
|
||||
|
||||
if (key.indexOf('|') != -1) {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import { Query } from 'cafy';
|
||||
|
||||
export const isAnId = x => mongo.ObjectID.isValid(x);
|
||||
export const isNotAnId = x => !isAnId(x);
|
||||
export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
|
||||
export const isNotAnId = (x: any) => !isAnId(x);
|
||||
|
||||
/**
|
||||
* ID
|
||||
|
@ -55,7 +55,7 @@ export default function(type, data): Notification {
|
||||
icon: data.user.avatarUrl + '?thumbnail&size=64'
|
||||
};
|
||||
|
||||
case 'othello_invited':
|
||||
case 'reversi_invited':
|
||||
return {
|
||||
title: '対局への招待があります',
|
||||
body: `${getUserName(data.parent)}さんから`,
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Stream from './stream';
|
||||
import MiOS from '../../../mios';
|
||||
|
||||
export class OthelloGameStream extends Stream {
|
||||
export class ReversiGameStream extends Stream {
|
||||
constructor(os: MiOS, me, game) {
|
||||
super(os, 'othello-game', {
|
||||
super(os, 'reversi-game', {
|
||||
i: me ? me.token : null,
|
||||
game: game.id
|
||||
});
|
@ -2,15 +2,15 @@ import StreamManager from './stream-manager';
|
||||
import Stream from './stream';
|
||||
import MiOS from '../../../mios';
|
||||
|
||||
export class OthelloStream extends Stream {
|
||||
export class ReversiStream extends Stream {
|
||||
constructor(os: MiOS, me) {
|
||||
super(os, 'othello', {
|
||||
super(os, 'reversi', {
|
||||
i: me.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class OthelloStreamManager extends StreamManager<OthelloStream> {
|
||||
export class ReversiStreamManager extends StreamManager<ReversiStream> {
|
||||
private me;
|
||||
private os: MiOS;
|
||||
|
||||
@ -23,7 +23,7 @@ export class OthelloStreamManager extends StreamManager<OthelloStream> {
|
||||
|
||||
public getConnection() {
|
||||
if (this.connection == null) {
|
||||
this.connection = new OthelloStream(this.os, this.me);
|
||||
this.connection = new ReversiStream(this.os, this.me);
|
||||
}
|
||||
|
||||
return this.connection;
|
@ -27,7 +27,7 @@ import urlPreview from './url-preview.vue';
|
||||
import twitterSetting from './twitter-setting.vue';
|
||||
import fileTypeIcon from './file-type-icon.vue';
|
||||
import Switch from './switch.vue';
|
||||
import Othello from './othello.vue';
|
||||
import Reversi from './reversi.vue';
|
||||
import welcomeTimeline from './welcome-timeline.vue';
|
||||
import uiInput from './ui/input.vue';
|
||||
import uiButton from './ui/button.vue';
|
||||
@ -65,7 +65,7 @@ Vue.component('mk-url-preview', urlPreview);
|
||||
Vue.component('mk-twitter-setting', twitterSetting);
|
||||
Vue.component('mk-file-type-icon', fileTypeIcon);
|
||||
Vue.component('mk-switch', Switch);
|
||||
Vue.component('mk-othello', Othello);
|
||||
Vue.component('mk-reversi', Reversi);
|
||||
Vue.component('mk-welcome-timeline', welcomeTimeline);
|
||||
Vue.component('ui-input', uiInput);
|
||||
Vue.component('ui-button', uiButton);
|
||||
|
@ -8,7 +8,10 @@
|
||||
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
|
||||
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
|
||||
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
|
||||
<img v-if="reaction == 'pudding'" src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
||||
<template v-if="reaction == 'pudding'">
|
||||
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
|
||||
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">
|
||||
</template>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
|
@ -43,7 +43,7 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as CRC32 from 'crc-32';
|
||||
import Othello, { Color } from '../../../../../othello/core';
|
||||
import Reversi, { Color } from '../../../../../reversi/core';
|
||||
import { url } from '../../../config';
|
||||
|
||||
export default Vue.extend({
|
||||
@ -52,7 +52,7 @@ export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
game: null,
|
||||
o: null as Othello,
|
||||
o: null as Reversi,
|
||||
logs: [],
|
||||
logPos: 0,
|
||||
pollingClock: null
|
||||
@ -98,7 +98,7 @@ export default Vue.extend({
|
||||
watch: {
|
||||
logPos(v) {
|
||||
if (!this.game.isEnded) return;
|
||||
this.o = new Othello(this.game.settings.map, {
|
||||
this.o = new Reversi(this.game.settings.map, {
|
||||
isLlotheo: this.game.settings.isLlotheo,
|
||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||
loopedBoard: this.game.settings.loopedBoard
|
||||
@ -115,7 +115,7 @@ export default Vue.extend({
|
||||
created() {
|
||||
this.game = this.initGame;
|
||||
|
||||
this.o = new Othello(this.game.settings.map, {
|
||||
this.o = new Reversi(this.game.settings.map, {
|
||||
isLlotheo: this.game.settings.isLlotheo,
|
||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||
loopedBoard: this.game.settings.loopedBoard
|
||||
@ -163,7 +163,7 @@ export default Vue.extend({
|
||||
|
||||
// サウンドを再生する
|
||||
if (this.$store.state.device.enableSounds) {
|
||||
const sound = new Audio(`${url}/assets/othello-put-me.mp3`);
|
||||
const sound = new Audio(`${url}/assets/reversi-put-me.mp3`);
|
||||
sound.volume = this.$store.state.device.soundVolume;
|
||||
sound.play();
|
||||
}
|
||||
@ -187,7 +187,7 @@ export default Vue.extend({
|
||||
|
||||
// サウンドを再生する
|
||||
if (this.$store.state.device.enableSounds && x.color != this.myColor) {
|
||||
const sound = new Audio(`${url}/assets/othello-put-you.mp3`);
|
||||
const sound = new Audio(`${url}/assets/reversi-put-you.mp3`);
|
||||
sound.volume = this.$store.state.device.soundVolume;
|
||||
sound.play();
|
||||
}
|
||||
@ -213,7 +213,7 @@ export default Vue.extend({
|
||||
onRescue(game) {
|
||||
this.game = game;
|
||||
|
||||
this.o = new Othello(this.game.settings.map, {
|
||||
this.o = new Reversi(this.game.settings.map, {
|
||||
isLlotheo: this.game.settings.isLlotheo,
|
||||
canPutEverywhere: this.game.settings.canPutEverywhere,
|
||||
loopedBoard: this.game.settings.loopedBoard
|
@ -7,9 +7,9 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XGame from './othello.game.vue';
|
||||
import XRoom from './othello.room.vue';
|
||||
import { OthelloGameStream } from '../../scripts/streaming/othello-game';
|
||||
import XGame from './reversi.game.vue';
|
||||
import XRoom from './reversi.room.vue';
|
||||
import { ReversiGameStream } from '../../scripts/streaming/reversi-game';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -25,7 +25,7 @@ export default Vue.extend({
|
||||
},
|
||||
created() {
|
||||
this.g = this.game;
|
||||
this.connection = new OthelloGameStream((this as any).os, this.$store.state.i, this.game);
|
||||
this.connection = new ReversiGameStream((this as any).os, this.$store.state.i, this.game);
|
||||
this.connection.on('started', this.onStarted);
|
||||
},
|
||||
beforeDestroy() {
|
@ -94,7 +94,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import * as maps from '../../../../../othello/maps';
|
||||
import * as maps from '../../../../../reversi/maps';
|
||||
|
||||
export default Vue.extend({
|
||||
props: ['game', 'connection'],
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="mk-othello">
|
||||
<div class="mk-reversi">
|
||||
<div v-if="game">
|
||||
<x-gameroom :game="game"/>
|
||||
</div>
|
||||
@ -11,14 +11,14 @@
|
||||
</div>
|
||||
<div class="index" v-else>
|
||||
<h1>Misskey %fa:circle%thell%fa:circle R%</h1>
|
||||
<p>他のMisskeyユーザーとオセロで対戦しよう</p>
|
||||
<p>他のMisskeyユーザーとリバーシで対戦しよう</p>
|
||||
<div class="play">
|
||||
<el-button round>フリーマッチ(準備中)</el-button>
|
||||
<el-button type="primary" round @click="match">指名</el-button>
|
||||
<details>
|
||||
<summary>遊び方</summary>
|
||||
<div>
|
||||
<p>オセロは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
|
||||
<p>リバーシは、相手と交互に石をボードに置いてゆき、相手の石を挟んでひっくり返しながら、最終的に残った石が多い方が勝ちというボードゲームです。</p>
|
||||
<dl>
|
||||
<dt><b>フリーマッチ</b></dt>
|
||||
<dd>ランダムなユーザーと対戦するモードです。</dd>
|
||||
@ -39,7 +39,7 @@
|
||||
</section>
|
||||
<section v-if="myGames.length > 0">
|
||||
<h2>自分の対局</h2>
|
||||
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
|
||||
<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
|
||||
<mk-avatar class="avatar" :user="g.user1"/>
|
||||
<mk-avatar class="avatar" :user="g.user2"/>
|
||||
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
||||
@ -48,7 +48,7 @@
|
||||
</section>
|
||||
<section v-if="games.length > 0">
|
||||
<h2>みんなの対局</h2>
|
||||
<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/othello/${g.id}`">
|
||||
<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
|
||||
<mk-avatar class="avatar" :user="g.user1"/>
|
||||
<mk-avatar class="avatar" :user="g.user2"/>
|
||||
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
|
||||
@ -61,7 +61,7 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import XGameroom from './othello.gameroom.vue';
|
||||
import XGameroom from './reversi.gameroom.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
components: {
|
||||
@ -93,24 +93,24 @@ export default Vue.extend({
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.connection = (this as any).os.streams.othelloStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.othelloStream.use();
|
||||
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
||||
this.connectionId = (this as any).os.streams.reversiStream.use();
|
||||
|
||||
this.connection.on('matched', this.onMatched);
|
||||
this.connection.on('invited', this.onInvited);
|
||||
|
||||
(this as any).api('othello/games', {
|
||||
(this as any).api('reversi/games', {
|
||||
my: true
|
||||
}).then(games => {
|
||||
this.myGames = games;
|
||||
});
|
||||
|
||||
(this as any).api('othello/games').then(games => {
|
||||
(this as any).api('reversi/games').then(games => {
|
||||
this.games = games;
|
||||
this.gamesFetching = false;
|
||||
});
|
||||
|
||||
(this as any).api('othello/invitations').then(invitations => {
|
||||
(this as any).api('reversi/invitations').then(invitations => {
|
||||
this.invitations = this.invitations.concat(invitations);
|
||||
});
|
||||
|
||||
@ -126,13 +126,13 @@ export default Vue.extend({
|
||||
beforeDestroy() {
|
||||
this.connection.off('matched', this.onMatched);
|
||||
this.connection.off('invited', this.onInvited);
|
||||
(this as any).os.streams.othelloStream.dispose(this.connectionId);
|
||||
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
||||
|
||||
clearInterval(this.pingClock);
|
||||
},
|
||||
methods: {
|
||||
go(game) {
|
||||
(this as any).api('othello/games/show', {
|
||||
(this as any).api('reversi/games/show', {
|
||||
gameId: game.id
|
||||
}).then(game => {
|
||||
this.matching = null;
|
||||
@ -146,7 +146,7 @@ export default Vue.extend({
|
||||
(this as any).api('users/show', {
|
||||
username
|
||||
}).then(user => {
|
||||
(this as any).api('othello/match', {
|
||||
(this as any).api('reversi/match', {
|
||||
userId: user.id
|
||||
}).then(res => {
|
||||
if (res == null) {
|
||||
@ -160,10 +160,10 @@ export default Vue.extend({
|
||||
},
|
||||
cancel() {
|
||||
this.matching = null;
|
||||
(this as any).api('othello/match/cancel');
|
||||
(this as any).api('reversi/match/cancel');
|
||||
},
|
||||
accept(invitation) {
|
||||
(this as any).api('othello/match', {
|
||||
(this as any).api('reversi/match', {
|
||||
userId: invitation.parent.id
|
||||
}).then(game => {
|
||||
if (game) {
|
||||
@ -186,7 +186,7 @@ export default Vue.extend({
|
||||
<style lang="stylus" scoped>
|
||||
@import '~const.styl'
|
||||
|
||||
.mk-othello
|
||||
.mk-reversi
|
||||
color #677f84
|
||||
background #fff
|
||||
|
@ -289,6 +289,10 @@ root(isDark, fill)
|
||||
> *
|
||||
display block
|
||||
min-width 16px
|
||||
max-width 150px
|
||||
overflow hidden
|
||||
white-space nowrap
|
||||
text-overflow ellipsis
|
||||
|
||||
> .prefix
|
||||
left 0
|
||||
|
@ -21,7 +21,7 @@ export default (os: OS) => opts => {
|
||||
res(file);
|
||||
};
|
||||
|
||||
window.open(url + '/selectdrive',
|
||||
window.open(url + `/selectdrive?multiple=${o.multiple}`,
|
||||
'choose_drive_window',
|
||||
'height=500, width=800');
|
||||
}
|
||||
|
@ -34,7 +34,8 @@ import MkMessagingRoom from './views/pages/messaging-room.vue';
|
||||
import MkNote from './views/pages/note.vue';
|
||||
import MkSearch from './views/pages/search.vue';
|
||||
import MkTag from './views/pages/tag.vue';
|
||||
import MkOthello from './views/pages/othello.vue';
|
||||
import MkReversi from './views/pages/reversi.vue';
|
||||
import MkShare from './views/pages/share.vue';
|
||||
|
||||
/**
|
||||
* init
|
||||
@ -62,8 +63,9 @@ init(async (launch) => {
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ path: '/tags/:tag', component: MkTag },
|
||||
{ path: '/othello', component: MkOthello },
|
||||
{ path: '/othello/:game', component: MkOthello },
|
||||
{ path: '/share', component: MkShare },
|
||||
{ path: '/reversi', component: MkReversi },
|
||||
{ path: '/reversi/:game', component: MkReversi },
|
||||
{ path: '/@:user', component: MkUser },
|
||||
{ path: '/notes/:note', component: MkNote }
|
||||
]
|
||||
@ -164,8 +166,8 @@ function registerNotifications(stream: HomeStreamManager) {
|
||||
setTimeout(n.close.bind(n), 7000);
|
||||
});
|
||||
|
||||
connection.on('othello_invited', matching => {
|
||||
const _n = composeNotification('othello_invited', matching);
|
||||
connection.on('reversi_invited', matching => {
|
||||
const _n = composeNotification('reversi_invited', matching);
|
||||
const n = new Notification(_n.title, {
|
||||
body: _n.body,
|
||||
icon: _n.icon
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
|
||||
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span>
|
||||
<mk-othello :class="$style.content" @gamed="g => game = g"/>
|
||||
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
|
||||
</mk-window>
|
||||
</template>
|
||||
|
||||
@ -18,8 +18,8 @@ export default Vue.extend({
|
||||
computed: {
|
||||
popout(): string {
|
||||
return this.game
|
||||
? `${url}/othello/${this.game.id}`
|
||||
: `${url}/othello`;
|
||||
? `${url}/reversi/${this.game.id}`
|
||||
: `${url}/reversi`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -84,7 +84,7 @@ const defaultDesktopHomeWidgets = {
|
||||
'calendar',
|
||||
'activity',
|
||||
'rss',
|
||||
'trends',
|
||||
'hashtags',
|
||||
'photo-stream',
|
||||
'version'
|
||||
],
|
||||
|
@ -58,7 +58,25 @@ export default Vue.extend({
|
||||
MkVisibilityChooser
|
||||
},
|
||||
|
||||
props: ['reply', 'renote'],
|
||||
props: {
|
||||
reply: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
renote: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
initialText: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
instant: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@ -118,6 +136,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.initialText) {
|
||||
this.text = this.initialText;
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.user.host != null) {
|
||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
||||
}
|
||||
@ -141,17 +163,19 @@ export default Vue.extend({
|
||||
|
||||
this.$nextTick(() => {
|
||||
// 書きかけの投稿を復元
|
||||
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId];
|
||||
if (draft) {
|
||||
this.text = draft.data.text;
|
||||
this.files = draft.data.files;
|
||||
if (draft.data.poll) {
|
||||
this.poll = true;
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.poll as any).set(draft.data.poll);
|
||||
});
|
||||
if (!this.instant) {
|
||||
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId];
|
||||
if (draft) {
|
||||
this.text = draft.data.text;
|
||||
this.files = draft.data.files;
|
||||
if (draft.data.poll) {
|
||||
this.poll = true;
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.poll as any).set(draft.data.poll);
|
||||
});
|
||||
}
|
||||
this.$emit('change-attached-media', this.files);
|
||||
}
|
||||
this.$emit('change-attached-media', this.files);
|
||||
}
|
||||
|
||||
this.$nextTick(() => this.watch());
|
||||
@ -349,6 +373,8 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
saveDraft() {
|
||||
if (this.instant) return;
|
||||
|
||||
const data = JSON.parse(localStorage.getItem('drafts') || '{}');
|
||||
|
||||
data[this.draftId] = {
|
||||
|
@ -45,6 +45,7 @@
|
||||
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/>
|
||||
<mk-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons" text="%i18n:@circle-icons%"/>
|
||||
<mk-switch v-model="$store.state.settings.gradientWindowHeader" @change="onChangeGradientWindowHeader" text="%i18n:@gradient-window-header%"/>
|
||||
<mk-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi" text="%i18n:common.i-like-sushi%"/>
|
||||
</div>
|
||||
<mk-switch v-model="$store.state.settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/>
|
||||
<mk-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget" text="%i18n:@show-reply-target%"/>
|
||||
@ -362,6 +363,12 @@ export default Vue.extend({
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeILikeSushi(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'iLikeSushi',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
onChangeGradientWindowHeader(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'gradientWindowHeader',
|
||||
|
@ -56,23 +56,23 @@ export default Vue.extend({
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
|
||||
this.connection.on('othello_invited', this.onOthelloInvited);
|
||||
this.connection.on('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.on('reversi_invited', this.onReversiInvited);
|
||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.off('othello_invited', this.onOthelloInvited);
|
||||
this.connection.off('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.off('reversi_invited', this.onReversiInvited);
|
||||
this.connection.off('reversi_no_invites', this.onReversiNoInvites);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOthelloInvited() {
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitations = true;
|
||||
},
|
||||
|
||||
onOthelloNoInvites() {
|
||||
onReversiNoInvites() {
|
||||
this.hasGameInvitations = false;
|
||||
},
|
||||
|
||||
|
@ -116,6 +116,8 @@ export default Vue.extend({
|
||||
data: {}
|
||||
}
|
||||
});
|
||||
|
||||
this.widgetAdderSelected = null;
|
||||
},
|
||||
|
||||
removeWidget(widget) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<component :is="ui ? 'mk-ui' : 'div'">
|
||||
<mk-othello v-if="!fetching" :init-game="game" @gamed="onGamed"/>
|
||||
<mk-reversi v-if="!fetching" :init-game="game" @gamed="onGamed"/>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@ -33,7 +33,7 @@ export default Vue.extend({
|
||||
Progress.start();
|
||||
this.fetching = true;
|
||||
|
||||
(this as any).api('othello/games/show', {
|
||||
(this as any).api('reversi/games/show', {
|
||||
gameId: this.$route.params.game
|
||||
}).then(game => {
|
||||
this.game = game;
|
||||
@ -43,7 +43,7 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
onGamed(game) {
|
||||
history.pushState(null, null, '/othello/' + game.id);
|
||||
history.pushState(null, null, '/reversi/' + game.id);
|
||||
}
|
||||
}
|
||||
});
|
58
src/client/app/desktop/views/pages/share.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div class="pptjhabgjtt7kwskbfv4y3uml6fpuhmr">
|
||||
<h1>Misskeyで共有</h1>
|
||||
<div>
|
||||
<mk-signin v-if="!$store.getters.isSignedIn"/>
|
||||
<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/>
|
||||
<p v-if="posted" class="posted">%fa:check%</p>
|
||||
</div>
|
||||
<button v-if="posted" class="ui button" @click="close">閉じる</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
posted: false,
|
||||
text: new URLSearchParams(location.search).get('text')
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.pptjhabgjtt7kwskbfv4y3uml6fpuhmr
|
||||
padding 16px
|
||||
|
||||
> h1
|
||||
margin 0 0 8px 0
|
||||
color #555
|
||||
font-size 20px
|
||||
text-align center
|
||||
|
||||
> div
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
background #fff
|
||||
border solid 1px rgba(#000, 0.1)
|
||||
border-radius 6px
|
||||
overflow hidden
|
||||
|
||||
> .posted
|
||||
display block
|
||||
margin 0
|
||||
padding 64px
|
||||
text-align center
|
||||
|
||||
> button
|
||||
display block
|
||||
margin 16px auto
|
||||
</style>
|
@ -13,7 +13,7 @@
|
||||
<h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" alt="Misskey"></h1>
|
||||
<p class="powerd-by" v-if="name">powerd by <b>Misskey</b></p>
|
||||
<p class="desc" v-html="description || '%i18n:common.about%'"></p>
|
||||
<a ref="signup" @click="signup">%i18n:@signup%</a>
|
||||
<a ref="signup" @click="signup">📦 %i18n:@signup%</a>
|
||||
</div>
|
||||
<div class="login">
|
||||
<mk-signin/>
|
||||
|
@ -11,7 +11,7 @@ import { DriveStreamManager } from './common/scripts/streaming/drive';
|
||||
import { ServerStatsStreamManager } from './common/scripts/streaming/server-stats';
|
||||
import { NotesStatsStreamManager } from './common/scripts/streaming/notes-stats';
|
||||
import { MessagingIndexStreamManager } from './common/scripts/streaming/messaging-index';
|
||||
import { OthelloStreamManager } from './common/scripts/streaming/othello';
|
||||
import { ReversiStreamManager } from './common/scripts/streaming/reversi';
|
||||
|
||||
import Err from './common/views/components/connect-failed.vue';
|
||||
import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
|
||||
@ -108,7 +108,7 @@ export default class MiOS extends EventEmitter {
|
||||
serverStatsStream: ServerStatsStreamManager;
|
||||
notesStatsStream: NotesStatsStreamManager;
|
||||
messagingIndexStream: MessagingIndexStreamManager;
|
||||
othelloStream: OthelloStreamManager;
|
||||
reversiStream: ReversiStreamManager;
|
||||
} = {
|
||||
localTimelineStream: null,
|
||||
globalTimelineStream: null,
|
||||
@ -116,7 +116,7 @@ export default class MiOS extends EventEmitter {
|
||||
serverStatsStream: null,
|
||||
notesStatsStream: null,
|
||||
messagingIndexStream: null,
|
||||
othelloStream: null
|
||||
reversiStream: null
|
||||
};
|
||||
|
||||
/**
|
||||
@ -233,7 +233,7 @@ export default class MiOS extends EventEmitter {
|
||||
this.streams.globalTimelineStream = new GlobalTimelineStreamManager(this, this.store.state.i);
|
||||
this.streams.driveStream = new DriveStreamManager(this, this.store.state.i);
|
||||
this.streams.messagingIndexStream = new MessagingIndexStreamManager(this, this.store.state.i);
|
||||
this.streams.othelloStream = new OthelloStreamManager(this, this.store.state.i);
|
||||
this.streams.reversiStream = new ReversiStreamManager(this, this.store.state.i);
|
||||
});
|
||||
//#endregion
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default (os) => (opts) => {
|
||||
}
|
||||
}).$mount();
|
||||
vm.$once('cancel', recover);
|
||||
vm.$once('note', recover);
|
||||
vm.$once('posted', recover);
|
||||
document.body.appendChild(vm.$el);
|
||||
(vm as any).focus();
|
||||
};
|
||||
|
@ -35,8 +35,9 @@ import MkFavorites from './views/pages/favorites.vue';
|
||||
import MkUserLists from './views/pages/user-lists.vue';
|
||||
import MkUserList from './views/pages/user-list.vue';
|
||||
import MkSettings from './views/pages/settings.vue';
|
||||
import MkOthello from './views/pages/othello.vue';
|
||||
import MkReversi from './views/pages/reversi.vue';
|
||||
import MkTag from './views/pages/tag.vue';
|
||||
import MkShare from './views/pages/share.vue';
|
||||
|
||||
/**
|
||||
* init
|
||||
@ -73,8 +74,9 @@ init((launch) => {
|
||||
{ path: '/selectdrive', component: MkSelectDrive },
|
||||
{ path: '/search', component: MkSearch },
|
||||
{ path: '/tags/:tag', component: MkTag },
|
||||
{ path: '/othello', name: 'othello', component: MkOthello },
|
||||
{ path: '/othello/:game', component: MkOthello },
|
||||
{ path: '/share', component: MkShare },
|
||||
{ path: '/reversi', name: 'reversi', component: MkReversi },
|
||||
{ path: '/reversi/:game', component: MkReversi },
|
||||
{ path: '/@:user', component: MkUser },
|
||||
{ path: '/@:user/followers', component: MkFollowers },
|
||||
{ path: '/@:user/following', component: MkFollowing },
|
||||
|
@ -22,6 +22,7 @@ import userTimeline from './user-timeline.vue';
|
||||
import userListTimeline from './user-list-timeline.vue';
|
||||
import activity from './activity.vue';
|
||||
import widgetContainer from './widget-container.vue';
|
||||
import postForm from './post-form.vue';
|
||||
|
||||
Vue.component('mk-ui', ui);
|
||||
Vue.component('mk-note', note);
|
||||
@ -45,3 +46,4 @@ Vue.component('mk-user-timeline', userTimeline);
|
||||
Vue.component('mk-user-list-timeline', userListTimeline);
|
||||
Vue.component('mk-activity', activity);
|
||||
Vue.component('mk-widget-container', widgetContainer);
|
||||
Vue.component('mk-post-form', postForm);
|
||||
|
@ -54,7 +54,25 @@ export default Vue.extend({
|
||||
MkVisibilityChooser
|
||||
},
|
||||
|
||||
props: ['reply', 'renote'],
|
||||
props: {
|
||||
reply: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
renote: {
|
||||
type: Object,
|
||||
required: false
|
||||
},
|
||||
initialText: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
instant: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
@ -112,6 +130,10 @@ export default Vue.extend({
|
||||
},
|
||||
|
||||
mounted() {
|
||||
if (this.initialText) {
|
||||
this.text = this.initialText;
|
||||
}
|
||||
|
||||
if (this.reply && this.reply.user.host != null) {
|
||||
this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
|
||||
}
|
||||
@ -252,8 +274,10 @@ export default Vue.extend({
|
||||
visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined,
|
||||
viaMobile: viaMobile
|
||||
}).then(data => {
|
||||
this.$emit('note');
|
||||
this.$destroy();
|
||||
this.$emit('posted');
|
||||
this.$nextTick(() => {
|
||||
this.$destroy();
|
||||
});
|
||||
}).catch(err => {
|
||||
this.posting = false;
|
||||
});
|
||||
|
@ -45,8 +45,8 @@ export default Vue.extend({
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
|
||||
this.connection.on('othello_invited', this.onOthelloInvited);
|
||||
this.connection.on('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.on('reversi_invited', this.onReversiInvited);
|
||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||
|
||||
const ago = (new Date().getTime() - new Date(this.$store.state.i.lastUsedAt).getTime()) / 1000;
|
||||
const isHisasiburi = ago >= 3600;
|
||||
@ -98,16 +98,16 @@ export default Vue.extend({
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.off('othello_invited', this.onOthelloInvited);
|
||||
this.connection.off('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.off('reversi_invited', this.onReversiInvited);
|
||||
this.connection.off('reversi_no_invites', this.onReversiNoInvites);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOthelloInvited() {
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitation = true;
|
||||
},
|
||||
onOthelloNoInvites() {
|
||||
onReversiNoInvites() {
|
||||
this.hasGameInvitation = false;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@
|
||||
<li><router-link to="/i/notifications" :data-active="$route.name == 'notifications'">%fa:R bell%%i18n:@notifications%<template v-if="hasUnreadNotification">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/i/messaging" :data-active="$route.name == 'messaging'">%fa:R comments%%i18n:@messaging%<template v-if="hasUnreadMessagingMessage">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li v-if="$store.getters.isSignedIn && $store.state.i.isLocked"><router-link to="/i/received-follow-requests" :data-active="$route.name == 'received-follow-requests'">%fa:R envelope%%i18n:@follow-requests%<template v-if="$store.getters.isSignedIn && $store.state.i.pendingReceivedFollowRequestsCount">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/othello" :data-active="$route.name == 'othello'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
<li><router-link to="/reversi" :data-active="$route.name == 'reversi'">%fa:gamepad%%i18n:@game%<template v-if="hasGameInvitation">%fa:circle%</template>%fa:angle-right%</router-link></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><router-link to="/i/widgets" :data-active="$route.name == 'widgets'">%fa:R calendar-alt%%i18n:@widgets%%fa:angle-right%</router-link></li>
|
||||
@ -66,14 +66,14 @@ export default Vue.extend({
|
||||
this.connection = (this as any).os.stream.getConnection();
|
||||
this.connectionId = (this as any).os.stream.use();
|
||||
|
||||
this.connection.on('othello_invited', this.onOthelloInvited);
|
||||
this.connection.on('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.on('reversi_invited', this.onReversiInvited);
|
||||
this.connection.on('reversi_no_invites', this.onReversiNoInvites);
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.$store.getters.isSignedIn) {
|
||||
this.connection.off('othello_invited', this.onOthelloInvited);
|
||||
this.connection.off('othello_no_invites', this.onOthelloNoInvites);
|
||||
this.connection.off('reversi_invited', this.onReversiInvited);
|
||||
this.connection.off('reversi_no_invites', this.onReversiNoInvites);
|
||||
(this as any).os.stream.dispose(this.connectionId);
|
||||
}
|
||||
},
|
||||
@ -83,10 +83,10 @@ export default Vue.extend({
|
||||
if (query == null || query == '') return;
|
||||
this.$router.push('/search?q=' + encodeURIComponent(query));
|
||||
},
|
||||
onOthelloInvited() {
|
||||
onReversiInvited() {
|
||||
this.hasGameInvitation = true;
|
||||
},
|
||||
onOthelloNoInvites() {
|
||||
onReversiNoInvites() {
|
||||
this.hasGameInvitation = false;
|
||||
},
|
||||
dark() {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<mk-ui>
|
||||
<span slot="header">%fa:gamepad%オセロ</span>
|
||||
<mk-othello v-if="!fetching" :init-game="game" @gamed="onGamed"/>
|
||||
<span slot="header">%fa:gamepad%リバーシ</span>
|
||||
<mk-reversi v-if="!fetching" :init-game="game" @gamed="onGamed"/>
|
||||
</mk-ui>
|
||||
</template>
|
||||
|
||||
@ -23,7 +23,7 @@ export default Vue.extend({
|
||||
this.fetch();
|
||||
},
|
||||
mounted() {
|
||||
document.title = 'Misskey オセロ';
|
||||
document.title = 'Misskey リバーシ';
|
||||
document.documentElement.style.background = '#fff';
|
||||
},
|
||||
methods: {
|
||||
@ -33,7 +33,7 @@ export default Vue.extend({
|
||||
Progress.start();
|
||||
this.fetching = true;
|
||||
|
||||
(this as any).api('othello/games/show', {
|
||||
(this as any).api('reversi/games/show', {
|
||||
gameId: this.$route.params.game
|
||||
}).then(game => {
|
||||
this.game = game;
|
||||
@ -43,7 +43,7 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
onGamed(game) {
|
||||
history.pushState(null, null, '/othello/' + game.id);
|
||||
history.pushState(null, null, '/reversi/' + game.id);
|
||||
}
|
||||
}
|
||||
});
|
@ -12,6 +12,7 @@
|
||||
|
||||
<ui-switch v-model="darkmode">%i18n:@dark-mode%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</ui-switch>
|
||||
<ui-switch v-model="$store.state.settings.iLikeSushi" @change="onChangeILikeSushi">%i18n:common.i-like-sushi%</ui-switch>
|
||||
|
||||
<div>
|
||||
<div>%i18n:@timeline%</div>
|
||||
@ -174,6 +175,13 @@ export default Vue.extend({
|
||||
});
|
||||
},
|
||||
|
||||
onChangeILikeSushi(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'iLikeSushi',
|
||||
value: v
|
||||
});
|
||||
},
|
||||
|
||||
onChangeShowReplyTarget(v) {
|
||||
this.$store.dispatch('settings/set', {
|
||||
key: 'showReplyTarget',
|
||||
|
56
src/client/app/mobile/views/pages/share.vue
Normal file
@ -0,0 +1,56 @@
|
||||
<template>
|
||||
<div class="azibmfpleajagva420swmu4c3r7ni7iw">
|
||||
<h1>Misskeyで共有</h1>
|
||||
<div>
|
||||
<mk-signin v-if="!$store.getters.isSignedIn"/>
|
||||
<mk-post-form v-else-if="!posted" :initial-text="text" :instant="true" @posted="posted = true"/>
|
||||
<p v-if="posted" class="posted">%fa:check%</p>
|
||||
</div>
|
||||
<ui-button class="close" v-if="posted" @click="close">閉じる</ui-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
|
||||
export default Vue.extend({
|
||||
data() {
|
||||
return {
|
||||
posted: false,
|
||||
text: new URLSearchParams(location.search).get('text')
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
window.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.azibmfpleajagva420swmu4c3r7ni7iw
|
||||
> h1
|
||||
margin 8px 0
|
||||
color #555
|
||||
font-size 20px
|
||||
text-align center
|
||||
|
||||
> div
|
||||
max-width 500px
|
||||
margin 0 auto
|
||||
|
||||
> .posted
|
||||
display block
|
||||
margin 0 auto
|
||||
padding 64px
|
||||
text-align center
|
||||
background #fff
|
||||
border-radius 6px
|
||||
width calc(100% - 32px)
|
||||
|
||||
> .close
|
||||
display block
|
||||
margin 16px auto
|
||||
width calc(100% - 32px)
|
||||
</style>
|
@ -18,7 +18,8 @@ const defaultSettings = {
|
||||
showRenotedMyNotes: true,
|
||||
loadRemoteMedia: true,
|
||||
disableViaMobile: false,
|
||||
memo: null
|
||||
memo: null,
|
||||
iLikeSushi: false
|
||||
};
|
||||
|
||||
const defaultDeviceSettings = {
|
||||
|
@ -4,11 +4,39 @@
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#313a42",
|
||||
"icons": {
|
||||
"16": "/assets/favicon/16.png",
|
||||
"32": "/assets/favicon/32.png",
|
||||
"64": "/assets/favicon/64.png",
|
||||
"128": "/assets/favicon/128.png",
|
||||
"256": "/assets/favicon/256.png"
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/icons/16.png",
|
||||
"sizes": "16x16",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/32.png",
|
||||
"sizes": "32x32",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/64.png",
|
||||
"sizes": "64x64",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/assets/icons/256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"share_target": {
|
||||
"url_template": "share?text={title}%20-%20{text}%20-%20{url}"
|
||||
}
|
||||
}
|
||||
|
BIN
src/client/assets/reactions/sushi.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
@ -19,9 +19,10 @@ import generateVars from '../vars';
|
||||
|
||||
const langs = Object.keys(locales);
|
||||
|
||||
const kebab = string => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
|
||||
const kebab = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
const parseParam = param => {
|
||||
// WIP type
|
||||
const parseParam = (param: any) => {
|
||||
const id = param.type.match(/^id\((.+?)\)|^id/);
|
||||
const entity = param.type.match(/^entity\((.+?)\)/);
|
||||
const isObject = /^object/.test(param.type);
|
||||
@ -57,7 +58,7 @@ const parseParam = param => {
|
||||
return param;
|
||||
};
|
||||
|
||||
const sortParams = params => {
|
||||
const sortParams = (params: Array<{name: string}>) => {
|
||||
params.sort((a, b) => {
|
||||
if (a.name < b.name)
|
||||
return -1;
|
||||
@ -68,14 +69,15 @@ const sortParams = params => {
|
||||
return params;
|
||||
};
|
||||
|
||||
const extractDefs = params => {
|
||||
let defs = [];
|
||||
// WIP type
|
||||
const extractDefs = (params: any[]) => {
|
||||
let defs: any[] = [];
|
||||
|
||||
params.forEach(param => {
|
||||
if (param.def) {
|
||||
defs.push({
|
||||
name: param.defName,
|
||||
params: sortParams(param.def.map(p => parseParam(p)))
|
||||
params: sortParams(param.def.map((p: any) => parseParam(p)))
|
||||
});
|
||||
|
||||
const childDefs = extractDefs(param.def);
|
||||
@ -109,8 +111,10 @@ gulp.task('doc:api:endpoints', async () => {
|
||||
path: ep.endpoint
|
||||
},
|
||||
desc: ep.desc,
|
||||
// @ts-ignore
|
||||
params: sortParams(ep.params.map(p => parseParam(p))),
|
||||
paramDefs: extractDefs(ep.params),
|
||||
// @ts-ignore
|
||||
res: ep.res ? sortParams(ep.res.map(p => parseParam(p))) : null,
|
||||
resDefs: ep.res ? extractDefs(ep.res) : null,
|
||||
};
|
||||
@ -155,7 +159,8 @@ gulp.task('doc:api:entities', async () => {
|
||||
const vars = {
|
||||
name: entity.name,
|
||||
desc: entity.desc,
|
||||
props: sortParams(entity.props.map(p => parseParam(p))),
|
||||
// WIP type
|
||||
props: sortParams(entity.props.map((p: any) => parseParam(p))),
|
||||
propDefs: extractDefs(entity.props),
|
||||
};
|
||||
langs.forEach(lang => {
|
||||
|
@ -8,8 +8,8 @@ import * as glob from 'glob';
|
||||
import * as gulp from 'gulp';
|
||||
import * as pug from 'pug';
|
||||
import * as mkdirp from 'mkdirp';
|
||||
import stylus = require('gulp-stylus');
|
||||
import cssnano = require('gulp-cssnano');
|
||||
const stylus = require('gulp-stylus');
|
||||
const cssnano = require('gulp-cssnano');
|
||||
|
||||
import I18nReplacer from '../../build/i18n';
|
||||
import fa from '../../build/fa';
|
||||
|
@ -38,7 +38,7 @@ export default async function(): Promise<{ [key: string]: any }> {
|
||||
vars['docs'][name]['title'][lang] = fs.readFileSync(x, 'utf-8').match(/^h1 (.+?)\r?\n/)[1];
|
||||
});
|
||||
|
||||
vars['kebab'] = string => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
|
||||
vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase();
|
||||
|
||||
vars['config'] = config;
|
||||
|
||||
|
@ -60,7 +60,7 @@ export type Source = {
|
||||
hook_secret: string;
|
||||
username: string;
|
||||
};
|
||||
othello_ai?: {
|
||||
reversi_ai?: {
|
||||
id: string;
|
||||
i: string;
|
||||
};
|
||||
|
@ -1,60 +0,0 @@
|
||||
import Note from '../models/note';
|
||||
|
||||
// 10分
|
||||
const interval = 1000 * 60 * 10;
|
||||
|
||||
async function tick() {
|
||||
const res = await Note.aggregate([{
|
||||
$match: {
|
||||
createdAt: {
|
||||
$gt: new Date(Date.now() - interval)
|
||||
},
|
||||
tags: {
|
||||
$exists: true,
|
||||
$ne: []
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$unwind: '$tags'
|
||||
}, {
|
||||
$group: {
|
||||
_id: '$tags',
|
||||
count: {
|
||||
$sum: 1
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$group: {
|
||||
_id: null,
|
||||
tags: {
|
||||
$push: {
|
||||
tag: '$_id',
|
||||
count: '$count'
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
$project: {
|
||||
_id: false,
|
||||
tags: true
|
||||
}
|
||||
}]) as {
|
||||
tags: Array<{
|
||||
tag: string;
|
||||
count: number;
|
||||
}>
|
||||
};
|
||||
|
||||
const stats = res.tags
|
||||
.sort((a, b) => a.count - b.count)
|
||||
.map(tag => [tag.tag, tag.count])
|
||||
.slice(0, 10);
|
||||
|
||||
console.log(stats);
|
||||
|
||||
process.send(stats);
|
||||
}
|
||||
|
||||
tick();
|
||||
|
||||
setInterval(tick, interval);
|
@ -1,20 +0,0 @@
|
||||
import * as childProcess from 'child_process';
|
||||
import Xev from 'xev';
|
||||
|
||||
const ev = new Xev();
|
||||
|
||||
export default function() {
|
||||
const log = [];
|
||||
|
||||
const p = childProcess.fork(__dirname + '/hashtags-stats-child.js');
|
||||
|
||||
p.on('message', stats => {
|
||||
ev.emit('hashtagsStats', stats);
|
||||
log.push(stats);
|
||||
if (log.length > 30) log.shift();
|
||||
});
|
||||
|
||||
ev.on('requestHashTagsStatsLog', id => {
|
||||
ev.emit('hashtagsStatsLog:' + id, log);
|
||||
});
|
||||
}
|
@ -4,7 +4,7 @@ import Xev from 'xev';
|
||||
const ev = new Xev();
|
||||
|
||||
export default function() {
|
||||
const log = [];
|
||||
const log: any[] = [];
|
||||
|
||||
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
|
||||
|
||||
|
@ -11,14 +11,14 @@ const interval = 1000;
|
||||
* Report server stats regularly
|
||||
*/
|
||||
export default function() {
|
||||
const log = [];
|
||||
const log: any[] = [];
|
||||
|
||||
ev.on('requestServerStatsLog', id => {
|
||||
ev.emit('serverStatsLog:' + id, log);
|
||||
});
|
||||
|
||||
async function tick() {
|
||||
osUtils.cpuUsage(cpuUsage => {
|
||||
osUtils.cpuUsage((cpuUsage: number) => {
|
||||
const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/');
|
||||
const stats = {
|
||||
cpu_usage: cpuUsage,
|
||||
|
@ -27,7 +27,7 @@ const nativeDbConn = async (): Promise<mongodb.Db> => {
|
||||
if (mdb) return mdb;
|
||||
|
||||
const db = await ((): Promise<mongodb.Db> => new Promise((resolve, reject) => {
|
||||
(mongodb as any).MongoClient.connect(uri, (e, client) => {
|
||||
(mongodb as any).MongoClient.connect(uri, (e: Error, client: any) => {
|
||||
if (e) return reject(e);
|
||||
resolve(client.db(config.mongodb.db));
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ function main() {
|
||||
/**
|
||||
* Init master process
|
||||
*/
|
||||
async function masterMain(opt) {
|
||||
async function masterMain(opt: any) {
|
||||
let config: Config;
|
||||
|
||||
try {
|
||||
@ -91,7 +91,7 @@ async function masterMain(opt) {
|
||||
/**
|
||||
* Init worker process
|
||||
*/
|
||||
async function workerMain(opt) {
|
||||
async function workerMain(opt: any) {
|
||||
if (!opt['only-processor']) {
|
||||
// start server
|
||||
await require('./server').default();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import AccessToken from './access-token';
|
||||
import db from '../db/mongodb';
|
||||
import config from '../config';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { pack as packApp } from './app';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import { pack as packFolder } from './drive-folder';
|
||||
import config from '../config';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import DriveFile from './drive-file';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { pack as packNote } from './note';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import { pack as packUser } from './user';
|
||||
import { pack as packFile } from './drive-file';
|
||||
import db from '../db/mongodb';
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import $ from 'cafy';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import Reaction from './note-reaction';
|
||||
import { pack as packUser } from './user';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
@ -37,7 +37,11 @@ export type INote = {
|
||||
mediaIds: mongo.ObjectID[];
|
||||
replyId: mongo.ObjectID;
|
||||
renoteId: mongo.ObjectID;
|
||||
poll: any; // todo
|
||||
poll: {
|
||||
choices: Array<{
|
||||
id: number;
|
||||
}>
|
||||
};
|
||||
text: string;
|
||||
tags: string[];
|
||||
tagsLower: string[];
|
||||
@ -216,7 +220,7 @@ export const pack = async (
|
||||
hide = false;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = _note.visibleUserIds.some(id => id.equals(meId));
|
||||
const specified = _note.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId));
|
||||
|
||||
if (specified) {
|
||||
hide = false;
|
||||
@ -268,7 +272,7 @@ export const pack = async (
|
||||
}
|
||||
|
||||
// Populate media
|
||||
_note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
|
||||
_note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) =>
|
||||
packFile(fileId)
|
||||
));
|
||||
|
||||
@ -304,7 +308,7 @@ export const pack = async (
|
||||
|
||||
if (vote != null) {
|
||||
const myChoice = poll.choices
|
||||
.filter(c => c.id == vote.choice)[0];
|
||||
.filter((c: any) => c.id == vote.choice)[0];
|
||||
|
||||
myChoice.isVoted = true;
|
||||
}
|
||||
@ -343,6 +347,10 @@ export const pack = async (
|
||||
_note.mediaIds = [];
|
||||
_note.text = null;
|
||||
_note.poll = null;
|
||||
_note.cw = null;
|
||||
_note.tags = [];
|
||||
_note.tagsLower = [];
|
||||
_note.geo = null;
|
||||
_note.isHidden = true;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packNote } from './note';
|
||||
|
@ -1,12 +1,12 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
|
||||
const OthelloGame = db.get<IOthelloGame>('othelloGames');
|
||||
export default OthelloGame;
|
||||
const ReversiGame = db.get<IReversiGame>('reversiGames');
|
||||
export default ReversiGame;
|
||||
|
||||
export interface IOthelloGame {
|
||||
export interface IReversiGame {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
startedAt: Date;
|
||||
@ -45,7 +45,7 @@ export interface IOthelloGame {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack an othello game for API response
|
||||
* Pack an reversi game for API response
|
||||
*/
|
||||
export const pack = (
|
||||
game: any,
|
||||
@ -62,11 +62,11 @@ export const pack = (
|
||||
|
||||
// Populate the game if 'game' is ID
|
||||
if (mongo.ObjectID.prototype.isPrototypeOf(game)) {
|
||||
_game = await OthelloGame.findOne({
|
||||
_game = await ReversiGame.findOne({
|
||||
_id: game
|
||||
});
|
||||
} else if (typeof game === 'string') {
|
||||
_game = await OthelloGame.findOne({
|
||||
_game = await ReversiGame.findOne({
|
||||
_id: new mongo.ObjectID(game)
|
||||
});
|
||||
} else {
|
@ -1,9 +1,9 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
|
||||
const Matching = db.get<IMatching>('othelloMatchings');
|
||||
const Matching = db.get<IMatching>('reversiMatchings');
|
||||
export default Matching;
|
||||
|
||||
export interface IMatching {
|
||||
@ -14,7 +14,7 @@ export interface IMatching {
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack an othello matching for API response
|
||||
* Pack an reversi matching for API response
|
||||
*/
|
||||
export const pack = (
|
||||
matching: any,
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Signin = db.get<ISignin>('signin');
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
const deepcopy = require('deepcopy');
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const UserList = db.get<IUserList>('userList');
|
||||
|
@ -1,6 +1,6 @@
|
||||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import sequential = require('promise-sequential');
|
||||
const deepcopy = require('deepcopy');
|
||||
const sequential = require('promise-sequential');
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import Note, { pack as packNote, deleteNote } from './note';
|
||||
@ -153,14 +153,6 @@ export function isValidBirthday(birthday: string): boolean {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export function init(user): IUser {
|
||||
user._id = new mongo.ObjectID(user._id);
|
||||
user.avatarId = new mongo.ObjectID(user.avatarId);
|
||||
user.bannerId = new mongo.ObjectID(user.bannerId);
|
||||
user.pinnedNoteId = new mongo.ObjectID(user.pinnedNoteId);
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Userを物理削除します
|
||||
*/
|
||||
|
@ -1,377 +0,0 @@
|
||||
/**
|
||||
* -AI-
|
||||
* Botのバックエンド(思考を担当)
|
||||
*
|
||||
* 対話と思考を同じプロセスで行うと、思考時間が長引いたときにストリームから
|
||||
* 切断されてしまうので、別々のプロセスで行うようにします
|
||||
*/
|
||||
|
||||
import * as request from 'request-promise-native';
|
||||
import Othello, { Color } from '../core';
|
||||
import conf from '../../config';
|
||||
import getUserName from '../../renderers/get-user-name';
|
||||
|
||||
let game;
|
||||
let form;
|
||||
|
||||
/**
|
||||
* BotアカウントのユーザーID
|
||||
*/
|
||||
const id = conf.othello_ai.id;
|
||||
|
||||
/**
|
||||
* BotアカウントのAPIキー
|
||||
*/
|
||||
const i = conf.othello_ai.i;
|
||||
|
||||
let note;
|
||||
|
||||
process.on('message', async msg => {
|
||||
// 親プロセスからデータをもらう
|
||||
if (msg.type == '_init_') {
|
||||
game = msg.game;
|
||||
form = msg.form;
|
||||
}
|
||||
|
||||
// フォームが更新されたとき
|
||||
if (msg.type == 'update-form') {
|
||||
form.find(i => i.id == msg.body.id).value = msg.body.value;
|
||||
}
|
||||
|
||||
// ゲームが始まったとき
|
||||
if (msg.type == 'started') {
|
||||
onGameStarted(msg.body);
|
||||
|
||||
//#region TLに投稿する
|
||||
const game = msg.body;
|
||||
const url = `${conf.url}/othello/${game.id}`;
|
||||
const user = game.user1Id == id ? game.user2 : game.user1;
|
||||
const isSettai = form[0].value === 0;
|
||||
const text = isSettai
|
||||
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんの接待を始めました!`
|
||||
: `対局を?[${getUserName(user)}](${conf.url}/@${user.username})さんと始めました! (強さ${form[0].value})`;
|
||||
|
||||
const res = await request.post(`${conf.api_url}/notes/create`, {
|
||||
json: { i,
|
||||
text: `${text}\n→[観戦する](${url})`
|
||||
}
|
||||
});
|
||||
|
||||
note = res.createdNote;
|
||||
//#endregion
|
||||
}
|
||||
|
||||
// ゲームが終了したとき
|
||||
if (msg.type == 'ended') {
|
||||
// ストリームから切断
|
||||
process.send({
|
||||
type: 'close'
|
||||
});
|
||||
|
||||
//#region TLに投稿する
|
||||
const user = game.user1Id == id ? game.user2 : game.user1;
|
||||
const isSettai = form[0].value === 0;
|
||||
const text = isSettai
|
||||
? msg.body.winnerId === null
|
||||
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で引き分けました...`
|
||||
: msg.body.winnerId == id
|
||||
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で勝ってしまいました...`
|
||||
: `?[${getUserName(user)}](${conf.url}/@${user.username})さんに接待で負けてあげました♪`
|
||||
: msg.body.winnerId === null
|
||||
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんと引き分けました~`
|
||||
: msg.body.winnerId == id
|
||||
? `?[${getUserName(user)}](${conf.url}/@${user.username})さんに勝ちました♪`
|
||||
: `?[${getUserName(user)}](${conf.url}/@${user.username})さんに負けました...`;
|
||||
|
||||
await request.post(`${conf.api_url}/notes/create`, {
|
||||
json: { i,
|
||||
renoteId: note.id,
|
||||
text: text
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
process.exit();
|
||||
}
|
||||
|
||||
// 打たれたとき
|
||||
if (msg.type == 'set') {
|
||||
onSet(msg.body);
|
||||
}
|
||||
});
|
||||
|
||||
let o: Othello;
|
||||
let botColor: Color;
|
||||
|
||||
// 各マスの強さ
|
||||
let cellWeights;
|
||||
|
||||
/**
|
||||
* ゲーム開始時
|
||||
* @param g ゲーム情報
|
||||
*/
|
||||
function onGameStarted(g) {
|
||||
game = g;
|
||||
|
||||
// オセロエンジン初期化
|
||||
o = new Othello(game.settings.map, {
|
||||
isLlotheo: game.settings.isLlotheo,
|
||||
canPutEverywhere: game.settings.canPutEverywhere,
|
||||
loopedBoard: game.settings.loopedBoard
|
||||
});
|
||||
|
||||
// 各マスの価値を計算しておく
|
||||
cellWeights = o.map.map((pix, i) => {
|
||||
if (pix == 'null') return 0;
|
||||
const [x, y] = o.transformPosToXy(i);
|
||||
let count = 0;
|
||||
const get = (x, y) => {
|
||||
if (x < 0 || y < 0 || x >= o.mapWidth || y >= o.mapHeight) return 'null';
|
||||
return o.mapDataGet(o.transformXyToPos(x, y));
|
||||
};
|
||||
|
||||
if (get(x , y - 1) == 'null') count++;
|
||||
if (get(x + 1, y - 1) == 'null') count++;
|
||||
if (get(x + 1, y ) == 'null') count++;
|
||||
if (get(x + 1, y + 1) == 'null') count++;
|
||||
if (get(x , y + 1) == 'null') count++;
|
||||
if (get(x - 1, y + 1) == 'null') count++;
|
||||
if (get(x - 1, y ) == 'null') count++;
|
||||
if (get(x - 1, y - 1) == 'null') count++;
|
||||
//return Math.pow(count, 3);
|
||||
return count >= 4 ? 1 : 0;
|
||||
});
|
||||
|
||||
botColor = game.user1Id == id && game.black == 1 || game.user2Id == id && game.black == 2;
|
||||
|
||||
if (botColor) {
|
||||
think();
|
||||
}
|
||||
}
|
||||
|
||||
function onSet(x) {
|
||||
o.put(x.color, x.pos);
|
||||
|
||||
if (x.next === botColor) {
|
||||
think();
|
||||
}
|
||||
}
|
||||
|
||||
const db = {};
|
||||
|
||||
function think() {
|
||||
console.log('Thinking...');
|
||||
console.time('think');
|
||||
|
||||
const isSettai = form[0].value === 0;
|
||||
|
||||
// 接待モードのときは、全力(5手先読みくらい)で負けるようにする
|
||||
const maxDepth = isSettai ? 5 : form[0].value;
|
||||
|
||||
/**
|
||||
* Botにとってある局面がどれだけ有利か取得する
|
||||
*/
|
||||
function staticEval() {
|
||||
let score = o.canPutSomewhere(botColor).length;
|
||||
|
||||
cellWeights.forEach((weight, i) => {
|
||||
// 係数
|
||||
const coefficient = 30;
|
||||
weight = weight * coefficient;
|
||||
|
||||
const stone = o.board[i];
|
||||
if (stone === botColor) {
|
||||
// TODO: 価値のあるマスに設置されている自分の石に縦か横に接するマスは価値があると判断する
|
||||
score += weight;
|
||||
} else if (stone !== null) {
|
||||
score -= weight;
|
||||
}
|
||||
});
|
||||
|
||||
// ロセオならスコアを反転
|
||||
if (game.settings.isLlotheo) score = -score;
|
||||
|
||||
// 接待ならスコアを反転
|
||||
if (isSettai) score = -score;
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
/**
|
||||
* αβ法での探索
|
||||
*/
|
||||
const dive = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => {
|
||||
// 試し打ち
|
||||
o.put(o.turn, pos);
|
||||
|
||||
const key = o.board.toString();
|
||||
let cache = db[key];
|
||||
if (cache) {
|
||||
if (alpha >= cache.upper) {
|
||||
o.undo();
|
||||
return cache.upper;
|
||||
}
|
||||
if (beta <= cache.lower) {
|
||||
o.undo();
|
||||
return cache.lower;
|
||||
}
|
||||
alpha = Math.max(alpha, cache.lower);
|
||||
beta = Math.min(beta, cache.upper);
|
||||
} else {
|
||||
cache = {
|
||||
upper: Infinity,
|
||||
lower: -Infinity
|
||||
};
|
||||
}
|
||||
|
||||
const isBotTurn = o.turn === botColor;
|
||||
|
||||
// 勝った
|
||||
if (o.turn === null) {
|
||||
const winner = o.winner;
|
||||
|
||||
// 勝つことによる基本スコア
|
||||
const base = 10000;
|
||||
|
||||
let score;
|
||||
|
||||
if (game.settings.isLlotheo) {
|
||||
// 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する
|
||||
score = o.winner ? base - (o.blackCount * 100) : base - (o.whiteCount * 100);
|
||||
} else {
|
||||
// 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する
|
||||
score = o.winner ? base + (o.blackCount * 100) : base + (o.whiteCount * 100);
|
||||
}
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
// 接待なら自分が負けた方が高スコア
|
||||
return isSettai
|
||||
? winner !== botColor ? score : -score
|
||||
: winner === botColor ? score : -score;
|
||||
}
|
||||
|
||||
if (depth === maxDepth) {
|
||||
// 静的に評価
|
||||
const score = staticEval();
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
return score;
|
||||
} else {
|
||||
const cans = o.canPutSomewhere(o.turn);
|
||||
|
||||
let value = isBotTurn ? -Infinity : Infinity;
|
||||
let a = alpha;
|
||||
let b = beta;
|
||||
|
||||
// 次のターンのプレイヤーにとって最も良い手を取得
|
||||
for (const p of cans) {
|
||||
if (isBotTurn) {
|
||||
const score = dive(p, a, beta, depth + 1);
|
||||
value = Math.max(value, score);
|
||||
a = Math.max(a, value);
|
||||
if (value >= beta) break;
|
||||
} else {
|
||||
const score = dive(p, alpha, b, depth + 1);
|
||||
value = Math.min(value, score);
|
||||
b = Math.min(b, value);
|
||||
if (value <= alpha) break;
|
||||
}
|
||||
}
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
if (value <= alpha) {
|
||||
cache.upper = value;
|
||||
} else if (value >= beta) {
|
||||
cache.lower = value;
|
||||
} else {
|
||||
cache.upper = value;
|
||||
cache.lower = value;
|
||||
}
|
||||
|
||||
db[key] = cache;
|
||||
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* αβ法での探索(キャッシュ無し)(デバッグ用)
|
||||
*/
|
||||
const dive2 = (pos: number, alpha = -Infinity, beta = Infinity, depth = 0): number => {
|
||||
// 試し打ち
|
||||
o.put(o.turn, pos);
|
||||
|
||||
const isBotTurn = o.turn === botColor;
|
||||
|
||||
// 勝った
|
||||
if (o.turn === null) {
|
||||
const winner = o.winner;
|
||||
|
||||
// 勝つことによる基本スコア
|
||||
const base = 10000;
|
||||
|
||||
let score;
|
||||
|
||||
if (game.settings.isLlotheo) {
|
||||
// 勝ちは勝ちでも、より自分の石を少なくした方が美しい勝ちだと判定する
|
||||
score = o.winner ? base - (o.blackCount * 100) : base - (o.whiteCount * 100);
|
||||
} else {
|
||||
// 勝ちは勝ちでも、より相手の石を少なくした方が美しい勝ちだと判定する
|
||||
score = o.winner ? base + (o.blackCount * 100) : base + (o.whiteCount * 100);
|
||||
}
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
// 接待なら自分が負けた方が高スコア
|
||||
return isSettai
|
||||
? winner !== botColor ? score : -score
|
||||
: winner === botColor ? score : -score;
|
||||
}
|
||||
|
||||
if (depth === maxDepth) {
|
||||
// 静的に評価
|
||||
const score = staticEval();
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
return score;
|
||||
} else {
|
||||
const cans = o.canPutSomewhere(o.turn);
|
||||
|
||||
// 次のターンのプレイヤーにとって最も良い手を取得
|
||||
for (const p of cans) {
|
||||
if (isBotTurn) {
|
||||
alpha = Math.max(alpha, dive2(p, alpha, beta, depth + 1));
|
||||
} else {
|
||||
beta = Math.min(beta, dive2(p, alpha, beta, depth + 1));
|
||||
}
|
||||
if (alpha >= beta) break;
|
||||
}
|
||||
|
||||
// 巻き戻し
|
||||
o.undo();
|
||||
|
||||
return isBotTurn ? alpha : beta;
|
||||
}
|
||||
};
|
||||
|
||||
const cans = o.canPutSomewhere(botColor);
|
||||
const scores = cans.map(p => dive(p));
|
||||
const pos = cans[scores.indexOf(Math.max(...scores))];
|
||||
|
||||
console.log('Thinked:', pos);
|
||||
console.timeEnd('think');
|
||||
|
||||
process.send({
|
||||
type: 'put',
|
||||
pos
|
||||
});
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/**
|
||||
* -AI-
|
||||
* Botのフロントエンド(ストリームとの対話を担当)
|
||||
*
|
||||
* 対話と思考を同じプロセスで行うと、思考時間が長引いたときにストリームから
|
||||
* 切断されてしまうので、別々のプロセスで行うようにします
|
||||
*/
|
||||
|
||||
import * as childProcess from 'child_process';
|
||||
const WebSocket = require('ws');
|
||||
import * as ReconnectingWebSocket from 'reconnecting-websocket';
|
||||
import * as request from 'request-promise-native';
|
||||
import conf from '../../config';
|
||||
|
||||
// 設定 ////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* BotアカウントのAPIキー
|
||||
*/
|
||||
const i = conf.othello_ai.i;
|
||||
|
||||
/**
|
||||
* BotアカウントのユーザーID
|
||||
*/
|
||||
const id = conf.othello_ai.id;
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* ホームストリーム
|
||||
*/
|
||||
const homeStream = new ReconnectingWebSocket(`${conf.ws_url}/?i=${i}`, undefined, {
|
||||
constructor: WebSocket
|
||||
});
|
||||
|
||||
homeStream.on('open', () => {
|
||||
console.log('home stream opened');
|
||||
});
|
||||
|
||||
homeStream.on('close', () => {
|
||||
console.log('home stream closed');
|
||||
});
|
||||
|
||||
homeStream.on('message', message => {
|
||||
const msg = JSON.parse(message.toString());
|
||||
|
||||
// タイムライン上でなんか言われたまたは返信されたとき
|
||||
if (msg.type == 'mention' || msg.type == 'reply') {
|
||||
const note = msg.body;
|
||||
|
||||
if (note.userId == id) return;
|
||||
|
||||
// リアクションする
|
||||
request.post(`${conf.api_url}/notes/reactions/create`, {
|
||||
json: { i,
|
||||
noteId: note.id,
|
||||
reaction: 'love'
|
||||
}
|
||||
});
|
||||
|
||||
if (note.text) {
|
||||
if (note.text.indexOf('オセロ') > -1) {
|
||||
request.post(`${conf.api_url}/notes/create`, {
|
||||
json: { i,
|
||||
replyId: note.id,
|
||||
text: '良いですよ~'
|
||||
}
|
||||
});
|
||||
|
||||
invite(note.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// メッセージでなんか言われたとき
|
||||
if (msg.type == 'messaging_message') {
|
||||
const message = msg.body;
|
||||
if (message.text) {
|
||||
if (message.text.indexOf('オセロ') > -1) {
|
||||
request.post(`${conf.api_url}/messaging/messages/create`, {
|
||||
json: { i,
|
||||
userId: message.userId,
|
||||
text: '良いですよ~'
|
||||
}
|
||||
});
|
||||
|
||||
invite(message.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ユーザーを対局に誘う
|
||||
function invite(userId) {
|
||||
request.post(`${conf.api_url}/othello/match`, {
|
||||
json: { i,
|
||||
userId: userId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* オセロストリーム
|
||||
*/
|
||||
const othelloStream = new ReconnectingWebSocket(`${conf.ws_url}/othello?i=${i}`, undefined, {
|
||||
constructor: WebSocket
|
||||
});
|
||||
|
||||
othelloStream.on('open', () => {
|
||||
console.log('othello stream opened');
|
||||
});
|
||||
|
||||
othelloStream.on('close', () => {
|
||||
console.log('othello stream closed');
|
||||
});
|
||||
|
||||
othelloStream.on('message', message => {
|
||||
const msg = JSON.parse(message.toString());
|
||||
|
||||
// 招待されたとき
|
||||
if (msg.type == 'invited') {
|
||||
onInviteMe(msg.body.parent);
|
||||
}
|
||||
|
||||
// マッチしたとき
|
||||
if (msg.type == 'matched') {
|
||||
gameStart(msg.body);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* ゲーム開始
|
||||
* @param game ゲーム情報
|
||||
*/
|
||||
function gameStart(game) {
|
||||
// ゲームストリームに接続
|
||||
const gw = new ReconnectingWebSocket(`${conf.ws_url}/othello-game?i=${i}&game=${game.id}`, undefined, {
|
||||
constructor: WebSocket
|
||||
});
|
||||
|
||||
gw.on('open', () => {
|
||||
console.log('othello game stream opened');
|
||||
|
||||
// フォーム
|
||||
const form = [{
|
||||
id: 'strength',
|
||||
type: 'radio',
|
||||
label: '強さ',
|
||||
value: 2,
|
||||
items: [{
|
||||
label: '接待',
|
||||
value: 0
|
||||
}, {
|
||||
label: '弱',
|
||||
value: 1
|
||||
}, {
|
||||
label: '中',
|
||||
value: 2
|
||||
}, {
|
||||
label: '強',
|
||||
value: 3
|
||||
}, {
|
||||
label: '最強',
|
||||
value: 5
|
||||
}]
|
||||
}];
|
||||
|
||||
//#region バックエンドプロセス開始
|
||||
const ai = childProcess.fork(__dirname + '/back.js');
|
||||
|
||||
// バックエンドプロセスに情報を渡す
|
||||
ai.send({
|
||||
type: '_init_',
|
||||
game,
|
||||
form
|
||||
});
|
||||
|
||||
ai.on('message', msg => {
|
||||
if (msg.type == 'put') {
|
||||
gw.send(JSON.stringify({
|
||||
type: 'set',
|
||||
pos: msg.pos
|
||||
}));
|
||||
} else if (msg.type == 'close') {
|
||||
gw.close();
|
||||
}
|
||||
});
|
||||
|
||||
// ゲームストリームから情報が流れてきたらそのままバックエンドプロセスに伝える
|
||||
gw.on('message', message => {
|
||||
const msg = JSON.parse(message.toString());
|
||||
ai.send(msg);
|
||||
});
|
||||
//#endregion
|
||||
|
||||
// フォーム初期化
|
||||
setTimeout(() => {
|
||||
gw.send(JSON.stringify({
|
||||
type: 'init-form',
|
||||
body: form
|
||||
}));
|
||||
}, 1000);
|
||||
|
||||
// どんな設定内容の対局でも受け入れる
|
||||
setTimeout(() => {
|
||||
gw.send(JSON.stringify({
|
||||
type: 'accept'
|
||||
}));
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
gw.on('close', () => {
|
||||
console.log('othello game stream closed');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* オセロの対局に招待されたとき
|
||||
* @param inviter 誘ってきたユーザー
|
||||
*/
|
||||
async function onInviteMe(inviter) {
|
||||
console.log(`Someone invited me: @${inviter.username}`);
|
||||
|
||||
// 承認
|
||||
const game = await request.post(`${conf.api_url}/othello/match`, {
|
||||
json: {
|
||||
i,
|
||||
userId: inviter.id
|
||||
}
|
||||
});
|
||||
|
||||
gameStart(game);
|
||||
}
|
@ -1 +0,0 @@
|
||||
require('./front');
|
@ -1,6 +1,6 @@
|
||||
import * as nopt from 'nopt';
|
||||
|
||||
export default (vector, index) => {
|
||||
export default (vector: any, index: any) => {
|
||||
const parsed = nopt({
|
||||
'only-processor': Boolean,
|
||||
'only-server': Boolean
|
||||
|
@ -11,7 +11,7 @@ if (config.sw) {
|
||||
config.sw.private_key);
|
||||
}
|
||||
|
||||
export default async function(userId: mongo.ObjectID | string, type, body?) {
|
||||
export default async function(userId: mongo.ObjectID | string, type: string, body?: any) {
|
||||
if (!config.sw) return;
|
||||
|
||||
if (typeof userId === 'string') {
|
||||
@ -34,7 +34,7 @@ export default async function(userId: mongo.ObjectID | string, type, body?) {
|
||||
|
||||
push.sendNotification(pushSubscription, JSON.stringify({
|
||||
type, body
|
||||
})).catch(err => {
|
||||
})).catch((err: any) => {
|
||||
//console.log(err.statusCode);
|
||||
//console.log(err.headers);
|
||||
//console.log(err.body);
|
||||
|
@ -37,12 +37,12 @@ class MisskeyEvent {
|
||||
this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
public publishOthelloStream(userId: ID, type: string, value?: any): void {
|
||||
this.publish(`othello-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
public publishReversiStream(userId: ID, type: string, value?: any): void {
|
||||
this.publish(`reversi-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
public publishOthelloGameStream(gameId: ID, type: string, value?: any): void {
|
||||
this.publish(`othello-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
|
||||
public publishReversiGameStream(gameId: ID, type: string, value?: any): void {
|
||||
this.publish(`reversi-game-stream:${gameId}`, type, typeof value === 'undefined' ? null : value);
|
||||
}
|
||||
|
||||
public publishLocalTimelineStream(note: any): void {
|
||||
@ -73,5 +73,5 @@ export const publishUserListStream = ev.publishUserListStream.bind(ev);
|
||||
export const publishNoteStream = ev.publishNoteStream.bind(ev);
|
||||
export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
|
||||
export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev);
|
||||
export const publishOthelloStream = ev.publishOthelloStream.bind(ev);
|
||||
export const publishOthelloGameStream = ev.publishOthelloGameStream.bind(ev);
|
||||
export const publishReversiStream = ev.publishReversiStream.bind(ev);
|
||||
export const publishReversiGameStream = ev.publishReversiGameStream.bind(ev);
|
||||
|
@ -12,7 +12,7 @@ const queue = createQueue({
|
||||
}
|
||||
});
|
||||
|
||||
export function createHttp(data) {
|
||||
export function createHttp(data: any) {
|
||||
return queue
|
||||
.create('http', data)
|
||||
.removeOnComplete(true)
|
||||
@ -21,7 +21,7 @@ export function createHttp(data) {
|
||||
.backoff({ delay: 16384, type: 'exponential' });
|
||||
}
|
||||
|
||||
export function deliver(user: ILocalUser, content, to) {
|
||||
export function deliver(user: ILocalUser, content: any, to: any) {
|
||||
createHttp({
|
||||
title: 'deliver',
|
||||
type: 'deliver',
|
||||
|
@ -2,7 +2,7 @@ import * as kue from 'kue';
|
||||
|
||||
import request from '../../../remote/activitypub/request';
|
||||
|
||||
export default async (job: kue.Job, done): Promise<void> => {
|
||||
export default async (job: kue.Job, done: any): Promise<void> => {
|
||||
try {
|
||||
await request(job.data.user, job.data.to, job.data.content);
|
||||
done();
|
||||
|
@ -1,12 +1,12 @@
|
||||
import deliver from './deliver';
|
||||
import processInbox from './process-inbox';
|
||||
|
||||
const handlers = {
|
||||
const handlers: any = {
|
||||
deliver,
|
||||
processInbox,
|
||||
};
|
||||
|
||||
export default (job, done) => {
|
||||
export default (job: any, done: any) => {
|
||||
const handler = handlers[job.data.type];
|
||||
|
||||
if (handler) {
|
||||
|
@ -10,7 +10,7 @@ import { resolvePerson } from '../../../remote/activitypub/models/person';
|
||||
const log = debug('misskey:queue:inbox');
|
||||
|
||||
// ユーザーのinboxにアクティビティが届いた時の処理
|
||||
export default async (job: kue.Job, done): Promise<void> => {
|
||||
export default async (job: kue.Job, done: any): Promise<void> => {
|
||||
const signature = job.data.signature;
|
||||
const activity = job.data.activity;
|
||||
|
||||
@ -22,7 +22,7 @@ export default async (job: kue.Job, done): Promise<void> => {
|
||||
//#endregion
|
||||
|
||||
const keyIdLower = signature.keyId.toLowerCase();
|
||||
let user;
|
||||
let user: IRemoteUser;
|
||||
|
||||
if (keyIdLower.startsWith('acct:')) {
|
||||
const { username, host } = parseAcct(keyIdLower.slice('acct:'.length));
|
||||
@ -36,7 +36,7 @@ export default async (job: kue.Job, done): Promise<void> => {
|
||||
|
||||
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
|
||||
if (user === null) {
|
||||
user = await resolvePerson(activity.actor);
|
||||
user = await resolvePerson(activity.actor) as IRemoteUser;
|
||||
}
|
||||
} else {
|
||||
user = await User.findOne({
|
||||
@ -46,7 +46,7 @@ export default async (job: kue.Job, done): Promise<void> => {
|
||||
|
||||
// アクティビティを送信してきたユーザーがまだMisskeyサーバーに登録されていなかったら登録する
|
||||
if (user === null) {
|
||||
user = await resolvePerson(signature.keyId);
|
||||
user = await resolvePerson(signature.keyId) as IRemoteUser;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import * as debug from 'debug';
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import acceptFollow from './follow';
|
||||
import { IAccept } from '../../type';
|
||||
import { IAccept, IFollow } from '../../type';
|
||||
|
||||
const log = debug('misskey:activitypub');
|
||||
|
||||
@ -25,7 +25,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise<void> => {
|
||||
|
||||
switch (object.type) {
|
||||
case 'Follow':
|
||||
acceptFollow(actor, object);
|
||||
acceptFollow(actor, object as IFollow);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -3,7 +3,7 @@ import * as debug from 'debug';
|
||||
import Resolver from '../../resolver';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import announceNote from './note';
|
||||
import { IAnnounce } from '../../type';
|
||||
import { IAnnounce, INote } from '../../type';
|
||||
|
||||
const log = debug('misskey:activitypub');
|
||||
|
||||
@ -25,7 +25,7 @@ export default async (actor: IRemoteUser, activity: IAnnounce): Promise<void> =>
|
||||
|
||||
switch (object.type) {
|
||||
case 'Note':
|
||||
announceNote(resolver, actor, activity, object);
|
||||
announceNote(resolver, actor, activity, object as INote);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2,7 +2,7 @@ import * as debug from 'debug';
|
||||
|
||||
import Resolver from '../../resolver';
|
||||
import post from '../../../../services/note/create';
|
||||
import { IRemoteUser } from '../../../../models/user';
|
||||
import { IRemoteUser, IUser } from '../../../../models/user';
|
||||
import { IAnnounce, INote } from '../../type';
|
||||
import { fetchNote, resolveNote } from '../../models/note';
|
||||
import { resolvePerson } from '../../models/person';
|
||||
@ -36,7 +36,7 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
|
||||
|
||||
//#region Visibility
|
||||
let visibility = 'public';
|
||||
let visibleUsers = [];
|
||||
let visibleUsers: IUser[] = [];
|
||||
if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||
if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
|
||||
visibility = 'home';
|
||||
|