Compare commits

...

177 Commits

Author SHA1 Message Date
f7959c073f 4.3.0 2018-06-18 17:25:45 +09:00
6953970be7 Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-18 17:25:23 +09:00
1496fdaf80 nanka 2018-06-18 17:25:20 +09:00
0fc034b1ac Merge pull request #1743 from Tosuke/patch-1
Replace "size" to "sizes"
2018-06-18 16:48:49 +09:00
c3312c918e replace "size" to "sizes"
やらかした
2018-06-18 16:44:42 +09:00
5a13964ced Fix bug 2018-06-18 16:34:26 +09:00
fe07b1cb7f Merge pull request #1742 from Tosuke/patch-1
Fix path
2018-06-18 15:39:40 +09:00
d805a70508 Fix path 2018-06-18 15:38:58 +09:00
0f0009e0db Clean up 2018-06-18 15:12:29 +09:00
4c4cb2bb17 ✌️ 2018-06-18 15:05:38 +09:00
fe319a529f 4.2.0 2018-06-18 14:54:09 +09:00
91bea1f6c7 ✌️ 2018-06-18 14:52:36 +09:00
01745f7c65 Merge pull request #1706 from syuilo/l10n_master
New Crowdin translations
2018-06-18 14:44:29 +09:00
5d3943ffa8 Merge branch 'master' into l10n_master 2018-06-18 14:43:56 +09:00
e66d7babc5 yatta 2018-06-18 14:28:43 +09:00
80e5645a84 wip 2018-06-18 09:54:53 +09:00
a766faeae9 Merge pull request #1738 from rinsuki/features/ts-noimplicitany-true
[WIP] noImplicitAny: true
2018-06-18 08:42:17 +09:00
4d2d226446 New translations ja.yml (Spanish) 2018-06-18 00:40:45 +09:00
61e83b10c3 New translations ja.yml (Spanish) 2018-06-18 00:30:44 +09:00
ed675f0956 New translations ja.yml (French) 2018-06-17 22:10:47 +09:00
9cce8ab214 New translations ja.yml (French) 2018-06-17 22:00:55 +09:00
daa409cd82 [noImplicitAny: true] src/services/drive 2018-06-17 20:04:19 +09:00
9d65415fdc [noImplicitAny: true] src/services/note 2018-06-17 19:59:02 +09:00
8c40917cc2 [noImplicitAny: true] src/text 2018-06-17 19:55:39 +09:00
871f886702 build:ts success 2018-06-17 19:09:24 +09:00
f19075c50a upgrade font-awesome packages 2018-06-17 17:57:50 +09:00
71da205ab7 fix 2018-06-17 17:45:59 +09:00
a34cc47a11 tsconfig.json noImplicitAny: true 2018-06-17 17:43:54 +09:00
cbddaf1d19 Merge pull request #1735 from rinsuki/fix/minor-fix-201806171721
minor fix
2018-06-17 17:21:50 +09:00
1f1ed2da4c minor fix 2018-06-17 17:21:16 +09:00
8d81bd0dc0 Merge pull request #1732 from rinsuki/fix/1731
Fix #1731
2018-06-17 17:15:31 +09:00
5773a5bfa6 Refactor 2018-06-17 17:15:03 +09:00
7275a48102 Fix #1731 2018-06-17 17:11:05 +09:00
8f84dd610c Use ' 2018-06-17 16:55:55 +09:00
f1f466ed23 Merge pull request #1730 from rinsuki/fix/1729
Fix #1729
2018-06-17 16:51:24 +09:00
0ca5237139 Send servicewroker script 2018-06-17 16:47:37 +09:00
20549bfdf0 Fix #1729 2018-06-17 16:45:01 +09:00
d692bb3c52 Improve user search 2018-06-17 16:40:18 +09:00
44cd1e9223 Merge pull request #1728 from rinsuki/fix/1726
fix #1726
2018-06-17 16:02:21 +09:00
f0fec654ff fix #1726 2018-06-17 15:58:23 +09:00
4e04e5e0c0 Merge pull request #1722 from rinsuki/fix/1700
Fix #1700
2018-06-17 14:30:07 +09:00
4991fb2769 Fix #1700 2018-06-17 14:29:07 +09:00
4d90d554f8 Merge pull request #1721 from rinsuki/activitypub/support-cw
maybe Support CW in ActivityPub
2018-06-17 14:12:50 +09:00
e5468713ac maybe Support CW in ActivityPub 2018-06-17 14:11:31 +09:00
77013f982d Fiox bug 2018-06-17 13:52:52 +09:00
0460cdedd7 inputのsuffixがはみ出す問題を修正 2018-06-17 11:42:23 +09:00
73f5bf69e8 4.1.0 2018-06-17 11:33:24 +09:00
750c0d7df2 Use wildcard 2018-06-17 11:27:20 +09:00
2fcebdd281 cors 2018-06-17 11:23:18 +09:00
e4e65a4cd5 Update CHANGELOG.md 2018-06-17 08:23:46 +09:00
e010ecb03f New translations ja.yml (Portuguese) 2018-06-17 08:21:10 +09:00
fc74db668d New translations ja.yml (Korean) 2018-06-17 08:21:08 +09:00
1bac3418b4 New translations ja.yml (Polish) 2018-06-17 08:21:06 +09:00
53df8c48b7 New translations ja.yml (Chinese Simplified) 2018-06-17 08:21:04 +09:00
92702fe47e New translations ja.yml (Italian) 2018-06-17 08:21:02 +09:00
017c4c12cd New translations ja.yml (Russian) 2018-06-17 08:21:01 +09:00
830d246ba4 New translations ja.yml (English) 2018-06-17 08:20:59 +09:00
6b33afa916 New translations ja.yml (Spanish) 2018-06-17 08:20:57 +09:00
69a3efd534 Update CHANGELOG.md 2018-06-17 08:20:56 +09:00
2d0adb8f4c New translations ja.yml (German) 2018-06-17 08:20:55 +09:00
da9d8cb138 New translations ja.yml (French) 2018-06-17 08:20:53 +09:00
2acaca8582 Update CHANGELOG.md 2018-06-17 08:20:22 +09:00
11cf82c6a4 4.0.0 2018-06-17 08:15:57 +09:00
1ef66c962a reversi 💮 💯 2018-06-17 08:10:54 +09:00
03f20599ba Add missing semicolon 2018-06-17 07:39:51 +09:00
d150b10b3e Update default home widgets 2018-06-17 06:57:50 +09:00
c4f323aae3 Merge pull request #1719 from Tosuke/master
Improve web app manifest(#1716)
2018-06-16 19:53:08 +09:00
8297f8ccd0 Merge branch 'master' of github.com:syuilo/misskey 2018-06-16 19:46:51 +09:00
f336241576 improve web app manifest 2018-06-16 19:45:49 +09:00
f6d9a7e7c3 3.1.1 2018-06-16 19:45:47 +09:00
80d1ee7543 Fix bug 2018-06-16 19:45:29 +09:00
e55a254353 3.1.0 2018-06-16 18:43:25 +09:00
555a0f276c MisskeyShare 2018-06-16 18:42:49 +09:00
792632d726 3.0.1 2018-06-16 15:23:44 +09:00
9cac293efc #1708 2018-06-16 15:23:03 +09:00
cd8bfca29c npm install --only=dev するのが既存のドキュメントと互換性が無いため戻す 2018-06-16 13:10:17 +09:00
b5b437b878 ✌️ 2018-06-16 12:43:58 +09:00
cc2947063a add lock file 2018-06-16 12:42:59 +09:00
2864a9027f save-exact=true 2018-06-16 12:02:38 +09:00
e11f547308 #1715 2018-06-16 10:40:53 +09:00
cdce7aa5e2 New translations ja.yml (Spanish) 2018-06-16 09:51:05 +09:00
82cea185b2 New translations ja.yml (Spanish) 2018-06-16 09:30:38 +09:00
f92a4bb195 New translations ja.yml (Spanish) 2018-06-16 09:20:56 +09:00
9f4f88df9c New translations ja.yml (Spanish) 2018-06-16 09:10:48 +09:00
e69803cbd1 New translations ja.yml (Spanish) 2018-06-16 09:00:56 +09:00
f164661ef2 2.42.0 2018-06-16 07:40:39 +09:00
c9d993b838 ✌️ 2018-06-16 07:40:07 +09:00
65f35dc9f4 ✌️ 2018-06-16 07:31:35 +09:00
b600d462c1 ✌️ 2018-06-16 07:13:45 +09:00
fa5a82c9ab ✌️ 2018-06-16 07:06:58 +09:00
a9885be09e New translations ja.yml (Spanish) 2018-06-16 05:40:57 +09:00
7b011f4a91 New translations ja.yml (Spanish) 2018-06-16 05:30:45 +09:00
41c404abe6 New translations ja.yml (Spanish) 2018-06-16 05:20:43 +09:00
2089a761cf New translations ja.yml (Spanish) 2018-06-16 05:00:48 +09:00
0ee2df010d New translations ja.yml (Spanish) 2018-06-16 04:50:43 +09:00
466844c016 New translations ja.yml (Spanish) 2018-06-16 04:40:58 +09:00
bbf9a08649 New translations ja.yml (Polish) 2018-06-15 20:11:35 +09:00
c985c66652 New translations ja.yml (Portuguese) 2018-06-15 20:02:34 +09:00
f9dc96320e New translations ja.yml (Korean) 2018-06-15 20:02:32 +09:00
42552789fe New translations ja.yml (Polish) 2018-06-15 20:02:30 +09:00
1a2ffeb0b5 New translations ja.yml (Chinese Simplified) 2018-06-15 20:02:28 +09:00
4f75493249 New translations ja.yml (Italian) 2018-06-15 20:02:25 +09:00
42193695fb New translations ja.yml (Russian) 2018-06-15 20:02:23 +09:00
02af0de21e New translations ja.yml (English) 2018-06-15 20:02:21 +09:00
5f8e10e524 New translations ja.yml (Spanish) 2018-06-15 20:02:19 +09:00
cee93d746c New translations ja.yml (German) 2018-06-15 20:02:17 +09:00
08704a383f New translations ja.yml (French) 2018-06-15 20:02:14 +09:00
7c596be638 2.41.1 2018-06-15 19:58:15 +09:00
07265f594b ✌️ 2018-06-15 19:58:04 +09:00
392cb1ba89 2.41.0 2018-06-15 19:57:13 +09:00
e6f33e997f 🎨 2018-06-15 19:56:18 +09:00
a44387f250 ✌️ 2018-06-15 13:58:09 +09:00
b1b1b7592b ✌️ 2018-06-15 13:08:56 +09:00
ca668898f4 2.40.1 2018-06-15 09:57:06 +09:00
fcd437c89f Fix bug 2018-06-15 09:56:59 +09:00
7f7d7edc7f 2.40.0 2018-06-15 09:53:53 +09:00
bd827f946a ドライブのファイルの削除を実装 2018-06-15 09:53:30 +09:00
ad8aa1c179 Fix 2018-06-15 09:33:25 +09:00
3ebaf83ce0 2.39.0 2018-06-15 08:00:14 +09:00
39b1978ff3 Merge pull request #1713 from syuilo/without-vue-material
Without vue material
2018-06-15 07:58:58 +09:00
bddff17e5e wip 2018-06-15 07:58:27 +09:00
0ac9120064 wip 2018-06-15 07:56:56 +09:00
d90f75425f wip 2018-06-14 21:38:39 +09:00
dec7d537dc wip 2018-06-14 20:23:50 +09:00
11e95ea092 wip 2018-06-14 18:57:54 +09:00
c5e9b69eb3 wip 2018-06-14 18:53:02 +09:00
120c11b181 wip 2018-06-14 16:48:49 +09:00
a1ae832129 wip 2018-06-14 14:52:37 +09:00
3a4833818f wip 2018-06-14 09:51:55 +09:00
8814fc9c9c wip 2018-06-14 07:22:50 +09:00
e6e02ece89 wip 2018-06-14 06:29:01 +09:00
9059c149dd 2.38.3 2018-06-13 05:40:27 +09:00
7d8e70b2ac Fix bug 2018-06-13 05:40:12 +09:00
89105f5641 2.38.2 2018-06-13 05:25:27 +09:00
1813d17b4c Fix bug 2018-06-13 05:24:44 +09:00
ce27b36fd0 Fix bug 2018-06-13 05:21:55 +09:00
e635a87628 2.38.1 2018-06-13 05:16:53 +09:00
80c52433cc Fix bug 2018-06-13 05:15:26 +09:00
1472f0b141 Fix #1712 2018-06-13 05:11:55 +09:00
4d914f5c0a 2.38.0 2018-06-12 19:07:36 +09:00
0318f7344f Fix bug 2018-06-12 19:05:40 +09:00
413fbb3d0c モバイルでもハッシュタグを検索できるように 2018-06-12 19:03:57 +09:00
8bc47baf4f #1710 2018-06-12 18:54:36 +09:00
e3f6d42a47 Fix bug 2018-06-12 11:38:44 +09:00
8230935fd3 🎨 2018-06-12 11:27:35 +09:00
f968d05ea0 2.37.7 2018-06-12 09:10:52 +09:00
d6e5dc2167 Fix bugs 2018-06-12 09:10:34 +09:00
460147fea2 2.37.6 2018-06-12 08:59:36 +09:00
cea44834bb Improve usability 2018-06-12 08:58:50 +09:00
1af50fd7b8 冗長なハッシュタグの表示を無くした 2018-06-12 08:43:48 +09:00
b18013025f 🎨 2018-06-12 08:09:27 +09:00
acdf7c244f New translations ja.yml (Polish) 2018-06-12 03:15:56 +09:00
399eb60809 2.37.5 2018-06-12 02:47:17 +09:00
ed67e3506b ✌️ 2018-06-12 02:46:54 +09:00
a72b6745aa New translations ja.yml (Portuguese) 2018-06-12 02:33:16 +09:00
24086e9023 New translations ja.yml (Korean) 2018-06-12 02:33:14 +09:00
c3d4b5ad38 New translations ja.yml (Polish) 2018-06-12 02:33:12 +09:00
cc618a83e5 New translations ja.yml (Chinese Simplified) 2018-06-12 02:33:08 +09:00
9eaa0b27db New translations ja.yml (Italian) 2018-06-12 02:33:06 +09:00
a8835a679e New translations ja.yml (Russian) 2018-06-12 02:33:00 +09:00
656bc6df84 New translations ja.yml (English) 2018-06-12 02:32:57 +09:00
019aaf7d82 New translations ja.yml (Spanish) 2018-06-12 02:32:55 +09:00
76bafbf398 New translations ja.yml (German) 2018-06-12 02:32:52 +09:00
030bcb99b1 New translations ja.yml (French) 2018-06-12 02:32:47 +09:00
d8ff37fc45 ✌️ 2018-06-12 02:28:28 +09:00
2fcc3bb1ea 2.37.4 2018-06-12 02:19:00 +09:00
2e680c3d1e Fix bug 2018-06-12 02:18:29 +09:00
af0a0ef41b Merge branch 'master' of https://github.com/syuilo/misskey 2018-06-12 02:03:26 +09:00
bbfccb0bbf 🎨 2018-06-12 02:03:18 +09:00
c89eb5d69f Merge pull request #1705 from syuilo/l10n_master
New Crowdin translations
2018-06-12 02:03:06 +09:00
ebde84214e Improve hashtag trend detection 2018-06-12 02:00:05 +09:00
03fbae7b6d 変数調整 2018-06-12 01:51:51 +09:00
f90e9596d4 Fix bug 2018-06-12 01:48:29 +09:00
944f9524e2 Fix bug 2018-06-12 01:45:58 +09:00
c61050244e 変数調整 2018-06-12 01:43:56 +09:00
90337adbbc Improve hashtag trend detection 2018-06-12 01:41:17 +09:00
7b67e41c5b New translations ja.yml (Polish) 2018-06-11 19:22:16 +09:00
314 changed files with 15611 additions and 4061 deletions

View File

@ -1,3 +1,9 @@
# インスタンス名
name:
# インスタンスの紹介
description:
# サーバーのメンテナ情報 # サーバーのメンテナ情報
maintainer: maintainer:
# メンテナの名前 # メンテナの名前

1
.gitattributes vendored
View File

@ -1,3 +1,4 @@
*.svg -diff -text *.svg -diff -text
*.psd -diff -text *.psd -diff -text
*.ai -diff -text *.ai -diff -text
yarn.lock -diff -text

1
.npmrc
View File

@ -1 +1,2 @@
package-lock = false package-lock = false
save-exact=true

28
CHANGELOG.md Normal file
View File

@ -0,0 +1,28 @@
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
-----
### Migration
起動する前に、`node cli/recount-stats`してください。
Please run `node cli/recount-stats` before launch.

BIN
assets/icons/128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/icons/16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

BIN
assets/icons/192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
assets/icons/256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
assets/icons/32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

BIN
assets/icons/64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

42
cli/recount-stats.js Normal file
View File

@ -0,0 +1,42 @@
const { default: Note } = require('../built/models/note');
const { default: Meta } = require('../built/models/meta');
const { default: User } = require('../built/models/user');
async function main() {
const meta = await Meta.findOne({});
const notesCount = await Note.count();
const usersCount = await User.count();
const originalNotesCount = await Note.count({
'_user.host': null
});
const originalUsersCount = await User.count({
host: null
});
const stats = {
notesCount,
usersCount,
originalNotesCount,
originalUsersCount
};
if (meta) {
await Meta.update({}, {
$set: {
stats
}
});
} else {
await Meta.insert({
stats
});
}
}
main().then(() => {
console.log('done');
}).catch(console.error);

View File

@ -8,12 +8,12 @@ import * as gutil from 'gulp-util';
import * as ts from 'gulp-typescript'; import * as ts from 'gulp-typescript';
const sourcemaps = require('gulp-sourcemaps'); const sourcemaps = require('gulp-sourcemaps');
import tslint from 'gulp-tslint'; import tslint from 'gulp-tslint';
import cssnano = require('gulp-cssnano'); const cssnano = require('gulp-cssnano');
import * as uglifyComposer from 'gulp-uglify/composer'; import * as uglifyComposer from 'gulp-uglify/composer';
import pug = require('gulp-pug'); import pug = require('gulp-pug');
import * as rimraf from 'rimraf'; import * as rimraf from 'rimraf';
import chalk from 'chalk'; import chalk from 'chalk';
import imagemin = require('gulp-imagemin'); const imagemin = require('gulp-imagemin');
import * as rename from 'gulp-rename'; import * as rename from 'gulp-rename';
import * as mocha from 'gulp-mocha'; import * as mocha from 'gulp-mocha';
import * as replace from 'gulp-replace'; import * as replace from 'gulp-replace';

View File

@ -3,7 +3,7 @@ meta:
lang: "Deutsch" lang: "Deutsch"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Serverinformationen" title: "Serverinformationen"
toggle: "Sicht umschalten" toggle: "Sicht umschalten"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "Mehr" refresh: "Mehr"
close: "Schließen" close: "Schließen"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "Othello" game: "Reversi"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Verbunden" done: "Verbunden"
add-widget: "Widget hinzufügen:" add-widget: "Widget hinzufügen:"

View File

@ -3,7 +3,7 @@ meta:
lang: "English" lang: "English"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "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?" 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: time:
@ -47,6 +47,7 @@ common:
ok: "OK" ok: "OK"
update-available: "A new version of Misskey is now available({newer}, the current version is {current}). Reload the page to apply updates." 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." my-token-regenerated: "Your token has been renewed so you will be signed out."
i-like-sushi: "I like sushi rather than pudding"
widgets: widgets:
analog-clock: "Analog clock" analog-clock: "Analog clock"
profile: "Profile" profile: "Profile"
@ -228,6 +229,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "Hashtags" title: "Hashtags"
count: "{} users mentioned" count: "{} users mentioned"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Server info" title: "Server info"
toggle: "Toggle views" toggle: "Toggle views"
@ -333,7 +335,7 @@ desktop/views/components/friends-maker.vue:
refresh: "More" refresh: "More"
close: "Close" close: "Close"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "Othello" game: "Reversi"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Submit" done: "Submit"
add-widget: "Add widget:" add-widget: "Add widget:"
@ -549,7 +551,7 @@ desktop/views/components/ui.header.nav.vue:
home: "Home" home: "Home"
deck: "Deck" deck: "Deck"
messaging: "Messages" messaging: "Messages"
game: "Play Othello" game: "Play Reversi"
desktop/views/components/ui.header.notifications.vue: desktop/views/components/ui.header.notifications.vue:
title: "Notifications" title: "Notifications"
desktop/views/components/ui.header.post.vue: desktop/views/components/ui.header.post.vue:

View File

@ -1,223 +1,223 @@
--- ---
meta: meta:
lang: "日本語" lang: "Español"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "Una ⭐️ del fediverso"
about-title: "A ⭐ of fediverse." about-title: "Una ⭐️ del fediverso"
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" 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: time:
unknown: "なぞのじかん" unknown: "Desconocido"
future: "未来" future: "Futuro"
just_now: "たった今" just_now: "Ahora mismo"
seconds_ago: "{}秒前" seconds_ago: "Hace {}"
minutes_ago: "{}分前" minutes_ago: "Hace {} minuto(s)"
hours_ago: "{}時間前" hours_ago: "Hace {} hora(s)"
days_ago: "{}日前" days_ago: "Hace {} dia(s)"
weeks_ago: "{}週間前" weeks_ago: "Hace {} semana(s)"
months_ago: "{}ヶ月前" months_ago: "Hace {} mes(es)"
years_ago: "{}年前" years_ago: "Hace {} año(s)"
weekday-short: weekday-short:
sunday: "" sunday: "domingo"
monday: "" monday: "lunes"
tuesday: "" tuesday: "martes"
wednesday: "" wednesday: "miércoles"
thursday: "" thursday: "jueves"
friday: "" friday: "viernes"
saturday: "" saturday: "sábado"
reactions: reactions:
like: "いいね" like: "me gusta"
love: "しゅき" love: "amor"
laugh: "" laugh: "risa"
hmm: "ふぅ~む" hmm: "hmm"
surprise: "わお" surprise: "sorpresa"
congrats: "おめでとう" congrats: "felicidades"
angry: "おこ" angry: "enfadado"
confused: "こまこまのこまり" confused: "confundido"
pudding: "Pudding" pudding: "Chafado"
note-placeholders: note-placeholders:
a: "今どうしてる?" a: "¿Qué haces?"
b: "何かありましたか?" b: "¿Qué está pasando?"
c: "何をお考えですか?" c: "¿Qué te pasa por la cabeza?"
d: "言いたいことは?" d: "¿Quieres decir algo?"
e: "ここに書いてください" e: "¡Escribe aquí!"
f: "あなたが書くのを待っています..." f: "Esperando a que escribas algo..."
delete: "削除" delete: "eliminar"
loading: "読み込み中" loading: "cargando"
ok: "わかった" ok: "OK"
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。" 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: "あなたのトークンが更新されたのでサインアウトします。" my-token-regenerated: "Tu token se ha regenerado vas a ser desconectado."
widgets: widgets:
analog-clock: "アナログ時計" analog-clock: "Reloj analógico"
profile: "プロフィール" profile: "Perfil"
calendar: "カレンダー" calendar: "Calendario"
timemachine: "カレンダー(タイムマシン)" timemachine: "Calendario (máquina del tiempo)"
activity: "アクティビティ" activity: "Actividad"
rss: "RSSリーダー" rss: "Lector RSS"
memo: "付箋" memo: "Notas adhesivas"
trends: "トレンド" trends: "Tendencias"
photo-stream: "フォトストリーム" photo-stream: "Secuencia de fotos"
posts-monitor: "投稿チャート" posts-monitor: "Gráfico de publicaciones"
slideshow: "スライドショー" slideshow: "Diapositivas"
version: "バージョン" version: "Versión"
broadcast: "ブロードキャスト" broadcast: "Transmisión"
notifications: "通知" notifications: "Notificaciones"
users: "おすすめユーザー" users: "Usuarios destacados"
polls: "アンケート" polls: "Encuestas"
post-form: "投稿フォーム" post-form: "Formulario"
messaging: "メッセージ" messaging: "Mensajes"
server: "サーバー情報" server: "Información del servidor"
donation: "寄付のお願い" donation: "Donaciones"
nav: "ナビゲーション" nav: "Navegación"
tips: "ヒント" tips: "Consejos"
hashtags: "ハッシュタグ" hashtags: "Etiquetas"
deck: deck:
widgets: "ウィジェット" widgets: "Accesorios"
home: "ホーム" home: "Inicio"
local: "ローカル" local: "Local"
global: "グローバル" global: "Global"
notifications: "通知" notifications: "Notificaciones"
list: "リスト" list: "Listado"
swap-left: "左に移動" swap-left: "Desplazar a la izq."
swap-right: "右に移動" swap-right: "Desplazar a la dcha."
swap-up: "上に移動" swap-up: "Desplazar arriba"
swap-down: "下に移動" swap-down: "Desplazar abajo"
remove: "カラムを削除" remove: "Borrar"
add-column: "カラムを追加" add-column: "Añadir columna"
rename: "名前を変更" rename: "Renombrar"
stack-left: "左に重ねる" stack-left: "A la izqda."
pop-right: "右に出す" pop-right: "A la dcha."
common/views/components/connect-failed.vue: common/views/components/connect-failed.vue:
title: "サーバーに接続できません" title: "Imposible conectar al servidor"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから{再度お試し}ください。" 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: "いつもMisskeyをご利用いただきありがとうございます。" thanks: "Gracias por usar Misskey."
troubleshoot: "トラブルシュート" troubleshoot: "Problemas más frecuentes"
common/views/components/connect-failed.troubleshooter.vue: common/views/components/connect-failed.troubleshooter.vue:
title: "トラブルシューティング" title: "Resolución de problemas"
network: "ネットワーク接続" network: "Conexión de red"
checking-network: "ネットワーク接続を確認中" checking-network: "Verificar la conexión a la red"
internet: "インターネット接続" internet: "Conexión a Internet"
checking-internet: "インターネット接続を確認中" checking-internet: "Comprobando la conexión a Internet"
server: "サーバー接続" server: "Conexión al servidor"
checking-server: "サーバー接続を確認中" checking-server: "Probando la conexión al servidor"
finding: "問題を調べています" finding: "Buscando cualquier problema"
no-network: "ネットワークに接続されていません" no-network: "Sin conexión"
no-network-desc: "お使いのPCのネットワーク接続が正常か確認してください。" no-network-desc: "Por favor, asegurate que estás conectado a una red"
no-internet: "インターネットに接続されていません" no-internet: "Sin conexión a Internet"
no-internet-desc: "ネットワークには接続されていますが、インターネットには接続されていないようです。お使いのPCのインターネット接続が正常か確認してください。" no-internet-desc: "Por favor, asegurate de estar conectado a Internet."
no-server: "Misskeyのサーバーに接続できません" no-server: "Imposible conectarse al servidor de Misskey"
no-server-desc: "お使いのPCのインターネット接続は正常ですが、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: "Misskeyのサーバーに接続できました" success: "Conectado al servidor de Misskey de manera correcta"
success-desc: "正常に接続できるようです。ページを再度読み込みしてください。" success-desc: "Parece que la conexión ha sido posible. Por favor refresca la página."
flush: "キャッシュの削除" flush: "Limpiar la memoria caché"
set-version: "バージョン指定" set-version: "Escoge la versión"
common/views/components/messaging.vue: common/views/components/messaging.vue:
search-user: "ユーザーを探す" search-user: "Encuentra un usuario"
you: "あなた" you: "Tu"
no-history: "履歴はありません" no-history: "Sin historial"
common/views/components/messaging-room.vue: common/views/components/messaging-room.vue:
empty: "このユーザーと話したことはありません" empty: "Sin conversaciones"
more: "もっと読む" more: "Leer más"
no-history: "これより過去の履歴はありません" no-history: "El historial se ha acabado"
resize-form: "ドラッグしてフォームの広さを調整" resize-form: "Arrastra para redimensionar"
new-message: "新しいメッセージがあります" new-message: "Nuevo mensaje"
common/views/components/messaging-room.form.vue: common/views/components/messaging-room.form.vue:
input-message-here: "ここにメッセージを入力" input-message-here: "Escribe el mensaje aquí"
send: "送信" send: "Enviar"
attach-from-local: "PCからファイルを添付する" attach-from-local: "Adjunta ficheros desde tu PC"
attach-from-drive: "ドライブからファイルを添付する" attach-from-drive: "Adjunta ficheros desde tu disco"
common/views/components/messaging-room.message.vue: common/views/components/messaging-room.message.vue:
is-read: "既読" is-read: "Leer"
deleted: "このメッセージは削除されました" deleted: "El mensaje se ha borrado"
common/views/components/nav.vue: common/views/components/nav.vue:
about: "Misskeyについて" about: "Sobre"
stats: "統計" stats: "Estadísticas"
status: "ステータス" status: "Estado"
wiki: "Wiki" wiki: "Wiki"
donors: "ドナー" donors: "Donantes"
repository: "リポジトリ" repository: "Repositorio"
develop: "開発者" develop: "Desarrolladores"
feedback: "フィードバック" feedback: "Opiniones"
common/views/components/note-menu.vue: common/views/components/note-menu.vue:
favorite: "お気に入り" favorite: "Me gusta esta nota"
pin: "ピン留め" pin: "Fijar en el perfil"
delete: "削除" delete: "Borrar"
delete-confirm: "この投稿を削除しますか?" delete-confirm: "¿Seguro que quieres borrar la publicación?"
remote: "投稿元で見る" remote: "Ver el original"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "{}」に投票する" vote-to: "'{}' para votar"
vote-count: "{}" vote-count: "{} votos"
total-users: "{}人が投票" total-users: "{} usuario(s) que ha(n) votado"
vote: "投票する" vote: "Vota"
show-result: "結果を見る" show-result: "Mostrar resultados"
voted: "投票済み" voted: "Votado"
common/views/components/poll-editor.vue: common/views/components/poll-editor.vue:
no-only-one-choice: "アンケートには、選択肢が最低2つ必要です" no-only-one-choice: "Selecciona dos o más opciones."
choice-n: "選択肢{}" choice-n: "{} opcion(es)"
remove: "この選択肢を削除" remove: "Borra la opción"
add: "+選択肢を追加" add: "+ Añade una opción"
destroy: "アンケートを破棄" destroy: "Cancelar la encuesta"
common/views/components/reaction-picker.vue: common/views/components/reaction-picker.vue:
choose-reaction: "リアクションを選択" choose-reaction: "Escoge una reacción"
common/views/components/signin.vue: common/views/components/signin.vue:
username: "ユーザー名" username: "Usuario"
password: "パスワード" password: "Contraseña"
token: "トークン" token: "Identificador"
signing-in: "やってます..." signing-in: "Entrando..."
signin: "サインイン" signin: "Entra"
common/views/components/signup.vue: common/views/components/signup.vue:
username: "ユーザー名" username: "Usuario"
checking: "確認しています..." checking: "Comprobando..."
available: "利用できます" available: "Disponible"
unavailable: "既に利用されています" unavailable: "Utilizado"
error: "通信エラー" error: "Error de conexión"
invalid-format: "a~z、A~Z、0~9、_が使えます" invalid-format: "utiliza letras, números y/o -."
too-short: "1文字以上でお願いします" too-short: "¡Mínimo tienes que introducir un caracter!"
too-long: "20文字以内でお願いします" too-long: "No puedes usar más de 20 caracteres."
password: "パスワード" password: "Contraseña"
password-placeholder: "8文字以上を推奨します" password-placeholder: "Te recomendamos más de 8 caracteres"
weak-password: "弱いパスワード" weak-password: "Contraseña débil"
normal-password: "まあまあのパスワード" normal-password: "No está mal"
strong-password: "強いパスワード" strong-password: "Muy buena contraseña"
retype: "再入力" retype: "Inténtalo otra vez"
retype-placeholder: "確認のため再入力してください" retype-placeholder: "Confirma la contraseña"
password-matched: "確認されました" password-matched: "OK"
password-not-matched: "一致していません" password-not-matched: "Las contraseñas no son las mismas"
recaptcha: "認証" recaptcha: "Verificar"
create: "アカウント作成" create: "Crea una cuenta"
some-error: "何らかの原因によりアカウントの作成に失敗しました。再度お試しください。" 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: common/views/components/special-message.vue:
new-year: "Happy New Year!" new-year: "¡Feliz Año Nuevo!"
christmas: "Merry Christmas!" christmas: "¡Feliz Navidad!"
common/views/components/stream-indicator.vue: common/views/components/stream-indicator.vue:
connecting: "接続中" connecting: "Conectando"
reconnecting: "再接続中" reconnecting: "Reconectando"
connected: "接続完了" connected: "Conectado"
common/views/components/twitter-setting.vue: common/views/components/twitter-setting.vue:
description: "お使いのTwitterアカウントをお使いのMisskeyアカウントに接続しておくと、プロフィールでTwitterアカウント情報が表示されるようになったり、Twitterを用いた便利なサインインを利用できるようになります。" 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: "次のTwitterアカウントに接続されています" connected-to: "Estas comectado con las siguientes cuentas de Twitter"
detail: "詳細..." detail: "Detalles..."
reconnect: "再接続する" reconnect: "Conectar de nuevo"
connect: "Twitterと接続する" connect: "Conectate usando Twitter"
disconnect: "切断する" disconnect: "Desconectado"
common/views/components/uploader.vue: common/views/components/uploader.vue:
waiting: "待機中" waiting: "Un momento"
common/views/components/visibility-chooser.vue: common/views/components/visibility-chooser.vue:
public: "公開" public: "Público"
home: "ホーム" home: "Inicio"
home-desc: "ホームタイムラインにのみ公開" home-desc: "Publica solo en la página de inicio"
followers: "フォロワー" followers: "Seguidores"
followers-desc: "自分のフォロワーにのみ公開" followers-desc: "Piblica solo para tus seguidores"
specified: "ダイレクト" specified: "Directo"
specified-desc: "指定したユーザーにのみ公開" specified-desc: "Publica solo para los seguidores que quieras"
private: "非公開" private: "Privada"
common/views/widgets/broadcast.vue: common/views/widgets/broadcast.vue:
fetching: "確認中" fetching: "Recuperando"
no-broadcasts: "お知らせはありません" no-broadcasts: "Sin emisión"
have-a-nice-day: "良い一日を!" have-a-nice-day: "¡Buenos dias!"
next: "" next: "Siguiente"
common/views/widgets/donation.vue: common/views/widgets/donation.vue:
title: "寄付のお願い" title: "Donaciones"
text: "Misskeyの運営にはドメイン、サーバー等のコストが掛かります。Misskeyは広告を掲載したりしないため、収入を皆様からの寄付に頼っています。もしご興味があれば、{}までご連絡ください。ご協力ありがとうございます。" text: "Misskeyの運営にはドメイン、サーバー等のコストが掛かります。Misskeyは広告を掲載したりしないため、収入を皆様からの寄付に頼っています。もしご興味があれば、{}までご連絡ください。ご協力ありがとうございます。"
common/views/widgets/photo-stream.vue: common/views/widgets/photo-stream.vue:
title: "フォトストリーム" title: "フォトストリーム"
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -257,66 +258,66 @@ desktop/views/components/choose-file-from-drive-window.vue:
desktop/views/components/choose-folder-from-drive-window.vue: desktop/views/components/choose-folder-from-drive-window.vue:
cancel: "キャンセル" cancel: "キャンセル"
ok: "決定" ok: "決定"
choose-prompt: "フォルダを選択" choose-prompt: "Escoge una Carpeta"
desktop/views/components/crop-window.vue: desktop/views/components/crop-window.vue:
skip: "クロップをスキップ" skip: "クロップをスキップ"
cancel: "キャンセル" cancel: "Cancelar"
ok: "決定" ok: "OK"
desktop/views/components/drive-window.vue: desktop/views/components/drive-window.vue:
used: "使用中" used: "usado"
drive: "ドライブ" drive: "Disco"
desktop/views/components/drive.file.vue: desktop/views/components/drive.file.vue:
avatar: "アイコン" avatar: "Avatar"
banner: "バナー" banner: "Banner"
contextmenu: contextmenu:
rename: "名前を変更" rename: "Renombrar"
copy-url: "URLをコピー" copy-url: "Copia la URL"
download: "ダウンロード" download: "Descargar"
else-files: "その他..." else-files: "Otros"
set-as-avatar: "アイコンに設定" set-as-avatar: "Utilizar como avatar"
set-as-banner: "バナーに設定" set-as-banner: "Utilizar como banner"
open-in-app: "アプリで開く" open-in-app: "Abrir en la aplicación"
add-app: "アプリを追加" add-app: "Añadir aplicación"
rename-file: "ファイル名の変更" rename-file: "Renombra el fichero"
input-new-file-name: "新しいファイル名を入力してください" input-new-file-name: "Escribe el nombre nuevo"
copied: "コピー完了" copied: "Copiado"
copied-url-to-clipboard: "URLをクリップボードにコピーしました" copied-url-to-clipboard: "URL copiada al porta papeles"
desktop/views/components/drive.folder.vue: desktop/views/components/drive.folder.vue:
unable-to-process: "操作を完了できません" unable-to-process: "La operación no se puede llevar a cabo"
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。" circular-reference-detected: "La carpeta de destino es una sub-carpeta de la carpeta que quieres mover."
unhandled-error: "不明なエラー" unhandled-error: "Error desconocido"
contextmenu: contextmenu:
move-to-this-folder: "このフォルダへ移動" move-to-this-folder: "Mover a esta carpeta"
show-in-new-window: "新しいウィンドウで表示" show-in-new-window: "Abrir en una ventana nueva"
rename: "名前を変更" rename: "Renombrar"
rename-folder: "フォルダ名の変更" rename-folder: "Renombrar carpeta"
input-new-folder-name: "新しいフォルダ名を入力してください" input-new-folder-name: "Escribe el nombre nuevo"
desktop/views/components/drive.nav-folder.vue: desktop/views/components/drive.nav-folder.vue:
drive: "ドライブ" drive: "Disco"
desktop/views/components/drive.vue: desktop/views/components/drive.vue:
search: "検索" search: "Buscar"
load-more: "もっと読み込む" load-more: "Cargar más"
empty-draghover: "ドロップですか?いいですよ、ボクはカワイイですからね" empty-draghover: "¡Saluda!"
empty-drive: "ドライブには何もありません。" empty-drive: "Tu disco está vacio"
empty-drive-description: "右クリックして「ファイルをアップロード」を選んだり、ファイルをドラッグ&ドロップすることでもアップロードできます。" 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: "このフォルダーは空です" empty-folder: "La carpeta está vacia"
unable-to-process: "操作を完了できません" unable-to-process: "La operación no se puede llevar a cabo."
circular-reference-detected: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。" circular-reference-detected: "La carpeta de destino es una sub-carpeta de la carpeta que quieres mover."
unhandled-error: "不明なエラー" unhandled-error: "Errer desconocido"
url-upload: "URLアップロード" url-upload: "Subir desde una URL"
url-of-file: "アップロードしたいファイルのURL" url-of-file: "URL del fichero que quieres subir"
url-upload-requested: "アップロードをリクエストしました" url-upload-requested: "Subida solicitada"
may-take-time: "アップロードが完了するまで時間がかかる場合があります。" may-take-time: "Subir el fichero puede tardar un tiempo."
create-folder: "フォルダー作成" create-folder: "Crear una carpeta"
folder-name: "フォルダー名" folder-name: "Nombre de la carpeta"
contextmenu: contextmenu:
create-folder: "フォルダーを作成" create-folder: "Crear una carpeta"
upload: "ファイルをアップロード" upload: "Subir fichero"
url-upload: "URLからアップロード" url-upload: "Subir desde una URL"
desktop/views/components/follow-button.vue: desktop/views/components/follow-button.vue:
following: "フォロー中" following: "Siguiendo"
follow: "フォロー" follow: "Sigue"
request-pending: "フォロー許可待ち" request-pending: "Pendiente de aprobación"
follow-request: "フォロー申請" follow-request: "フォロー申請"
desktop/views/components/followers-window.vue: desktop/views/components/followers-window.vue:
followers: "{} のフォロワー" followers: "{} のフォロワー"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"
@ -381,55 +382,55 @@ desktop/views/components/post-form.vue:
renote-failed: "Renoteに失敗しました" renote-failed: "Renoteに失敗しました"
posting: "投稿中" posting: "投稿中"
attach-media-from-local: "PCからメディアを添付" attach-media-from-local: "PCからメディアを添付"
attach-media-from-drive: "ドライブからメディアを添付" attach-media-from-drive: "Adjunta multimedia desde tu Disco"
attach-cancel: "添付取り消し" attach-cancel: "Quitar el archivo adjunto"
insert-a-kao: "v(‘ω’)v" insert-a-kao: "v(‘ω’)v"
create-poll: "アンケートを作成" create-poll: "Crea una encuesta"
text-remain: "残り{}文字" text-remain: "quedan {} caracteres"
desktop/views/components/post-form-window.vue: desktop/views/components/post-form-window.vue:
note: "新規投稿" note: "Nota nueva"
reply: "返信" reply: "Responder"
attaches: "添付: {}メディア" attaches: "{} archivo(s) multimedia adjuntados"
uploading-media: "{}個のメディアをアップロード中" uploading-media: "Subiendo {} archivo(s) multimedia"
desktop/views/components/progress-dialog.vue: desktop/views/components/progress-dialog.vue:
waiting: "待機中" waiting: "Un momento"
desktop/views/components/renote-form.vue: desktop/views/components/renote-form.vue:
quote: "引用する..." quote: "Cita..."
cancel: "キャンセル" cancel: "Cancelar"
renote: "Renote" renote: "Volver a publicar"
reposting: "しています..." reposting: "Publicando de nuevo..."
success: "Renoteしました" success: "¡Publicado!"
failure: "Renoteに失敗しました" failure: "La publicación ha fallado"
desktop/views/components/renote-form-window.vue: desktop/views/components/renote-form-window.vue:
title: "この投稿をRenoteしますか" title: "¿Seguro qué quieres volver a publicarlo?"
desktop/views/components/settings-window.vue: desktop/views/components/settings-window.vue:
settings: "設定" settings: "Configuración"
desktop/views/components/settings.vue: desktop/views/components/settings.vue:
profile: "プロフィール" profile: "Perfil"
notification: "通知" notification: "Notificación"
apps: "アプリ" apps: "Aplicaciones"
mute: "ミュート" mute: "Silenciar"
drive: "ドライブ" drive: "Disco"
security: "セキュリティ" security: "Seguridad"
signin: "サインイン履歴" signin: "Historial de inicios de sesión"
password: "パスワード" password: "Contraseña"
2fa: "二段階認証" 2fa: "Autenticación de Doble-Factor"
other: "その他" other: "Otros"
license: "ライセンス" license: "Licencia"
behaviour: "動作" behaviour: "Acciones"
fetch-on-scroll: "スクロールで自動読み込み" fetch-on-scroll: "Desplazamiento infinito"
fetch-on-scroll-desc: "ページを下までスクロールしたときに自動で追加のコンテンツを読み込みます。" fetch-on-scroll-desc: "Cuando te deslizas al final de la página nuevo contenido se carga automáticamente."
auto-popout: "ウィンドウの自動ポップアウト" auto-popout: "Ventana emergente automática"
auto-popout-desc: "ウィンドウが開かれるとき、ポップアウト(ブラウザ外に切り離す)可能なら自動でポップアウトします。この設定はブラウザに記憶されます。" auto-popout-desc: "Muestra una ventana emergente si es posible. Esta configuración depende del navegador."
advanced: "詳細設定" advanced: "Configuración avanzada"
api-via-stream: "ストリームを経由したAPIリクエスト" api-via-stream: "Solicitar API por medio de un stream"
api-via-stream-desc: "この設定をオンにすると、websocket接続を経由してAPIリクエストが行われます(パフォーマンス向上が期待できます)。オフにすると、ネイティブの fetch APIが利用されます。この設定はこのデバイスのみ有効です。" 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: "デザインと表示" display: "Diseño y pantalla"
customize: "ホームをカスタマイズ" customize: "Personaliza la página principal"
dark-mode: "ダークモード" dark-mode: "Modo Nocturno"
circle-icons: "円形のアイコンを使用" circle-icons: "Usar iconos circulares"
gradient-window-header: "ウィンドウのタイトルバーにグラデーションを使用" gradient-window-header: "Usar degradados en las cabeceras de las páginas"
post-form-on-timeline: "タイムライン上部に投稿フォームを表示する" post-form-on-timeline: "Mostrar el formulario de las entradas encima de la línea de tiempo"
show-reply-target: "リプライ先を表示する" show-reply-target: "リプライ先を表示する"
show-my-renotes: "自分の行ったRenoteをタイムラインに表示する" show-my-renotes: "自分の行ったRenoteをタイムラインに表示する"
show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する" show-renoted-my-notes: "Renoteされた自分の投稿をタイムラインに表示する"

View File

@ -3,20 +3,20 @@ meta:
lang: "Français" lang: "Français"
divider: "" divider: ""
common: common:
misskey: "Une planète du fédiverse" misskey: "A ⭐ of fediverse"
about-title: "Une ⭐ du fédiverse." 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 ?" 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: time:
unknown: "inconnu" unknown: "inconnu"
future: "future" future: "future"
just_now: "maintenant" just_now: "à l'instant"
seconds_ago: "Il y a {}seconde(s)" seconds_ago: "Il y a {} seconde·s"
minutes_ago: "Il y a {}minute(s)" minutes_ago: "Il y a {} minute·s"
hours_ago: "Il y a {}heure(s)" hours_ago: "Il y a {} heure·s"
days_ago: "Il y a {}jour(s)" days_ago: "Il y a {} jour·s"
weeks_ago: "Il y a{}semaines(s)" weeks_ago: "Il y a {} semaines·s"
months_ago: "Il y a {} mois" months_ago: "Il y a {} mois"
years_ago: "Il y a {}an(s)" years_ago: "Il y a {} an·s"
weekday-short: weekday-short:
sunday: "D" sunday: "D"
monday: "L" monday: "L"
@ -32,11 +32,11 @@ common:
hmm: "Hmm ... ?" hmm: "Hmm ... ?"
surprise: "Wow" surprise: "Wow"
congrats: "Félicitations !" congrats: "Félicitations !"
angry: "En Colère" angry: "En colère"
confused: "Confus" confused: "Confus"
pudding: "Pudding" pudding: "Pudding"
note-placeholders: note-placeholders:
a: "Que faîtes vous à cet instant ?" a: "Que faîtes vous maintenant ?"
b: "Quoi de neuf ?" b: "Quoi de neuf ?"
c: "Qu'avez-vous en tête ?" c: "Qu'avez-vous en tête ?"
d: "Voulez-vous exprimer quelque chose ?" d: "Voulez-vous exprimer quelque chose ?"
@ -45,7 +45,7 @@ common:
delete: "Supprimer" delete: "Supprimer"
loading: "Chargement" loading: "Chargement"
ok: "OK" 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é." my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté."
widgets: widgets:
analog-clock: "Horloge analogique" analog-clock: "Horloge analogique"
@ -70,7 +70,7 @@ common:
donation: "Dons" donation: "Dons"
nav: "Navigation" nav: "Navigation"
tips: "Conseils" tips: "Conseils"
hashtags: "ハッシュタグ" hashtags: "Étiquettes"
deck: deck:
widgets: "Widgets" widgets: "Widgets"
home: "Accueil" home: "Accueil"
@ -226,8 +226,9 @@ common/views/widgets/posts-monitor.vue:
title: "Graph des publications" title: "Graph des publications"
toggle: "表示を切り替え" toggle: "表示を切り替え"
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "Étiquettes"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Info sur le serveur" title: "Info sur le serveur"
toggle: "Afficher les vues" toggle: "Afficher les vues"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "Plus" refresh: "Plus"
close: "Fermer" close: "Fermer"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "Othello" game: "Reversi"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Envoyer" done: "Envoyer"
add-widget: "Ajouter un widget" add-widget: "Ajouter un widget"
@ -463,7 +464,7 @@ desktop/views/components/settings.vue:
update-checking: "Recherche de mises à jour" update-checking: "Recherche de mises à jour"
do-update: "Rechercher des mises à jour" do-update: "Rechercher des mises à jour"
update-settings: "Paramètres avancés" update-settings: "Paramètres avancés"
prevent-update: "アップデートを延期する(非推奨)" prevent-update: "Reporter les mises à jour (non recommandé)"
prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。" prevent-update-desc: "この設定をオンにしてもアップデートが反映される場合があります。この設定はこのデバイスのみ有効です。"
no-updates: "Aucune mise à jour disponible" no-updates: "Aucune mise à jour disponible"
no-updates-desc: "Votre Misskey est à jour." no-updates-desc: "Votre Misskey est à jour."
@ -578,7 +579,7 @@ desktop/views/components/window.vue:
popout: "ポップアウト" popout: "ポップアウト"
close: "Fermer" close: "Fermer"
desktop/views/pages/deck/deck.tl-column.vue: desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ" is-media-only: "Les publications médias uniquement"
is-media-view: "メディアビュー" is-media-view: "メディアビュー"
desktop/views/pages/deck/deck.note.vue: desktop/views/pages/deck/deck.note.vue:
reposted-by: "Reposté par {}" reposted-by: "Reposté par {}"
@ -678,7 +679,7 @@ mobile/views/components/drive.vue:
folder-name: "Nom du dossier" folder-name: "Nom du dossier"
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。" root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。" root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
url-prompt: "アップロードしたいファイルのURL" url-prompt: "URL du fichier que vous souhaitez téléverser"
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。" uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
mobile/views/components/drive-file-detail.vue: mobile/views/components/drive-file-detail.vue:
rename: "Renommer" rename: "Renommer"
@ -693,9 +694,9 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)" hash: "Hash (md5)"
exif: "EXIF" exif: "EXIF"
mobile/views/components/follow-button.vue: mobile/views/components/follow-button.vue:
following: "フォロー中" following: "Abonnements"
follow: "Suivre" follow: "Suivre"
request-pending: "フォロー許可待ち" request-pending: "En attente d'approbation"
follow-request: "Demande d'abonnement" follow-request: "Demande d'abonnement"
mobile/views/components/friends-maker.vue: mobile/views/components/friends-maker.vue:
title: "Abonnez-vous aux utilisateurs" title: "Abonnez-vous aux utilisateurs"
@ -746,7 +747,7 @@ mobile/views/components/sub-note-content.vue:
private: "cette publication est privée" private: "cette publication est privée"
deleted: "cette publication a été supprimée" deleted: "cette publication a été supprimée"
media-count: "{} médias attachés" media-count: "{} médias attachés"
poll: "アンケート" poll: "Sondage"
mobile/views/components/timeline.vue: mobile/views/components/timeline.vue:
empty: "Pas de notes" empty: "Pas de notes"
load-more: "Afficher plus" load-more: "Afficher plus"

View File

@ -5,12 +5,15 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
const loadLang = lang => yaml.safeLoad( export type LangKey = 'de' | 'en' | 'fr' | 'ja' | 'pl';
fs.readFileSync(`./locales/${lang}.yml`, 'utf-8')); 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 native = loadLang('ja');
const langs = { const langs: { [key: string]: LocaleObject } = {
'de': loadLang('de'), 'de': loadLang('de'),
'en': loadLang('en'), 'en': loadLang('en'),
'fr': loadLang('fr'), 'fr': loadLang('fr'),
@ -23,4 +26,8 @@ Object.entries(langs).map(([, locale]) => {
locale = Object.assign({}, native, locale); locale = Object.assign({}, native, locale);
}); });
export function isAvailableLanguage(lang: string): lang is LangKey {
return lang in langs;
}
export default langs; export default langs;

View File

@ -3,7 +3,7 @@ meta:
lang: "日本語" lang: "日本語"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"

View File

@ -3,7 +3,7 @@ meta:
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
@ -52,6 +52,7 @@ common:
ok: "わかった" ok: "わかった"
update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。" update-available: "Misskeyの新しいバージョンがあります({newer}。現在{current}を利用中)。ページを再度読み込みすると更新が適用されます。"
my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。" my-token-regenerated: "あなたのトークンが更新されたのでサインアウトします。"
i-like-sushi: "私は(プリンよりむしろ)寿司が好き"
widgets: widgets:
analog-clock: "アナログ時計" analog-clock: "アナログ時計"
@ -258,6 +259,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
@ -383,7 +385,7 @@ desktop/views/components/friends-maker.vue:
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"

View File

@ -3,7 +3,7 @@ meta:
lang: "日本語" lang: "日本語"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"

View File

@ -3,7 +3,7 @@ meta:
lang: "język polski" lang: "język polski"
divider: "" divider: ""
common: common:
misskey: "Planeta Fediwersum" misskey: " Fediwersum"
about-title: "⭐ 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?" 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: time:
@ -70,7 +70,7 @@ common:
donation: "Dotacje" donation: "Dotacje"
nav: "Nawigacja" nav: "Nawigacja"
tips: "Wskazówki" tips: "Wskazówki"
hashtags: "ハッシュタグ" hashtags: "Hashtagi"
deck: deck:
widgets: "Widżety" widgets: "Widżety"
home: "Strona główna" home: "Strona główna"
@ -226,8 +226,9 @@ common/views/widgets/posts-monitor.vue:
title: "Wykres wpisów" title: "Wykres wpisów"
toggle: "Przełącz widok" toggle: "Przełącz widok"
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "Hashtagi"
count: "{}人が投稿" count: "Wspomniany przez {} użytkowników"
empty: "Brak popularnych hashtagów"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "Informacje o serwerze" title: "Informacje o serwerze"
toggle: "Przełącz widok" toggle: "Przełącz widok"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "Więcej" refresh: "Więcej"
close: "Zamknij" close: "Zamknij"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "Othello" game: "Reversi"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "Wyślij" done: "Wyślij"
add-widget: "Dodaj widżet:" add-widget: "Dodaj widżet:"

View File

@ -3,7 +3,7 @@ meta:
lang: "Português" lang: "Português"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"

View File

@ -3,7 +3,7 @@ meta:
lang: "Русский язык" lang: "Русский язык"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"

View File

@ -3,7 +3,7 @@ meta:
lang: "中文(简体)" lang: "中文(简体)"
divider: "" divider: ""
common: common:
misskey: "A planet of fediverse" misskey: "A of fediverse"
about-title: "A ⭐ of fediverse." about-title: "A ⭐ of fediverse."
about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。" about: "Misskeyを見つけていただき、ありがとうございます。Misskeyは、地球で生まれた<b>分散マイクロブログSNS</b>です。Fediverse(様々なSNSで構成される宇宙)の中に存在するため、他のSNSと相互に繋がっています。暫し都会の喧騒から離れて、新しいインターネットにダイブしてみませんか。"
time: time:
@ -228,6 +228,7 @@ common/views/widgets/posts-monitor.vue:
common/views/widgets/hashtags.vue: common/views/widgets/hashtags.vue:
title: "ハッシュタグ" title: "ハッシュタグ"
count: "{}人が投稿" count: "{}人が投稿"
empty: "トレンドなし"
common/views/widgets/server.vue: common/views/widgets/server.vue:
title: "サーバー情報" title: "サーバー情報"
toggle: "表示を切り替え" toggle: "表示を切り替え"
@ -333,7 +334,7 @@ desktop/views/components/friends-maker.vue:
refresh: "もっと見る" refresh: "もっと見る"
close: "閉じる" close: "閉じる"
desktop/views/components/game-window.vue: desktop/views/components/game-window.vue:
game: "オセロ" game: "リバーシ"
desktop/views/components/home.vue: desktop/views/components/home.vue:
done: "完了" done: "完了"
add-widget: "ウィジェットを追加:" add-widget: "ウィジェットを追加:"

View File

@ -1,8 +1,8 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "2.37.3", "version": "4.3.0",
"clientVersion": "1.0.6453", "clientVersion": "1.0.6630",
"codename": "nighthike", "codename": "nighthike",
"main": "./built/index.js", "main": "./built/index.js",
"private": true, "private": true,
@ -23,72 +23,12 @@
"format": "gulp format" "format": "gulp format"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome": "1.0.1", "@fortawesome/fontawesome": "1.1.8",
"@fortawesome/fontawesome-free-brands": "5.0.2", "@fortawesome/fontawesome-free-brands": "5.0.13",
"@fortawesome/fontawesome-free-regular": "5.0.2", "@fortawesome/fontawesome-free-regular": "5.0.13",
"@fortawesome/fontawesome-free-solid": "5.0.2", "@fortawesome/fontawesome-free-solid": "5.0.13",
"@koa/cors": "2.2.1", "@koa/cors": "2.2.1",
"@prezzemolo/rap": "0.1.2", "@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"
},
"devDependencies": {
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.1", "@types/bcryptjs": "2.4.1",
"@types/debug": "0.0.30", "@types/debug": "0.0.30",
@ -144,17 +84,30 @@
"@types/ws": "5.1.1", "@types/ws": "5.1.1",
"animejs": "2.2.0", "animejs": "2.2.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"bootstrap-vue": "2.0.0-rc.6", "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", "css-loader": "0.28.11",
"debug": "3.1.0",
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
"deepcopy": "0.6.3",
"diskusage": "0.2.4",
"dompurify": "1.0.4", "dompurify": "1.0.4",
"elasticsearch": "15.0.0",
"element-ui": "2.3.9", "element-ui": "2.3.9",
"emojilib": "2.2.12",
"escape-regexp": "0.0.1",
"eslint": "4.19.1", "eslint": "4.19.1",
"eslint-plugin-vue": "4.5.0", "eslint-plugin-vue": "4.5.0",
"eventemitter3": "3.1.0", "eventemitter3": "3.1.0",
"exif-js": "2.3.0", "exif-js": "2.3.0",
"file-loader": "1.1.11", "file-loader": "1.1.11",
"file-type": "8.0.0",
"fuckadblock": "3.2.1", "fuckadblock": "3.2.1",
"gm": "1.23.1",
"gulp": "3.9.1", "gulp": "3.9.1",
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
"gulp-htmlmin": "4.0.0", "gulp-htmlmin": "4.0.0",
@ -172,32 +125,71 @@
"hard-source-webpack-plugin": "0.6.10", "hard-source-webpack-plugin": "0.6.10",
"highlight.js": "9.12.0", "highlight.js": "9.12.0",
"html-minifier": "3.5.16", "html-minifier": "3.5.16",
"http-signature": "1.2.0",
"inquirer": "5.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", "license-checker": "20.0.0",
"loader-utils": "1.1.0", "loader-utils": "1.1.0",
"mecab-async": "0.1.2", "mecab-async": "0.1.2",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.2.0", "mocha": "5.2.0",
"moji": "0.5.1", "moji": "0.5.1",
"mongodb": "3.0.10",
"monk": "6.0.6",
"ms": "2.1.1",
"nan": "2.10.0", "nan": "2.10.0",
"node-sass": "4.9.0", "node-sass": "4.9.0",
"node-sass-json-importer": "3.2.0", "node-sass-json-importer": "3.2.0",
"nopt": "4.0.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0", "on-build-webpack": "0.1.0",
"os-utils": "0.0.14",
"parse5": "5.0.0",
"progress-bar-webpack-plugin": "1.11.0", "progress-bar-webpack-plugin": "1.11.0",
"prominence": "0.2.0",
"promise-sequential": "1.1.1",
"pug": "2.0.3", "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", "rimraf": "2.6.2",
"rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass-loader": "7.0.1", "sass-loader": "7.0.1",
"seedrandom": "2.4.3", "seedrandom": "2.4.3",
"single-line-log": "1.1.2", "single-line-log": "1.1.2",
"speakeasy": "2.0.0",
"style-loader": "0.21.0", "style-loader": "0.21.0",
"stylus": "0.54.5", "stylus": "0.54.5",
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.0.6",
"swagger-jsdoc": "1.9.7", "swagger-jsdoc": "1.9.7",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"tcp-port-used": "0.1.2",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"tmp": "0.0.33",
"ts-loader": "4.3.0", "ts-loader": "4.3.0",
"ts-node": "6.0.4", "ts-node": "6.0.4",
"tslint": "5.10.0", "tslint": "5.10.0",
@ -205,19 +197,26 @@
"typescript-eslint-parser": "15.0.0", "typescript-eslint-parser": "15.0.0",
"uglify-es": "3.3.9", "uglify-es": "3.3.9",
"url-loader": "1.0.1", "url-loader": "1.0.1",
"uuid": "3.2.1",
"v-animate-css": "0.0.2", "v-animate-css": "0.0.2",
"vue": "2.5.16", "vue": "2.5.16",
"vue-cropperjs": "2.2.0", "vue-cropperjs": "2.2.0",
"vue-js-modal": "1.3.13", "vue-js-modal": "1.3.13",
"vue-json-tree-view": "2.1.4", "vue-json-tree-view": "2.1.4",
"vue-loader": "15.2.1", "vue-loader": "15.2.1",
"vue-material": "^1.0.0-beta-10.2",
"vue-router": "3.0.1", "vue-router": "3.0.1",
"vue-template-compiler": "2.5.16", "vue-template-compiler": "2.5.16",
"vuedraggable": "2.16.0", "vuedraggable": "2.16.0",
"vuex": "3.0.1", "vuex": "3.0.1",
"vuex-persistedstate": "^2.5.4", "vuex-persistedstate": "^2.5.4",
"web-push": "3.3.1",
"webfinger.js": "2.6.6",
"webpack": "4.9.1", "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"
} }
} }

View File

@ -1,4 +1,4 @@
export default acct => { export default (acct: string) => {
const splitted = acct.split('@', 2); const splitted = acct.split('@', 2);
return { username: splitted[0], host: splitted[1] || null }; return { username: splitted[0], host: splitted[1] || null };
}; };

View File

@ -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}`; return user.host === null ? user.username : `${user.username}@${user.host}`;
}; };

View File

@ -3,18 +3,18 @@
*/ */
import * as fontawesome from '@fortawesome/fontawesome'; import * as fontawesome from '@fortawesome/fontawesome';
import * as regular from '@fortawesome/fontawesome-free-regular'; import regular from '@fortawesome/fontawesome-free-regular';
import * as solid from '@fortawesome/fontawesome-free-solid'; import solid from '@fortawesome/fontawesome-free-solid';
import * as brands from '@fortawesome/fontawesome-free-brands'; import brands from '@fortawesome/fontawesome-free-brands';
fontawesome.library.add(regular, solid, brands); fontawesome.library.add(regular, solid, brands);
export const pattern = /%fa:(.+?)%/g; export const pattern = /%fa:(.+?)%/g;
export const replacement = (match, key) => { export const replacement = (match: string, key: string) => {
const args = key.split(' '); const args = key.split(' ');
let prefix = 'fas'; let prefix = 'fas';
const classes = []; const classes: string[] = [];
let transform = ''; let transform = '';
let name; let name;
@ -34,12 +34,12 @@ export const replacement = (match, key) => {
} }
}); });
const icon = fontawesome.icon({ prefix, iconName: name }, { const icon = fontawesome.icon({ prefix, iconName: name } as fontawesome.IconLookup, {
classes: classes classes: classes,
transform: fontawesome.parse.transform(transform)
}); });
if (icon) { if (icon) {
icon.transform = fontawesome.parse.transform(transform);
return `<i data-fa class="${name}">${icon.html[0]}</i>`; return `<i data-fa class="${name}">${icon.html[0]}</i>`;
} else { } else {
console.warn(`'${name}' not found in fa`); console.warn(`'${name}' not found in fa`);

View File

@ -2,7 +2,7 @@
* Replace i18n texts * Replace i18n texts
*/ */
import locale from '../../locales'; import locale, { isAvailableLanguage, LocaleObject } from '../../locales';
export default class Replacer { export default class Replacer {
private lang: string; private lang: string;
@ -16,19 +16,19 @@ export default class Replacer {
this.replacement = this.replacement.bind(this); this.replacement = this.replacement.bind(this);
} }
private get(path: string, key: string) { private get(path: string, key: string): string {
const texts = locale[this.lang]; if (!isAvailableLanguage(this.lang)) {
if (texts == null) {
console.warn(`lang '${this.lang}' is not supported`); console.warn(`lang '${this.lang}' is not supported`);
return key; // Fallback return key; // Fallback
} }
const texts = locale[this.lang];
let text = texts; let text = texts;
if (path) { if (path) {
if (text.hasOwnProperty(path)) { if (text.hasOwnProperty(path)) {
text = text[path]; text = text[path] as LocaleObject;
} else { } else {
console.warn(`path '${path}' not found in '${this.lang}'`); console.warn(`path '${path}' not found in '${this.lang}'`);
return key; // Fallback return key; // Fallback
@ -38,7 +38,7 @@ export default class Replacer {
// Check the key existance // Check the key existance
const error = key.split('.').some(k => { const error = key.split('.').some(k => {
if (text.hasOwnProperty(k)) { if (text.hasOwnProperty(k)) {
text = text[k]; text = (text as LocaleObject)[k];
return false; return false;
} else { } else {
return true; return true;
@ -48,12 +48,15 @@ export default class Replacer {
if (error) { if (error) {
console.warn(`key '${key}' not found in '${path}' of '${this.lang}'`); console.warn(`key '${key}' not found in '${path}' of '${this.lang}'`);
return key; // Fallback return key; // Fallback
} else if (typeof text !== 'string') {
console.warn(`key '${key}' is not string in '${path}' of '${this.lang}'`);
return key; // Fallback
} else { } else {
return text; return text;
} }
} }
public replacement(match, key) { public replacement(match: string, key: string) {
let path = null; let path = null;
if (key.indexOf('|') != -1) { if (key.indexOf('|') != -1) {

View File

@ -1,8 +1,8 @@
import * as mongo from 'mongodb'; import * as mongo from 'mongodb';
import { Query } from 'cafy'; import { Query } from 'cafy';
export const isAnId = x => mongo.ObjectID.isValid(x); export const isAnId = (x: any) => mongo.ObjectID.isValid(x);
export const isNotAnId = x => !isAnId(x); export const isNotAnId = (x: any) => !isAnId(x);
/** /**
* ID * ID

View File

@ -7,11 +7,6 @@ html
cursor progress !important cursor progress !important
body body
// for md
font-size 16px !important
line-height initial !important
letter-spacing initial !important
overflow-wrap break-word overflow-wrap break-word
#error #error

View File

@ -55,7 +55,7 @@ export default function(type, data): Notification {
icon: data.user.avatarUrl + '?thumbnail&size=64' icon: data.user.avatarUrl + '?thumbnail&size=64'
}; };
case 'othello_invited': case 'reversi_invited':
return { return {
title: '対局への招待があります', title: '対局への招待があります',
body: `${getUserName(data.parent)}さんから`, body: `${getUserName(data.parent)}さんから`,

View File

@ -1,9 +1,9 @@
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../../mios'; import MiOS from '../../../mios';
export class OthelloGameStream extends Stream { export class ReversiGameStream extends Stream {
constructor(os: MiOS, me, game) { constructor(os: MiOS, me, game) {
super(os, 'othello-game', { super(os, 'reversi-game', {
i: me ? me.token : null, i: me ? me.token : null,
game: game.id game: game.id
}); });

View File

@ -2,15 +2,15 @@ import StreamManager from './stream-manager';
import Stream from './stream'; import Stream from './stream';
import MiOS from '../../../mios'; import MiOS from '../../../mios';
export class OthelloStream extends Stream { export class ReversiStream extends Stream {
constructor(os: MiOS, me) { constructor(os: MiOS, me) {
super(os, 'othello', { super(os, 'reversi', {
i: me.token i: me.token
}); });
} }
} }
export class OthelloStreamManager extends StreamManager<OthelloStream> { export class ReversiStreamManager extends StreamManager<ReversiStream> {
private me; private me;
private os: MiOS; private os: MiOS;
@ -23,7 +23,7 @@ export class OthelloStreamManager extends StreamManager<OthelloStream> {
public getConnection() { public getConnection() {
if (this.connection == null) { if (this.connection == null) {
this.connection = new OthelloStream(this.os, this.me); this.connection = new ReversiStream(this.os, this.me);
} }
return this.connection; return this.connection;

View File

@ -13,9 +13,6 @@
.a .a
display block display block
position fixed
top 0
right 0
> svg > svg
display block display block

View File

@ -27,8 +27,16 @@ import urlPreview from './url-preview.vue';
import twitterSetting from './twitter-setting.vue'; import twitterSetting from './twitter-setting.vue';
import fileTypeIcon from './file-type-icon.vue'; import fileTypeIcon from './file-type-icon.vue';
import Switch from './switch.vue'; import Switch from './switch.vue';
import Othello from './othello.vue'; import Reversi from './reversi.vue';
import welcomeTimeline from './welcome-timeline.vue'; import welcomeTimeline from './welcome-timeline.vue';
import uiInput from './ui/input.vue';
import uiButton from './ui/button.vue';
import uiCard from './ui/card.vue';
import uiForm from './ui/form.vue';
import uiTextarea from './ui/textarea.vue';
import uiSwitch from './ui/switch.vue';
import uiRadio from './ui/radio.vue';
import uiSelect from './ui/select.vue';
Vue.component('mk-analog-clock', analogClock); Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-menu', menu); Vue.component('mk-menu', menu);
@ -57,5 +65,13 @@ Vue.component('mk-url-preview', urlPreview);
Vue.component('mk-twitter-setting', twitterSetting); Vue.component('mk-twitter-setting', twitterSetting);
Vue.component('mk-file-type-icon', fileTypeIcon); Vue.component('mk-file-type-icon', fileTypeIcon);
Vue.component('mk-switch', Switch); Vue.component('mk-switch', Switch);
Vue.component('mk-othello', Othello); Vue.component('mk-reversi', Reversi);
Vue.component('mk-welcome-timeline', welcomeTimeline); Vue.component('mk-welcome-timeline', welcomeTimeline);
Vue.component('ui-input', uiInput);
Vue.component('ui-button', uiButton);
Vue.component('ui-card', uiCard);
Vue.component('ui-form', uiForm);
Vue.component('ui-textarea', uiTextarea);
Vue.component('ui-switch', uiSwitch);
Vue.component('ui-radio', uiRadio);
Vue.component('ui-select', uiSelect);

View File

@ -40,6 +40,17 @@ export default Vue.component('mk-note-html', {
ast = this.ast; ast = this.ast;
} }
if (ast.filter(x => x.type != 'hashtag').length == 0) {
return;
}
while (ast[ast.length - 1] && (
ast[ast.length - 1].type == 'hashtag' ||
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == ' ') ||
(ast[ast.length - 1].type == 'text' && ast[ast.length - 1].content == '\n'))) {
ast.pop();
}
// Parse ast to DOM // Parse ast to DOM
const els = flatten(ast.map(token => { const els = flatten(ast.map(token => {
switch (token.type) { switch (token.type) {
@ -92,7 +103,7 @@ export default Vue.component('mk-note-html', {
case 'hashtag': case 'hashtag':
return createElement('a', { return createElement('a', {
attrs: { attrs: {
href: `${url}/search?q=${token.content}`, href: `${url}/tags/${token.hashtag}`,
target: '_blank' target: '_blank'
} }
}, token.content); }, token.content);

View File

@ -8,7 +8,10 @@
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%"> <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 == '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 == '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> </span>
</template> </template>

View File

@ -43,7 +43,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as CRC32 from 'crc-32'; import * as CRC32 from 'crc-32';
import Othello, { Color } from '../../../../../othello/core'; import Reversi, { Color } from '../../../../../reversi/core';
import { url } from '../../../config'; import { url } from '../../../config';
export default Vue.extend({ export default Vue.extend({
@ -52,7 +52,7 @@ export default Vue.extend({
data() { data() {
return { return {
game: null, game: null,
o: null as Othello, o: null as Reversi,
logs: [], logs: [],
logPos: 0, logPos: 0,
pollingClock: null pollingClock: null
@ -98,7 +98,7 @@ export default Vue.extend({
watch: { watch: {
logPos(v) { logPos(v) {
if (!this.game.isEnded) return; 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, isLlotheo: this.game.settings.isLlotheo,
canPutEverywhere: this.game.settings.canPutEverywhere, canPutEverywhere: this.game.settings.canPutEverywhere,
loopedBoard: this.game.settings.loopedBoard loopedBoard: this.game.settings.loopedBoard
@ -115,7 +115,7 @@ export default Vue.extend({
created() { created() {
this.game = this.initGame; 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, isLlotheo: this.game.settings.isLlotheo,
canPutEverywhere: this.game.settings.canPutEverywhere, canPutEverywhere: this.game.settings.canPutEverywhere,
loopedBoard: this.game.settings.loopedBoard loopedBoard: this.game.settings.loopedBoard
@ -163,7 +163,7 @@ export default Vue.extend({
// //
if (this.$store.state.device.enableSounds) { 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.volume = this.$store.state.device.soundVolume;
sound.play(); sound.play();
} }
@ -187,7 +187,7 @@ export default Vue.extend({
// //
if (this.$store.state.device.enableSounds && x.color != this.myColor) { 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.volume = this.$store.state.device.soundVolume;
sound.play(); sound.play();
} }
@ -213,7 +213,7 @@ export default Vue.extend({
onRescue(game) { onRescue(game) {
this.game = 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, isLlotheo: this.game.settings.isLlotheo,
canPutEverywhere: this.game.settings.canPutEverywhere, canPutEverywhere: this.game.settings.canPutEverywhere,
loopedBoard: this.game.settings.loopedBoard loopedBoard: this.game.settings.loopedBoard

View File

@ -7,9 +7,9 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XGame from './othello.game.vue'; import XGame from './reversi.game.vue';
import XRoom from './othello.room.vue'; import XRoom from './reversi.room.vue';
import { OthelloGameStream } from '../../scripts/streaming/othello-game'; import { ReversiGameStream } from '../../scripts/streaming/reversi-game';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -25,7 +25,7 @@ export default Vue.extend({
}, },
created() { created() {
this.g = this.game; 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); this.connection.on('started', this.onStarted);
}, },
beforeDestroy() { beforeDestroy() {

View File

@ -94,7 +94,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import * as maps from '../../../../../othello/maps'; import * as maps from '../../../../../reversi/maps';
export default Vue.extend({ export default Vue.extend({
props: ['game', 'connection'], props: ['game', 'connection'],

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="mk-othello"> <div class="mk-reversi">
<div v-if="game"> <div v-if="game">
<x-gameroom :game="game"/> <x-gameroom :game="game"/>
</div> </div>
@ -11,14 +11,14 @@
</div> </div>
<div class="index" v-else> <div class="index" v-else>
<h1>Misskey %fa:circle%thell%fa:circle R%</h1> <h1>Misskey %fa:circle%thell%fa:circle R%</h1>
<p>他のMisskeyユーザーとオセロで対戦しよう</p> <p>他のMisskeyユーザーとリバーシで対戦しよう</p>
<div class="play"> <div class="play">
<el-button round>フリーマッチ(準備中)</el-button> <el-button round>フリーマッチ(準備中)</el-button>
<el-button type="primary" round @click="match">指名</el-button> <el-button type="primary" round @click="match">指名</el-button>
<details> <details>
<summary>遊び方</summary> <summary>遊び方</summary>
<div> <div>
<p>オセロ相手と交互に石をボードに置いてゆき相手の石を挟んでひっくり返しながら最終的に残った石が多い方が勝ちというボードゲームです</p> <p>リバーシ相手と交互に石をボードに置いてゆき相手の石を挟んでひっくり返しながら最終的に残った石が多い方が勝ちというボードゲームです</p>
<dl> <dl>
<dt><b>フリーマッチ</b></dt> <dt><b>フリーマッチ</b></dt>
<dd>ランダムなユーザーと対戦するモードです</dd> <dd>ランダムなユーザーと対戦するモードです</dd>
@ -39,7 +39,7 @@
</section> </section>
<section v-if="myGames.length > 0"> <section v-if="myGames.length > 0">
<h2>自分の対局</h2> <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.user1"/>
<mk-avatar class="avatar" :user="g.user2"/> <mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span> <span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
@ -48,7 +48,7 @@
</section> </section>
<section v-if="games.length > 0"> <section v-if="games.length > 0">
<h2>みんなの対局</h2> <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.user1"/>
<mk-avatar class="avatar" :user="g.user2"/> <mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span> <span><b>{{ g.user1.name }}</b> vs <b>{{ g.user2.name }}</b></span>
@ -61,7 +61,7 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import XGameroom from './othello.gameroom.vue'; import XGameroom from './reversi.gameroom.vue';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -93,24 +93,24 @@ export default Vue.extend({
} }
}, },
mounted() { mounted() {
this.connection = (this as any).os.streams.othelloStream.getConnection(); this.connection = (this as any).os.streams.reversiStream.getConnection();
this.connectionId = (this as any).os.streams.othelloStream.use(); this.connectionId = (this as any).os.streams.reversiStream.use();
this.connection.on('matched', this.onMatched); this.connection.on('matched', this.onMatched);
this.connection.on('invited', this.onInvited); this.connection.on('invited', this.onInvited);
(this as any).api('othello/games', { (this as any).api('reversi/games', {
my: true my: true
}).then(games => { }).then(games => {
this.myGames = games; this.myGames = games;
}); });
(this as any).api('othello/games').then(games => { (this as any).api('reversi/games').then(games => {
this.games = games; this.games = games;
this.gamesFetching = false; 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); this.invitations = this.invitations.concat(invitations);
}); });
@ -126,13 +126,13 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
this.connection.off('matched', this.onMatched); this.connection.off('matched', this.onMatched);
this.connection.off('invited', this.onInvited); 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); clearInterval(this.pingClock);
}, },
methods: { methods: {
go(game) { go(game) {
(this as any).api('othello/games/show', { (this as any).api('reversi/games/show', {
gameId: game.id gameId: game.id
}).then(game => { }).then(game => {
this.matching = null; this.matching = null;
@ -146,7 +146,7 @@ export default Vue.extend({
(this as any).api('users/show', { (this as any).api('users/show', {
username username
}).then(user => { }).then(user => {
(this as any).api('othello/match', { (this as any).api('reversi/match', {
userId: user.id userId: user.id
}).then(res => { }).then(res => {
if (res == null) { if (res == null) {
@ -160,10 +160,10 @@ export default Vue.extend({
}, },
cancel() { cancel() {
this.matching = null; this.matching = null;
(this as any).api('othello/match/cancel'); (this as any).api('reversi/match/cancel');
}, },
accept(invitation) { accept(invitation) {
(this as any).api('othello/match', { (this as any).api('reversi/match', {
userId: invitation.parent.id userId: invitation.parent.id
}).then(game => { }).then(game => {
if (game) { if (game) {
@ -186,7 +186,7 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
.mk-othello .mk-reversi
color #677f84 color #677f84
background #fff background #fff

View File

@ -1,24 +1,33 @@
<template> <template>
<form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit"> <form class="mk-signin" :class="{ signing }" @submit.prevent="onSubmit">
<label class="user-name"> <div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="%i18n:@username%" autofocus required @change="onUsernameChange"/>%fa:at% <ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @input="onUsernameChange">
</label> <span>%i18n:@username%</span>
<label class="password"> <span slot="prefix">@</span>
<input v-model="password" type="password" placeholder="%i18n:@password%" required/>%fa:lock% <span slot="suffix">@{{ host }}</span>
</label> </ui-input>
<label class="token" v-if="user && user.twoFactorEnabled"> <ui-input v-model="password" type="password" required>
<input v-model="token" type="number" placeholder="%i18n:@token%" required/>%fa:lock% <span>%i18n:@password%</span>
</label> <span slot="prefix">%fa:lock%</span>
<button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</button> </ui-input>
もしくは <a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a> <ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
<p style="margin: 8px 0;">または<a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a></p>
</form> </form>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { apiUrl } from '../../../config'; import { apiUrl, host } from '../../../config';
export default Vue.extend({ export default Vue.extend({
props: {
withAvatar: {
type: Boolean,
required: false,
default: true
}
},
data() { data() {
return { return {
signing: false, signing: false,
@ -27,6 +36,7 @@ export default Vue.extend({
password: '', password: '',
token: '', token: '',
apiUrl, apiUrl,
host
}; };
}, },
methods: { methods: {
@ -35,6 +45,8 @@ export default Vue.extend({
username: this.username username: this.username
}).then(user => { }).then(user => {
this.user = user; this.user = user;
}, () => {
this.user = null;
}); });
}, },
onSubmit() { onSubmit() {
@ -59,84 +71,19 @@ export default Vue.extend({
@import '~const.styl' @import '~const.styl'
.mk-signin .mk-signin
color #555
&.signing &.signing
&, * &, *
cursor wait !important cursor wait !important
label > .avatar
display block margin 16px auto 0 auto
margin 12px 0 width 64px
height 64px
[data-fa] background #ddd
display block background-position center
pointer-events none background-size cover
position absolute border-radius 100%
bottom 0
top 0
left 0
z-index 1
margin auto
padding 0 16px
height 1em
color #898786
input[type=text]
input[type=password]
input[type=number]
user-select text
display inline-block
cursor auto
padding 0 0 0 38px
margin 0
width 100%
line-height 44px
font-size 1em
color rgba(#000, 0.7)
background #fff
outline none
border solid 1px #eee
border-radius 4px
&:hover
background rgba(255, 255, 255, 0.7)
border-color #ddd
& + i
color #797776
&:focus
background #fff
border-color #ccc
& + i
color #797776
[type=submit]
cursor pointer
padding 16px
margin -6px 0 0 0
width 100%
font-size 1.2em
color rgba(#000, 0.5)
outline none
border none
border-radius 0
background transparent
transition all .5s ease
&:hover
color $theme-color
transition all .2s ease
&:focus
color $theme-color
transition all .2s ease
&:active
color darken($theme-color, 30%)
transition all .2s ease
&:disabled
opacity 0.7
</style> </style>

View File

@ -1,60 +1,58 @@
<template> <template>
<form class="mk-signup" @submit.prevent="onSubmit" autocomplete="off"> <form class="mk-signup" @submit.prevent="onSubmit" :autocomplete="Math.random()">
<label class="username"> <ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername">
<p class="caption">%fa:at%%i18n:@username%</p> <span>%i18n:@username%</span>
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" placeholder="a~z、A~Z、0~9、-" autocomplete="off" required @input="onChangeUsername"/> <span slot="prefix">@</span>
<p class="profile-page-url-preview" v-if="shouldShowProfileUrl">{{ `${url}/@${username}` }}</p> <span slot="suffix">@{{ host }}</span>
<p class="info" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw%%i18n:@checking%</p> <p slot="text" v-if="usernameState == 'wait'" style="color:#999">%fa:spinner .pulse .fw% %i18n:@checking%</p>
<p class="info" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw%%i18n:@available%</p> <p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5">%fa:check .fw% %i18n:@available%</p>
<p class="info" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@unavailable%</p> <p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@unavailable%</p>
<p class="info" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@error%</p> <p slot="text" v-if="usernameState == 'error'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@error%</p>
<p class="info" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@invalid-format%</p> <p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@invalid-format%</p>
<p class="info" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-short%</p> <p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-short%</p>
<p class="info" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@too-long%</p> <p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@too-long%</p>
</label> </ui-input>
<label class="password"> <ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true">
<p class="caption">%fa:lock%%i18n:@password%</p> <span>%i18n:@password%</span>
<input v-model="password" type="password" placeholder="%i18n:@password-placeholder%" autocomplete="off" required @input="onChangePassword"/> <span slot="prefix">%fa:lock%</span>
<div class="meter" v-show="passwordStrength != ''" :data-strength="passwordStrength"> <div slot="text">
<div class="value" ref="passwordMetar"></div> <p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@weak-password%</p>
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw% %i18n:@normal-password%</p>
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw% %i18n:@strong-password%</p>
</div> </div>
<p class="info" v-if="passwordStrength == 'low'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@weak-password%</p> </ui-input>
<p class="info" v-if="passwordStrength == 'medium'" style="color:#3CB7B5">%fa:check .fw%%i18n:@normal-password%</p> <ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype">
<p class="info" v-if="passwordStrength == 'high'" style="color:#3CB7B5">%fa:check .fw%%i18n:@strong-password%</p> <span>%i18n:@password% (%i18n:@retype%)</span>
</label> <span slot="prefix">%fa:lock%</span>
<label class="retype-password"> <div slot="text">
<p class="caption">%fa:lock%%i18n:@password%(%i18n:@retype%)</p> <p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw% %i18n:@password-matched%</p>
<input v-model="retypedPassword" type="password" placeholder="%i18n:@retype-placeholder%" autocomplete="off" required @input="onChangePasswordRetype"/> <p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw% %i18n:@password-not-matched%</p>
<p class="info" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5">%fa:check .fw%%i18n:@password-matched%</p> </div>
<p class="info" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161">%fa:exclamation-triangle .fw%%i18n:@password-not-matched%</p> </ui-input>
</label> <div class="g-recaptcha" :data-sitekey="recaptchaSitekey" style="margin: 16px 0;"></div>
<label class="recaptcha"> <label class="agree-tou" style="display: block; margin: 16px 0;">
<p class="caption"><template v-if="recaptchaed">%fa:toggle-on%</template><template v-if="!recaptchaed">%fa:toggle-off%</template>%i18n:@recaptcha%</p> <input name="agree-tou" type="checkbox" required/>
<div class="g-recaptcha" data-callback="onRecaptchaed" data-expired-callback="onRecaptchaExpired" :data-sitekey="recaptchaSitekey"></div>
</label>
<label class="agree-tou">
<input name="agree-tou" type="checkbox" autocomplete="off" required/>
<p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p> <p><a :href="touUrl" target="_blank">利用規約</a>に同意する</p>
</label> </label>
<button type="submit">%i18n:@create%</button> <ui-button type="submit">%i18n:@create%</ui-button>
</form> </form>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength'); const getPasswordStrength = require('syuilo-password-strength');
import { url, docsUrl, lang, recaptchaSitekey } from '../../../config'; import { host, url, docsUrl, lang, recaptchaSitekey } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
host,
username: '', username: '',
password: '', password: '',
retypedPassword: '', retypedPassword: '',
url, url,
touUrl: `${docsUrl}/${lang}/tou`, touUrl: `${docsUrl}/${lang}/tou`,
recaptchaSitekey, recaptchaSitekey,
recaptchaed: false,
usernameState: null, usernameState: null,
passwordStrength: '', passwordStrength: '',
passwordRetypeState: null passwordRetypeState: null
@ -104,7 +102,6 @@ export default Vue.extend({
const strength = getPasswordStrength(this.password); const strength = getPasswordStrength(this.password);
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low'; this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
}, },
onChangePasswordRetype() { onChangePasswordRetype() {
if (this.retypedPassword == '') { if (this.retypedPassword == '') {
@ -130,19 +127,9 @@ export default Vue.extend({
alert('%i18n:@some-error%'); alert('%i18n:@some-error%');
(window as any).grecaptcha.reset(); (window as any).grecaptcha.reset();
this.recaptchaed = false;
}); });
} }
}, },
created() {
(window as any).onRecaptchaed = () => {
this.recaptchaed = true;
};
(window as any).onRecaptchaExpired = () => {
this.recaptchaed = false;
};
},
mounted() { mounted() {
const head = document.getElementsByTagName('head')[0]; const head = document.getElementsByTagName('head')[0];
const script = document.createElement('script'); const script = document.createElement('script');
@ -158,100 +145,6 @@ export default Vue.extend({
.mk-signup .mk-signup
min-width 302px min-width 302px
label
display block
margin 0 0 16px 0
> .caption
margin 0 0 4px 0
color #828888
font-size 0.95em
> [data-fa]
margin-right 0.25em
color #96adac
> .info
display block
margin 4px 0
font-size 0.8em
> [data-fa]
margin-right 0.3em
&.username
.profile-page-url-preview
display block
margin 4px 8px 0 4px
font-size 0.8em
color #888
&:empty
display none
&:not(:empty) + .info
margin-top 0
&.password
.meter
display block
margin-top 8px
width 100%
height 8px
&[data-strength='']
display none
&[data-strength='low']
> .value
background #d73612
&[data-strength='medium']
> .value
background #d7ca12
&[data-strength='high']
> .value
background #61bb22
> .value
display block
width 0%
height 100%
background transparent
border-radius 4px
transition all 0.1s ease
[type=text], [type=password]
user-select text
display inline-block
cursor auto
padding 0 12px
margin 0
width 100%
line-height 44px
font-size 1em
color #333 !important
background #fff !important
outline none
border solid 1px rgba(#000, 0.1)
border-radius 4px
box-shadow 0 0 0 114514px #fff inset
transition all .3s ease
&:hover
border-color rgba(#000, 0.2)
transition all .1s ease
&:focus
color $theme-color !important
border-color $theme-color
box-shadow 0 0 0 1024px #fff inset, 0 0 0 4px rgba($theme-color, 10%)
transition all 0s ease
&:disabled
opacity 0.5
.agree-tou .agree-tou
padding 4px padding 4px
border-radius 4px border-radius 4px
@ -269,19 +162,4 @@ export default Vue.extend({
display inline display inline
color #555 color #555
button
margin 0
padding 16px
width 100%
font-size 1em
color #fff
background $theme-color
border-radius 3px
&:hover
background lighten($theme-color, 5%)
&:active
background darken($theme-color, 5%)
</style> </style>

View File

@ -0,0 +1,82 @@
<template>
<div class="ui-button" :class="[styl]">
<button :type="type" @click="$emit('click')">
<slot></slot>
</button>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
type: {
type: String,
required: false
}
},
data() {
return {
styl: 'fill'
};
},
inject: {
isCardChild: { default: false }
},
created() {
if (this.isCardChild) {
this.styl = 'line';
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark, fill)
> button
display block
width 100%
margin 0
padding 0
font-weight bold
font-size 16px
line-height 44px
border none
border-radius 6px
outline none
box-shadow none
if fill
color $theme-color-foreground
background $theme-color
&:hover
background lighten($theme-color, 5%)
&:active
background darken($theme-color, 5%)
else
color $theme-color
background none
&:hover
color darken($theme-color, 5%)
&:active
background rgba($theme-color, 0.3)
.ui-button[data-darkmode]
&.fill
root(true, true)
&:not(.fill)
root(true, false)
.ui-button:not([data-darkmode])
&.fill
root(false, true)
&:not(.fill)
root(false, false)
</style>

View File

@ -0,0 +1,46 @@
<template>
<div class="ui-card">
<header>
<slot name="title"></slot>
</header>
<slot></slot>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
provide() {
return {
isCardChild: true
};
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
margin 16px
padding 16px
color isDark ? #fff : #000
background isDark ? #282C37 : #fff
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
@media (min-width 500px)
padding 32px
> header
font-weight normal
font-size 24px
color isDark ? #fff : #444
.ui-card[data-darkmode]
root(true)
.ui-card:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,30 @@
<template>
<div class="ui-form">
<fieldset :disabled="disabled">
<slot></slot>
</fieldset>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
disabled: {
type: Boolean,
required: false
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
.ui-form
> fieldset
margin 0
padding 0
border none
</style>

View File

@ -0,0 +1,350 @@
<template>
<div class="ui-input" :class="[{ focused, filled }, styl]">
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input">
<div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
<div class="value" ref="passwordMetar"></div>
</div>
<span class="label" ref="label"><slot></slot></span>
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<template v-if="type != 'file'">
<input ref="input"
:type="type"
v-model="v"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
:spellcheck="spellcheck"
@focus="focused = true"
@blur="focused = false">
</template>
<template v-else>
<input ref="input"
type="text"
:value="placeholder"
readonly
@click="chooseFile">
<input ref="file"
type="file"
:value="value"
@change="onChangeFile">
</template>
<div class="suffix" ref="suffix"><slot name="suffix"></slot></div>
</div>
<div class="text"><slot name="text"></slot></div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength');
export default Vue.extend({
props: {
value: {
required: false
},
type: {
type: String,
required: false
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
},
autocomplete: {
required: false
},
spellcheck: {
required: false
},
withPasswordMeter: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
v: this.value,
focused: false,
passwordStrength: '',
styl: 'fill'
};
},
computed: {
filled(): boolean {
return this.v != '' && this.v != null;
},
placeholder(): string {
if (this.type != 'file') return null;
if (this.v == null) return null;
if (typeof this.v == 'string') return this.v;
if (Array.isArray(this.v)) {
return this.v.map(file => file.name).join(', ');
} else {
return this.v.name;
}
}
},
watch: {
value(v) {
this.v = v;
},
v(v) {
this.$emit('input', v);
if (this.withPasswordMeter) {
if (v == '') {
this.passwordStrength = '';
return;
}
const strength = getPasswordStrength(v);
this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
(this.$refs.passwordMetar as any).style.width = `${strength * 100}%`;
}
}
},
inject: {
isCardChild: { default: false }
},
created() {
if (this.isCardChild) {
this.styl = 'line';
}
},
mounted() {
if (this.$refs.prefix) {
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
if (this.$refs.prefix.offsetWidth) {
this.$refs.input.style.paddingLeft = this.$refs.prefix.offsetWidth + 'px';
}
}
if (this.$refs.suffix) {
if (this.$refs.suffix.offsetWidth) {
this.$refs.input.style.paddingRight = this.$refs.suffix.offsetWidth + 'px';
}
}
},
methods: {
focus() {
this.$refs.input.focus();
},
chooseFile() {
this.$refs.file.click();
},
onChangeFile() {
this.v = Array.from((this.$refs.file as any).files);
this.$emit('input', this.v);
this.$emit('change', this.v);
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark, fill)
margin 32px 0
> .icon
position absolute
top 0
left 0
width 24px
text-align center
line-height 32px
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
&:not(:empty) + .input
margin-left 28px
> .input
if !fill
&:before
content ''
display block
position absolute
bottom 0
left 0
right 0
height 1px
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
&:after
content ''
display block
position absolute
bottom 0
left 0
right 0
height 2px
background $theme-color
opacity 0
transform scaleX(0.12)
transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
will-change border opacity transform
> .password-meter
position absolute
top 0
left 0
width 100%
height 100%
border-radius 6px
overflow hidden
opacity 0.3
&[data-strength='']
display none
&[data-strength='low']
> .value
background #d73612
&[data-strength='medium']
> .value
background #d7ca12
&[data-strength='high']
> .value
background #61bb22
> .value
display block
width 0%
height 100%
background transparent
border-radius 6px
transition all 0.1s ease
> .label
position absolute
z-index 1
top fill ? 6px : 0
left 0
pointer-events none
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
transition-duration 0.3s
font-size 16px
line-height 32px
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
pointer-events none
//will-change transform
transform-origin top left
transform scale(1)
> input
display block
width 100%
margin 0
padding 0
font inherit
font-weight fill ? bold : normal
font-size 16px
line-height 32px
color isDark ? #fff : #000
background transparent
border none
border-radius 0
outline none
box-shadow none
if fill
padding 6px 12px
background rgba(#000, 0.035)
border-radius 6px
&[type='file']
display none
> .prefix
> .suffix
display block
position absolute
z-index 1
top 0
font-size 16px
line-height fill ? 44px : 32px
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
pointer-events none
&:empty
display none
> *
display block
min-width 16px
max-width 150px
overflow hidden
white-space nowrap
text-overflow ellipsis
> .prefix
left 0
padding-right 4px
if fill
padding-left 12px
> .suffix
right 0
padding-left 4px
if fill
padding-right 12px
> .text
margin 6px 0
font-size 13px
*
margin 0
&.focused
> .input
if fill
background rgba(#000, 0.05)
else
&:after
opacity 1
transform scaleX(1)
> .label
color $theme-color
&.focused
&.filled
> .input
> .label
top fill ? -24px : -17px
left 0 !important
transform scale(0.75)
.ui-input[data-darkmode]
&.fill
root(true, true)
&:not(.fill)
root(true, false)
.ui-input:not([data-darkmode])
&.fill
root(false, true)
&:not(.fill)
root(false, false)
</style>

View File

@ -0,0 +1,120 @@
<template>
<div
class="ui-radio"
:class="{ disabled, checked }"
:aria-checked="checked"
:aria-disabled="disabled"
@click="toggle"
>
<input type="radio"
:disabled="disabled"
>
<span class="button">
<span></span>
</span>
<span class="label"><slot></slot></span>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
model: {
prop: 'model',
event: 'change'
},
props: {
model: {
type: String,
required: false
},
value: {
type: String,
required: false
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
checked(): boolean {
return this.model === this.value;
}
},
methods: {
toggle() {
this.$emit('change', this.value);
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
display inline-block
margin 32px 32px 32px 0
cursor pointer
transition all 0.3s
> *
user-select none
&.disabled
opacity 0.6
cursor not-allowed
&.checked
> .button
border-color $theme-color
&:after
background-color $theme-color
transform scale(1)
opacity 1
> input
position absolute
width 0
height 0
opacity 0
margin 0
> .button
position absolute
width 20px
height 20px
background none
border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
border-radius 100%
transition inherit
&:after
content ''
display block
position absolute
top 3px
right 3px
bottom 3px
left 3px
border-radius 100%
opacity 0
transform scale(0)
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
> .label
margin-left 28px
display block
font-size 16px
line-height 20px
cursor pointer
.ui-radio[data-darkmode]
root(true)
.ui-radio:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,215 @@
<template>
<div class="ui-select" :class="[{ focused, filled }, styl]">
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input" @click="focus">
<span class="label" ref="label"><slot name="label"></slot></span>
<div class="prefix" ref="prefix"><slot name="prefix"></slot></div>
<select ref="input"
:value="v"
:required="required"
@input="$emit('input', $event.target.value)"
@focus="focused = true"
@blur="focused = false">
<slot></slot>
</select>
<div class="suffix"><slot name="suffix"></slot></div>
</div>
<div class="text"><slot name="text"></slot></div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
value: {
required: false
},
required: {
type: Boolean,
required: false
}
},
data() {
return {
v: this.value,
focused: false,
styl: 'fill'
};
},
computed: {
filled(): boolean {
return this.v != '' && this.v != null;
}
},
watch: {
value(v) {
this.v = v;
}
},
inject: {
isCardChild: { default: false }
},
created() {
if (this.isCardChild) {
this.styl = 'line';
}
},
mounted() {
if (this.$refs.prefix) {
this.$refs.label.style.left = (this.$refs.prefix.offsetLeft + this.$refs.prefix.offsetWidth) + 'px';
}
},
methods: {
focus() {
this.$refs.input.focus();
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark, fill)
margin 32px 0
> .icon
position absolute
top 0
left 0
width 24px
text-align center
line-height 32px
color rgba(#000, 0.54)
&:not(:empty) + .input
margin-left 28px
> .input
display flex
if fill
padding 6px 12px
background rgba(#000, 0.035)
border-radius 6px
else
&:before
content ''
display block
position absolute
bottom 0
left 0
right 0
height 1px
background isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
&:after
content ''
display block
position absolute
bottom 0
left 0
right 0
height 2px
background $theme-color
opacity 0
transform scaleX(0.12)
transition border 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)
will-change border opacity transform
> .label
position absolute
top fill ? 6px : 0
left 0
pointer-events none
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
transition-duration 0.3s
font-size 16px
line-height 32px
color rgba(#000, 0.54)
pointer-events none
//will-change transform
transform-origin top left
transform scale(1)
> select
display block
flex 1
width 100%
padding 0
font inherit
font-weight fill ? bold : normal
font-size 16px
height 32px
color isDark ? #fff : #000
background transparent
border none
border-radius 0
outline none
box-shadow none
*
color #000
> .prefix
> .suffix
display block
align-self center
justify-self center
font-size 16px
line-height 32px
color rgba(#000, 0.54)
pointer-events none
> *
display block
min-width 16px
> .prefix
padding-right 4px
> .suffix
padding-left 4px
> .text
margin 6px 0
font-size 13px
*
margin 0
&.focused
> .input
if fill
background rgba(#000, 0.05)
else
&:after
opacity 1
transform scaleX(1)
> .label
color $theme-color
&.focused
&.filled
> .input
> .label
top fill ? -24px : -17px
left 0 !important
transform scale(0.75)
.ui-select[data-darkmode]
&.fill
root(true, true)
&:not(.fill)
root(true, false)
.ui-select:not([data-darkmode])
&.fill
root(false, true)
&:not(.fill)
root(false, false)
</style>

View File

@ -0,0 +1,135 @@
<template>
<div
class="ui-switch"
:class="{ disabled, checked }"
role="switch"
:aria-checked="checked"
:aria-disabled="disabled"
@click="toggle"
>
<input
type="checkbox"
ref="input"
:disabled="disabled"
@keydown.enter="toggle"
>
<span class="button">
<span></span>
</span>
<span class="label">
<span :aria-hidden="!checked"><slot></slot></span>
<p :aria-hidden="!checked">
<slot name="text"></slot>
</p>
</span>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
computed: {
checked(): boolean {
return this.value;
}
},
methods: {
toggle() {
this.$emit('change', !this.checked);
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark)
display flex
margin 32px 0
cursor pointer
transition all 0.3s
> *
user-select none
&.disabled
opacity 0.6
cursor not-allowed
&.checked
> .button
background-color rgba($theme-color, 0.4)
border-color rgba($theme-color, 0.4)
> *
background-color $theme-color
transform translateX(14px)
> input
position absolute
width 0
height 0
opacity 0
margin 0
> .button
display inline-block
margin 3px 0 0 0
width 34px
height 14px
background isDark ? rgba(#fff, 0.15) : rgba(#000, 0.25)
outline none
border-radius 14px
transition inherit
> *
position absolute
top -3px
left 0
border-radius 100%
transition background-color 0.3s, transform 0.3s
width 20px
height 20px
background-color #fff
box-shadow 0 2px 1px -1px rgba(#000, 0.2), 0 1px 1px 0 rgba(#000, 0.14), 0 1px 3px 0 rgba(#000, 0.12)
> .label
margin-left 8px
display block
font-size 16px
cursor pointer
transition inherit
> span
display block
line-height 20px
color isDark ? #c4ccd2 : rgba(#000, 0.75)
transition inherit
> p
margin 0
//font-size 90%
color isDark ? #78858e : #9daab3
.ui-switch[data-darkmode]
root(true)
.ui-switch:not([data-darkmode])
root(false)
</style>

View File

@ -0,0 +1,174 @@
<template>
<div class="ui-textarea" :class="{ focused, filled }">
<div class="input">
<span class="label" ref="label"><slot></slot></span>
<textarea ref="input"
:value="value"
:required="required"
:readonly="readonly"
:pattern="pattern"
:autocomplete="autocomplete"
@input="$emit('input', $event.target.value)"
@focus="focused = true"
@blur="focused = false">
</textarea>
</div>
<div class="text"><slot name="text"></slot></div>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
const getPasswordStrength = require('syuilo-password-strength');
export default Vue.extend({
props: {
value: {
required: false
},
required: {
type: Boolean,
required: false
},
readonly: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
},
autocomplete: {
type: String,
required: false
}
},
data() {
return {
focused: false,
passwordStrength: ''
}
},
computed: {
filled(): boolean {
return this.value != '' && this.value != null;
}
},
methods: {
focus() {
this.$refs.input.focus();
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
root(isDark, fill)
margin 42px 0 32px 0
> .input
padding 12px
if fill
background rgba(#000, 0.035)
border-radius 6px
else
&:before
content ''
display block
position absolute
top 0
bottom 0
left 0
right 0
background none
border solid 1px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.42)
border-radius 3px
pointer-events none
&:after
content ''
display block
position absolute
top 0
bottom 0
left 0
right 0
background none
border solid 2px $theme-color
border-radius 3px
opacity 0
transition opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)
pointer-events none
> .label
position absolute
top 6px
left 12px
pointer-events none
transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
transition-duration 0.3s
font-size 16px
line-height 32px
color isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
pointer-events none
//will-change transform
transform-origin top left
transform scale(1)
> textarea
display block
width 100%
min-height 100px
padding 0
font inherit
font-weight fill ? bold : normal
font-size 16px
color isDark ? #fff : #000
background transparent
border none
border-radius 0
outline none
box-shadow none
> .text
margin 6px 0
font-size 13px
*
margin 0
&.focused
> .input
if fill
background rgba(#000, 0.05)
else
&:after
opacity 1
> .label
color $theme-color
&.focused
&.filled
> .input
> .label
top -24px
left 0 !important
transform scale(0.75)
.ui-textarea[data-darkmode]
&.fill
root(true, true)
&:not(.fill)
root(true, false)
.ui-textarea:not([data-darkmode])
&.fill
root(false, true)
&:not(.fill)
root(false, false)
</style>

View File

@ -109,6 +109,9 @@ root(isDark)
> .created-at > .created-at
color isDark ? #606984 : #c0c0c0 color isDark ? #606984 : #c0c0c0
> .text
text-align left
.mk-welcome-timeline[data-darkmode] .mk-welcome-timeline[data-darkmode]
root(true) root(true)

View File

@ -49,7 +49,8 @@ export default Vue.extend({
polylinePoints: '', polylinePoints: '',
polygonPoints: '', polygonPoints: '',
headX: null, headX: null,
headY: null headY: null,
clock: null
}; };
}, },
watch: { watch: {
@ -59,6 +60,12 @@ export default Vue.extend({
}, },
created() { created() {
this.draw(); this.draw();
// Vueが何故かWatchを発動させない場合があるので
this.clock = setInterval(this.draw, 1000);
},
beforeDestroy() {
clearInterval(this.clock);
}, },
methods: { methods: {
draw() { draw() {

View File

@ -5,7 +5,8 @@
<div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'"> <div class="mkw-hashtags--body" :data-mobile="platform == 'mobile'">
<p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p> <p class="fetching" v-if="fetching">%fa:spinner .pulse .fw%%i18n:common.loading%<mk-ellipsis/></p>
<div v-else> <p class="empty" v-else-if="stats.length == 0">%fa:exclamation-circle%%i18n:@empty%</p>
<transition-group v-else tag="div" name="chart">
<div v-for="stat in stats" :key="stat.tag"> <div v-for="stat in stats" :key="stat.tag">
<div class="tag"> <div class="tag">
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link> <router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
@ -13,7 +14,7 @@
</div> </div>
<x-chart class="chart" :src="stat.chart"/> <x-chart class="chart" :src="stat.chart"/>
</div> </div>
</div> </transition-group>
</div> </div>
</mk-widget-container> </mk-widget-container>
</div> </div>
@ -65,8 +66,9 @@ export default define({
root(isDark) root(isDark)
.mkw-hashtags--body .mkw-hashtags--body
> .fetching > .fetching
> .empty
margin 0 margin 0
padding 12px 16px padding 16px
text-align center text-align center
color #aaa color #aaa
@ -74,10 +76,13 @@ root(isDark)
margin-right 4px margin-right 4px
> div > div
.chart-move
transition transform 1s ease
> div > div
display flex display flex
align-items center align-items center
padding 16px padding 14px 16px
&:not(:last-child) &:not(:last-child)
border-bottom solid 1px isDark ? #393f4f : #eee border-bottom solid 1px isDark ? #393f4f : #eee

View File

@ -1,6 +1,8 @@
declare const _HOST_: string; declare const _HOST_: string;
declare const _HOSTNAME_: string; declare const _HOSTNAME_: string;
declare const _URL_: string; declare const _URL_: string;
declare const _NAME_: string;
declare const _DESCRIPTION_: string;
declare const _API_URL_: string; declare const _API_URL_: string;
declare const _WS_URL_: string; declare const _WS_URL_: string;
declare const _DOCS_URL_: string; declare const _DOCS_URL_: string;
@ -17,10 +19,13 @@ declare const _VERSION_: string;
declare const _CODENAME_: string; declare const _CODENAME_: string;
declare const _LICENSE_: string; declare const _LICENSE_: string;
declare const _GOOGLE_MAPS_API_KEY_: string; declare const _GOOGLE_MAPS_API_KEY_: string;
declare const _WELCOME_BG_URL_: string;
export const host = _HOST_; export const host = _HOST_;
export const hostname = _HOSTNAME_; export const hostname = _HOSTNAME_;
export const url = _URL_; export const url = _URL_;
export const name = _NAME_;
export const description = _DESCRIPTION_;
export const apiUrl = _API_URL_; export const apiUrl = _API_URL_;
export const wsUrl = _WS_URL_; export const wsUrl = _WS_URL_;
export const docsUrl = _DOCS_URL_; export const docsUrl = _DOCS_URL_;
@ -37,3 +42,4 @@ export const version = _VERSION_;
export const codename = _CODENAME_; export const codename = _CODENAME_;
export const license = _LICENSE_; export const license = _LICENSE_;
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_; export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
export const welcomeBgUrl = _WELCOME_BG_URL_;

View File

@ -21,7 +21,7 @@ export default (os: OS) => opts => {
res(file); res(file);
}; };
window.open(url + '/selectdrive', window.open(url + `/selectdrive?multiple=${o.multiple}`,
'choose_drive_window', 'choose_drive_window',
'height=500, width=800'); 'height=500, width=800');
} }

View File

@ -34,7 +34,8 @@ import MkMessagingRoom from './views/pages/messaging-room.vue';
import MkNote from './views/pages/note.vue'; import MkNote from './views/pages/note.vue';
import MkSearch from './views/pages/search.vue'; import MkSearch from './views/pages/search.vue';
import MkTag from './views/pages/tag.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 * init
@ -62,8 +63,9 @@ init(async (launch) => {
{ path: '/selectdrive', component: MkSelectDrive }, { path: '/selectdrive', component: MkSelectDrive },
{ path: '/search', component: MkSearch }, { path: '/search', component: MkSearch },
{ path: '/tags/:tag', component: MkTag }, { path: '/tags/:tag', component: MkTag },
{ path: '/othello', component: MkOthello }, { path: '/share', component: MkShare },
{ path: '/othello/:game', component: MkOthello }, { path: '/reversi', component: MkReversi },
{ path: '/reversi/:game', component: MkReversi },
{ path: '/@:user', component: MkUser }, { path: '/@:user', component: MkUser },
{ path: '/notes/:note', component: MkNote } { path: '/notes/:note', component: MkNote }
] ]
@ -164,8 +166,8 @@ function registerNotifications(stream: HomeStreamManager) {
setTimeout(n.close.bind(n), 7000); setTimeout(n.close.bind(n), 7000);
}); });
connection.on('othello_invited', matching => { connection.on('reversi_invited', matching => {
const _n = composeNotification('othello_invited', matching); const _n = composeNotification('reversi_invited', matching);
const n = new Notification(_n.title, { const n = new Notification(_n.title, {
body: _n.body, body: _n.body,
icon: _n.icon icon: _n.icon

View File

@ -145,7 +145,7 @@ export default Vue.extend({
(this as any).api('drive/files/update', { (this as any).api('drive/files/update', {
fileId: this.file.id, fileId: this.file.id,
name: name name: name
}) });
}); });
}, },
@ -173,7 +173,9 @@ export default Vue.extend({
}, },
deleteFile() { deleteFile() {
alert('not implemented yet'); (this as any).api('drive/files/delete', {
fileId: this.file.id
});
} }
} }
}); });

View File

@ -118,6 +118,7 @@ export default Vue.extend({
this.connection.on('file_created', this.onStreamDriveFileCreated); this.connection.on('file_created', this.onStreamDriveFileCreated);
this.connection.on('file_updated', this.onStreamDriveFileUpdated); this.connection.on('file_updated', this.onStreamDriveFileUpdated);
this.connection.on('file_deleted', this.onStreamDriveFileDeleted);
this.connection.on('folder_created', this.onStreamDriveFolderCreated); this.connection.on('folder_created', this.onStreamDriveFolderCreated);
this.connection.on('folder_updated', this.onStreamDriveFolderUpdated); this.connection.on('folder_updated', this.onStreamDriveFolderUpdated);
@ -130,6 +131,7 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
this.connection.off('file_created', this.onStreamDriveFileCreated); this.connection.off('file_created', this.onStreamDriveFileCreated);
this.connection.off('file_updated', this.onStreamDriveFileUpdated); this.connection.off('file_updated', this.onStreamDriveFileUpdated);
this.connection.off('file_deleted', this.onStreamDriveFileDeleted);
this.connection.off('folder_created', this.onStreamDriveFolderCreated); this.connection.off('folder_created', this.onStreamDriveFolderCreated);
this.connection.off('folder_updated', this.onStreamDriveFolderUpdated); this.connection.off('folder_updated', this.onStreamDriveFolderUpdated);
(this as any).os.streams.driveStream.dispose(this.connectionId); (this as any).os.streams.driveStream.dispose(this.connectionId);
@ -167,6 +169,10 @@ export default Vue.extend({
} }
}, },
onStreamDriveFileDeleted(fileId) {
this.removeFile(fileId);
},
onStreamDriveFolderCreated(folder) { onStreamDriveFolderCreated(folder) {
this.addFolder(folder, true); this.addFolder(folder, true);
}, },

View File

@ -1,7 +1,7 @@
<template> <template>
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy"> <mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="$destroy">
<span slot="header" :class="$style.header">%fa:gamepad%%i18n:@game%</span> <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> </mk-window>
</template> </template>
@ -18,8 +18,8 @@ export default Vue.extend({
computed: { computed: {
popout(): string { popout(): string {
return this.game return this.game
? `${url}/othello/${this.game.id}` ? `${url}/reversi/${this.game.id}`
: `${url}/othello`; : `${url}/reversi`;
} }
} }
}); });

View File

@ -84,7 +84,7 @@ const defaultDesktopHomeWidgets = {
'calendar', 'calendar',
'activity', 'activity',
'rss', 'rss',
'trends', 'hashtags',
'photo-stream', 'photo-stream',
'version' 'version'
], ],

View File

@ -50,6 +50,7 @@ import * as XDraggable from 'vuedraggable';
import getKao from '../../../common/scripts/get-kao'; import getKao from '../../../common/scripts/get-kao';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import { host } from '../../../config';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -57,7 +58,25 @@ export default Vue.extend({
MkVisibilityChooser 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() { data() {
return { return {
@ -117,6 +136,10 @@ export default Vue.extend({
}, },
mounted() { mounted() {
if (this.initialText) {
this.text = this.initialText;
}
if (this.reply && this.reply.user.host != null) { if (this.reply && this.reply.user.host != null) {
this.text = `@${this.reply.user.username}@${this.reply.user.host} `; this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
} }
@ -129,6 +152,7 @@ export default Vue.extend({
// 自分は除外 // 自分は除外
if (this.$store.state.i.username == x.username && x.host == null) return; if (this.$store.state.i.username == x.username && x.host == null) return;
if (this.$store.state.i.username == x.username && x.host == host) return;
// 重複は除外 // 重複は除外
if (this.text.indexOf(`${mention} `) != -1) return; if (this.text.indexOf(`${mention} `) != -1) return;
@ -139,6 +163,7 @@ export default Vue.extend({
this.$nextTick(() => { this.$nextTick(() => {
// 書きかけの投稿を復元 // 書きかけの投稿を復元
if (!this.instant) {
const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId]; const draft = JSON.parse(localStorage.getItem('drafts') || '{}')[this.draftId];
if (draft) { if (draft) {
this.text = draft.data.text; this.text = draft.data.text;
@ -151,6 +176,7 @@ export default Vue.extend({
} }
this.$emit('change-attached-media', this.files); this.$emit('change-attached-media', this.files);
} }
}
this.$nextTick(() => this.watch()); this.$nextTick(() => this.watch());
}); });
@ -347,6 +373,8 @@ export default Vue.extend({
}, },
saveDraft() { saveDraft() {
if (this.instant) return;
const data = JSON.parse(localStorage.getItem('drafts') || '{}'); const data = JSON.parse(localStorage.getItem('drafts') || '{}');
data[this.draftId] = { data[this.draftId] = {

View File

@ -45,6 +45,7 @@
<mk-switch v-model="darkmode" text="%i18n:@dark-mode%"/> <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.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.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> </div>
<mk-switch v-model="$store.state.settings.showPostFormOnTopOfTl" @change="onChangeShowPostFormOnTopOfTl" text="%i18n:@post-form-on-timeline%"/> <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%"/> <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 value: v
}); });
}, },
onChangeILikeSushi(v) {
this.$store.dispatch('settings/set', {
key: 'iLikeSushi',
value: v
});
},
onChangeGradientWindowHeader(v) { onChangeGradientWindowHeader(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'gradientWindowHeader', key: 'gradientWindowHeader',

View File

@ -56,23 +56,23 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
this.connection.on('othello_invited', this.onOthelloInvited); this.connection.on('reversi_invited', this.onReversiInvited);
this.connection.on('othello_no_invites', this.onOthelloNoInvites); this.connection.on('reversi_no_invites', this.onReversiNoInvites);
} }
}, },
beforeDestroy() { beforeDestroy() {
if (this.$store.getters.isSignedIn) { if (this.$store.getters.isSignedIn) {
this.connection.off('othello_invited', this.onOthelloInvited); this.connection.off('reversi_invited', this.onReversiInvited);
this.connection.off('othello_no_invites', this.onOthelloNoInvites); this.connection.off('reversi_no_invites', this.onReversiNoInvites);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
} }
}, },
methods: { methods: {
onOthelloInvited() { onReversiInvited() {
this.hasGameInvitations = true; this.hasGameInvitations = true;
}, },
onOthelloNoInvites() { onReversiNoInvites() {
this.hasGameInvitations = false; this.hasGameInvitations = false;
}, },

View File

@ -5,7 +5,7 @@
<div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq"> <div class="gqpwvtwtprsbmnssnbicggtwqhmylhnq">
<template v-if="edit"> <template v-if="edit">
<header> <header>
<select v-model="widgetAdderSelected"> <select v-model="widgetAdderSelected" @change="addWidget">
<option value="profile">%i18n:common.widgets.profile%</option> <option value="profile">%i18n:common.widgets.profile%</option>
<option value="analog-clock">%i18n:common.widgets.analog-clock%</option> <option value="analog-clock">%i18n:common.widgets.analog-clock%</option>
<option value="calendar">%i18n:common.widgets.calendar%</option> <option value="calendar">%i18n:common.widgets.calendar%</option>
@ -30,21 +30,16 @@
<option value="nav">%i18n:common.widgets.nav%</option> <option value="nav">%i18n:common.widgets.nav%</option>
<option value="tips">%i18n:common.widgets.tips%</option> <option value="tips">%i18n:common.widgets.tips%</option>
</select> </select>
<button @click="addWidget">%i18n:@add%</button>
</header> </header>
<x-draggable <x-draggable
:list="column.widgets" :list="column.widgets"
:options="{ handle: '.handle', animation: 150 }" :options="{ animation: 150 }"
@sort="onWidgetSort" @sort="onWidgetSort"
> >
<div v-for="widget in column.widgets" class="customize-container" :key="widget.id"> <div v-for="widget in column.widgets" class="customize-container" :key="widget.id" @contextmenu.stop.prevent="widgetFunc(widget.id)">
<header> <button class="remove" @click="removeWidget(widget)">%fa:times%</button>
<span class="handle">%fa:bars%</span>{{ widget.name }}<button class="remove" @click="removeWidget(widget)">%fa:times%</button>
</header>
<div @click="widgetFunc(widget.id)">
<component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/> <component :is="`mkw-${widget.name}`" :widget="widget" :ref="widget.id" :is-customize-mode="true" platform="deck"/>
</div> </div>
</div>
</x-draggable> </x-draggable>
</template> </template>
<template v-else> <template v-else>
@ -121,6 +116,8 @@ export default Vue.extend({
data: {} data: {}
} }
}); });
this.widgetAdderSelected = null;
}, },
removeWidget(widget) { removeWidget(widget) {
@ -142,6 +139,13 @@ export default Vue.extend({
root(isDark) root(isDark)
.gqpwvtwtprsbmnssnbicggtwqhmylhnq .gqpwvtwtprsbmnssnbicggtwqhmylhnq
> header
padding 16px
> *
width 100%
padding 4px
.widget, .customize-container .widget, .customize-container
margin 8px margin 8px
@ -149,7 +153,21 @@ root(isDark)
margin-top 0 margin-top 0
.customize-container .customize-container
background #fff cursor move
> *:not(.remove)
pointer-events none
> .remove
position absolute
z-index 1
top 8px
right 8px
width 32px
height 32px
color #fff
background rgba(#000, 0.7)
border-radius 4px
> header > header
color isDark ? #fff : #000 color isDark ? #fff : #000

View File

@ -1,6 +1,6 @@
<template> <template>
<component :is="ui ? 'mk-ui' : 'div'"> <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> </component>
</template> </template>
@ -33,7 +33,7 @@ export default Vue.extend({
Progress.start(); Progress.start();
this.fetching = true; this.fetching = true;
(this as any).api('othello/games/show', { (this as any).api('reversi/games/show', {
gameId: this.$route.params.game gameId: this.$route.params.game
}).then(game => { }).then(game => {
this.game = game; this.game = game;
@ -43,7 +43,7 @@ export default Vue.extend({
}); });
}, },
onGamed(game) { onGamed(game) {
history.pushState(null, null, '/othello/' + game.id); history.pushState(null, null, '/reversi/' + game.id);
} }
} }
}); });

View 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>

View File

@ -1,59 +1,80 @@
<template> <template>
<div class="mk-welcome"> <div class="mk-welcome">
<img ref="pointer" class="pointer" src="/assets/pointer.png" alt="">
<button @click="dark"> <button @click="dark">
<template v-if="$store.state.device.darkmode">%fa:moon%</template> <template v-if="$store.state.device.darkmode">%fa:moon%</template>
<template v-else>%fa:R moon%</template> <template v-else>%fa:R moon%</template>
</button> </button>
<main v-if="about" class="about"> <div class="body" :style="{ backgroundImage: `url('${ welcomeBgUrl }')` }">
<article> <div class="container">
<h1>%i18n:common.about-title%</h1> <main>
<p v-html="'%i18n:common.about%'"></p> <div class="about">
<span class="gotit" @click="about = false">%i18n:@gotit%</span> <h1 v-if="name">{{ name }}</h1>
</article> <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>
</div>
<div class="login">
<mk-signin/>
</div>
</main> </main>
<main v-else class="index"> <div class="info">
<img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" alt="Misskey"> <span>%i18n:common.misskey% <b>{{ host }}</b></span>
<p class="desc"><b>%i18n:common.misskey%</b> - <span @click="about = true">%i18n:@about%</span></p> <span class="stats" v-if="stats">
<p class="account"> <span>%fa:user% {{ stats.originalUsersCount | number }}</span>
<button class="signup" @click="signup">%i18n:@signup-button%</button> <span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
<button class="signin" @click="signin">%i18n:@signin-button%</button> </span>
</p> </div>
<mk-nav class="nav"/>
</div>
<mk-forkit class="forkit"/>
<img src="assets/title.dark.svg" alt="Misskey">
</div>
<div class="tl"> <div class="tl">
<header>%fa:comments R% %i18n:@timeline%<div><span></span><span></span><span></span></div></header>
<mk-welcome-timeline/> <mk-welcome-timeline/>
</div> </div>
</main>
<mk-forkit/>
<footer>
<div>
<mk-nav :class="$style.nav"/>
<p class="c">{{ copyright }}</p>
</div>
</footer>
<modal name="signup" width="500px" height="auto" scrollable> <modal name="signup" width="500px" height="auto" scrollable>
<header :class="$style.signupFormHeader">%i18n:@signup%</header> <header :class="$style.signupFormHeader">%i18n:@signup%</header>
<mk-signup :class="$style.signupForm"/> <mk-signup :class="$style.signupForm"/>
</modal> </modal>
<modal name="signin" width="500px" height="auto" scrollable>
<header :class="$style.signinFormHeader">%i18n:@signin%</header>
<mk-signin :class="$style.signinForm"/>
</modal>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { copyright } from '../../../config'; import { host, name, description, copyright, welcomeBgUrl } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
about: false, stats: null,
copyright copyright,
welcomeBgUrl,
host,
name,
description,
pointerInterval: null
}; };
}, },
created() {
(this as any).api('stats').then(stats => {
this.stats = stats;
});
},
mounted() {
this.point();
this.pointerInterval = setInterval(this.point, 100);
},
beforeDestroy() {
clearInterval(this.pointerInterval);
},
methods: { methods: {
point() {
const x = this.$refs.signup.getBoundingClientRect();
this.$refs.pointer.style.top = x.top + x.height + 'px';
this.$refs.pointer.style.left = x.left + 'px';
},
signup() { signup() {
this.$modal.show('signup'); this.$modal.show('signup');
}, },
@ -80,13 +101,20 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
@import '~const.styl' @import '~const.styl'
@import url(https://fonts.googleapis.com/earlyaccess/notosansjp.css);
root(isDark) root(isDark)
display flex
min-height 100vh min-height 100vh
background-image isDark ? url('/assets/welcome-bg.dark.svg') : url('/assets/welcome-bg.light.svg')
background-size cover > .pointer
background-position center display block
position absolute
z-index 1
top 0
right 0
width 180px
margin 0 0 0 -180px
transform rotateY(180deg) translateX(-10px) translateY(-48px)
pointer-events none
> button > button
position fixed position fixed
@ -95,141 +123,118 @@ root(isDark)
left 0 left 0
padding 16px padding 16px
font-size 18px font-size 18px
color isDark ? #fff : #555 color #fff
> main display none // TODO
> .body
flex 1 flex 1
padding 64px 0 0 0 padding 64px 0 0 0
text-align center text-align center
background #578394
background-position center
background-size cover
&.about &:before
font-family 'Noto Sans JP' content ''
color isDark ? #fff : #627574 display block
position absolute
top 0
left 0
right 0
bottom 0
background rgba(#000, 0.5)
> article > .forkit
max-width 700px
margin 42px auto 0 auto
padding 64px 82px
background isDark ? #282C37 : #fff
box-shadow 0 8px 32px rgba(#000, 0.15)
> h1
margin 0
font-weight 900
> p
margin 20px 0
line-height 2em
> .gotit
color $theme-color
cursor pointer
&:hover
text-decoration underline
&.index
color isDark ? #9aa4b3 : #555
> img
width 350px
> .desc
margin -12px 0 24px 0
color isDark ? #fff : #555
> span
color $theme-color
cursor pointer
&:hover
text-decoration underline
> .account
margin 8px 0
line-height 2em
button
padding 8px 16px
font-size inherit
.signup
color $theme-color
border solid 2px $theme-color
border-radius 4px
&:focus
box-shadow 0 0 0 3px rgba($theme-color, 0.2)
&:hover
color $theme-color-foreground
background $theme-color
&:active
color $theme-color-foreground
background darken($theme-color, 10%)
border-color darken($theme-color, 10%)
.signin
&:hover
color isDark ? #fff : #000
> .tl
margin 32px auto 0 auto
width 410px
text-align left
background isDark ? #313543 : #fff
border-radius 8px
box-shadow 0 8px 32px rgba(#000, 0.15)
overflow hidden
> header
z-index 1
padding 12px 16px
color isDark ? #e3e5e8 : #888d94
box-shadow 0 1px 0px rgba(#000, 0.1)
> div
position absolute position absolute
top 0 top 0
right 0 right 0
padding inherit
> span > img
display inline-block position absolute
height 11px bottom 16px
width 11px right 16px
margin-left 6px width 150px
border-radius 100%
vertical-align middle
&:nth-child(1) > .container
background #5BCC8B $aboutWidth = 380px
$loginWidth = 340px
$width = $aboutWidth + $loginWidth
&:nth-child(2) > main
background #E6BB46 display flex
margin auto
width $width
border-radius 8px
overflow hidden
box-shadow 0 2px 8px rgba(#000, 0.3)
&:nth-child(3) > .about
background #DF7065 width $aboutWidth
color #444
background #fff
> .mk-welcome-timeline > h1
max-height 350px margin 0 0 16px 0
overflow auto padding 32px 32px 0 32px
color #444
> footer > img
font-size 12px width 170px
color isDark ? #949ea5 : #737c82 vertical-align bottom
> div > .powerd-by
margin 0 auto margin 16px
padding 64px
text-align center
> .c
margin 16px 0 0 0
font-size 10px
opacity 0.7 opacity 0.7
> .desc
margin 0
padding 0 32px 16px 32px
> a
display inline-block
margin 0 0 32px 0
font-weight bold
> .login
width $loginWidth
padding 16px 32px 32px 32px
background #f5f5f5
> .info
margin 16px auto
padding 12px
width $width
font-size 14px
color #fff
background rgba(#000, 0.2)
border-radius 8px
> .stats
margin-left 16px
padding-left 16px
border-left solid 1px #fff
> *
margin-right 16px
> .nav
display block
margin 16px 0
font-size 14px
color #fff
> .tl
margin 0
width 410px
height 100vh
text-align left
background isDark ? #313543 : #fff
> *
max-height 100%
overflow auto
.mk-welcome[data-darkmode] .mk-welcome[data-darkmode]
root(true) root(true)

View File

@ -11,7 +11,7 @@ import { DriveStreamManager } from './common/scripts/streaming/drive';
import { ServerStatsStreamManager } from './common/scripts/streaming/server-stats'; import { ServerStatsStreamManager } from './common/scripts/streaming/server-stats';
import { NotesStatsStreamManager } from './common/scripts/streaming/notes-stats'; import { NotesStatsStreamManager } from './common/scripts/streaming/notes-stats';
import { MessagingIndexStreamManager } from './common/scripts/streaming/messaging-index'; 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 Err from './common/views/components/connect-failed.vue';
import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline'; import { LocalTimelineStreamManager } from './common/scripts/streaming/local-timeline';
@ -108,7 +108,7 @@ export default class MiOS extends EventEmitter {
serverStatsStream: ServerStatsStreamManager; serverStatsStream: ServerStatsStreamManager;
notesStatsStream: NotesStatsStreamManager; notesStatsStream: NotesStatsStreamManager;
messagingIndexStream: MessagingIndexStreamManager; messagingIndexStream: MessagingIndexStreamManager;
othelloStream: OthelloStreamManager; reversiStream: ReversiStreamManager;
} = { } = {
localTimelineStream: null, localTimelineStream: null,
globalTimelineStream: null, globalTimelineStream: null,
@ -116,7 +116,7 @@ export default class MiOS extends EventEmitter {
serverStatsStream: null, serverStatsStream: null,
notesStatsStream: null, notesStatsStream: null,
messagingIndexStream: 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.globalTimelineStream = new GlobalTimelineStreamManager(this, this.store.state.i);
this.streams.driveStream = new DriveStreamManager(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.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 //#endregion

View File

@ -18,7 +18,7 @@ export default (os) => (opts) => {
} }
}).$mount(); }).$mount();
vm.$once('cancel', recover); vm.$once('cancel', recover);
vm.$once('note', recover); vm.$once('posted', recover);
document.body.appendChild(vm.$el); document.body.appendChild(vm.$el);
(vm as any).focus(); (vm as any).focus();
}; };

View File

@ -2,17 +2,11 @@
* Mobile Client * Mobile Client
*/ */
import Vue from 'vue';
import VueRouter from 'vue-router'; import VueRouter from 'vue-router';
import { MdCard, MdButton, MdField, MdMenu, MdList, MdSwitch, MdSubheader, MdDialog, MdDialogAlert, MdRadio } from 'vue-material/dist/components';
import 'vue-material/dist/vue-material.min.css';
import 'vue-material/dist/theme/default.css';
// Style // Style
import './style.styl'; import './style.styl';
import '../../element.scss'; import '../../element.scss';
import '../../md.scss';
import init from '../init'; import init from '../init';
@ -41,18 +35,9 @@ import MkFavorites from './views/pages/favorites.vue';
import MkUserLists from './views/pages/user-lists.vue'; import MkUserLists from './views/pages/user-lists.vue';
import MkUserList from './views/pages/user-list.vue'; import MkUserList from './views/pages/user-list.vue';
import MkSettings from './views/pages/settings.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';
Vue.use(MdCard); import MkShare from './views/pages/share.vue';
Vue.use(MdButton);
Vue.use(MdField);
Vue.use(MdMenu);
Vue.use(MdList);
Vue.use(MdSwitch);
Vue.use(MdSubheader);
Vue.use(MdDialog);
Vue.use(MdDialogAlert);
Vue.use(MdRadio);
/** /**
* init * init
@ -88,8 +73,10 @@ init((launch) => {
{ path: '/i/drive/file/:file', component: MkDrive }, { path: '/i/drive/file/:file', component: MkDrive },
{ path: '/selectdrive', component: MkSelectDrive }, { path: '/selectdrive', component: MkSelectDrive },
{ path: '/search', component: MkSearch }, { path: '/search', component: MkSearch },
{ path: '/othello', name: 'othello', component: MkOthello }, { path: '/tags/:tag', component: MkTag },
{ 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', component: MkUser },
{ path: '/@:user/followers', component: MkFollowers }, { path: '/@:user/followers', component: MkFollowers },
{ path: '/@:user/following', component: MkFollowing }, { path: '/@:user/following', component: MkFollowing },

View File

@ -10,9 +10,6 @@ html
height 100% height 100%
background #ececed !important background #ececed !important
// for md
transition none !important
&[data-darkmode] &[data-darkmode]
background #191B22 !important background #191B22 !important

View File

@ -34,15 +34,10 @@
</div> </div>
<div class="menu"> <div class="menu">
<div> <div>
<a :href="`${file.url}?download`" :download="file.name"> <a :href="`${file.url}?download`" :download="file.name">%fa:download%%i18n:@download%</a>
%fa:download%%i18n:@download% <button @click="rename">%fa:pencil-alt%%i18n:@rename%</button>
</a> <button @click="move">%fa:R folder-open%%i18n:@move%</button>
<button @click="rename"> <button @click="del">%fa:trash-alt R%%i18n:@delete%</button>
%fa:pencil-alt%%i18n:@rename%
</button>
<button @click="move">
%fa:R folder-open%%i18n:@move%
</button>
</div> </div>
</div> </div>
<div class="exif" v-show="exif"> <div class="exif" v-show="exif">
@ -112,6 +107,13 @@ export default Vue.extend({
}); });
}); });
}, },
del() {
(this as any).api('drive/files/delete', {
fileId: this.file.id
}).then(() => {
this.browser.cd(this.file.folderId, true);
});
},
showCreatedAt() { showCreatedAt() {
alert(new Date(this.file.createdAt).toLocaleString()); alert(new Date(this.file.createdAt).toLocaleString());
}, },

View File

@ -100,6 +100,7 @@ export default Vue.extend({
this.connection.on('file_created', this.onStreamDriveFileCreated); this.connection.on('file_created', this.onStreamDriveFileCreated);
this.connection.on('file_updated', this.onStreamDriveFileUpdated); this.connection.on('file_updated', this.onStreamDriveFileUpdated);
this.connection.on('file_deleted', this.onStreamDriveFileDeleted);
this.connection.on('folder_created', this.onStreamDriveFolderCreated); this.connection.on('folder_created', this.onStreamDriveFolderCreated);
this.connection.on('folder_updated', this.onStreamDriveFolderUpdated); this.connection.on('folder_updated', this.onStreamDriveFolderUpdated);
@ -118,6 +119,7 @@ export default Vue.extend({
beforeDestroy() { beforeDestroy() {
this.connection.off('file_created', this.onStreamDriveFileCreated); this.connection.off('file_created', this.onStreamDriveFileCreated);
this.connection.off('file_updated', this.onStreamDriveFileUpdated); this.connection.off('file_updated', this.onStreamDriveFileUpdated);
this.connection.off('file_deleted', this.onStreamDriveFileDeleted);
this.connection.off('folder_created', this.onStreamDriveFolderCreated); this.connection.off('folder_created', this.onStreamDriveFolderCreated);
this.connection.off('folder_updated', this.onStreamDriveFolderUpdated); this.connection.off('folder_updated', this.onStreamDriveFolderUpdated);
(this as any).os.streams.driveStream.dispose(this.connectionId); (this as any).os.streams.driveStream.dispose(this.connectionId);
@ -136,6 +138,10 @@ export default Vue.extend({
} }
}, },
onStreamDriveFileDeleted(fileId) {
this.removeFile(fileId);
},
onStreamDriveFolderCreated(folder) { onStreamDriveFolderCreated(folder) {
this.addFolder(folder, true); this.addFolder(folder, true);
}, },

View File

@ -22,6 +22,7 @@ import userTimeline from './user-timeline.vue';
import userListTimeline from './user-list-timeline.vue'; import userListTimeline from './user-list-timeline.vue';
import activity from './activity.vue'; import activity from './activity.vue';
import widgetContainer from './widget-container.vue'; import widgetContainer from './widget-container.vue';
import postForm from './post-form.vue';
Vue.component('mk-ui', ui); Vue.component('mk-ui', ui);
Vue.component('mk-note', note); 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-user-list-timeline', userListTimeline);
Vue.component('mk-activity', activity); Vue.component('mk-activity', activity);
Vue.component('mk-widget-container', widgetContainer); Vue.component('mk-widget-container', widgetContainer);
Vue.component('mk-post-form', postForm);

View File

@ -46,6 +46,7 @@ import * as XDraggable from 'vuedraggable';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import getKao from '../../../common/scripts/get-kao'; import getKao from '../../../common/scripts/get-kao';
import parse from '../../../../../text/parse'; import parse from '../../../../../text/parse';
import { host } from '../../../config';
export default Vue.extend({ export default Vue.extend({
components: { components: {
@ -53,7 +54,25 @@ export default Vue.extend({
MkVisibilityChooser 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() { data() {
return { return {
@ -111,6 +130,10 @@ export default Vue.extend({
}, },
mounted() { mounted() {
if (this.initialText) {
this.text = this.initialText;
}
if (this.reply && this.reply.user.host != null) { if (this.reply && this.reply.user.host != null) {
this.text = `@${this.reply.user.username}@${this.reply.user.host} `; this.text = `@${this.reply.user.username}@${this.reply.user.host} `;
} }
@ -123,6 +146,7 @@ export default Vue.extend({
// 自分は除外 // 自分は除外
if (this.$store.state.i.username == x.username && x.host == null) return; if (this.$store.state.i.username == x.username && x.host == null) return;
if (this.$store.state.i.username == x.username && x.host == host) return;
// 重複は除外 // 重複は除外
if (this.text.indexOf(`${mention} `) != -1) return; if (this.text.indexOf(`${mention} `) != -1) return;
@ -250,8 +274,10 @@ export default Vue.extend({
visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined, visibleUserIds: this.visibility == 'specified' ? this.visibleUsers.map(u => u.id) : undefined,
viaMobile: viaMobile viaMobile: viaMobile
}).then(data => { }).then(data => {
this.$emit('note'); this.$emit('posted');
this.$nextTick(() => {
this.$destroy(); this.$destroy();
});
}).catch(err => { }).catch(err => {
this.posting = false; this.posting = false;
}); });

View File

@ -45,8 +45,8 @@ export default Vue.extend({
this.connection = (this as any).os.stream.getConnection(); this.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
this.connection.on('othello_invited', this.onOthelloInvited); this.connection.on('reversi_invited', this.onReversiInvited);
this.connection.on('othello_no_invites', this.onOthelloNoInvites); this.connection.on('reversi_no_invites', this.onReversiNoInvites);
const ago = (new Date().getTime() - new Date(this.$store.state.i.lastUsedAt).getTime()) / 1000; const ago = (new Date().getTime() - new Date(this.$store.state.i.lastUsedAt).getTime()) / 1000;
const isHisasiburi = ago >= 3600; const isHisasiburi = ago >= 3600;
@ -98,16 +98,16 @@ export default Vue.extend({
}, },
beforeDestroy() { beforeDestroy() {
if (this.$store.getters.isSignedIn) { if (this.$store.getters.isSignedIn) {
this.connection.off('othello_invited', this.onOthelloInvited); this.connection.off('reversi_invited', this.onReversiInvited);
this.connection.off('othello_no_invites', this.onOthelloNoInvites); this.connection.off('reversi_no_invites', this.onReversiNoInvites);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
} }
}, },
methods: { methods: {
onOthelloInvited() { onReversiInvited() {
this.hasGameInvitation = true; this.hasGameInvitation = true;
}, },
onOthelloNoInvites() { onReversiNoInvites() {
this.hasGameInvitation = false; this.hasGameInvitation = false;
} }
} }

View File

@ -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/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><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 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>
<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> <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.connection = (this as any).os.stream.getConnection();
this.connectionId = (this as any).os.stream.use(); this.connectionId = (this as any).os.stream.use();
this.connection.on('othello_invited', this.onOthelloInvited); this.connection.on('reversi_invited', this.onReversiInvited);
this.connection.on('othello_no_invites', this.onOthelloNoInvites); this.connection.on('reversi_no_invites', this.onReversiNoInvites);
} }
}, },
beforeDestroy() { beforeDestroy() {
if (this.$store.getters.isSignedIn) { if (this.$store.getters.isSignedIn) {
this.connection.off('othello_invited', this.onOthelloInvited); this.connection.off('reversi_invited', this.onReversiInvited);
this.connection.off('othello_no_invites', this.onOthelloNoInvites); this.connection.off('reversi_no_invites', this.onReversiNoInvites);
(this as any).os.stream.dispose(this.connectionId); (this as any).os.stream.dispose(this.connectionId);
} }
}, },
@ -83,10 +83,10 @@ export default Vue.extend({
if (query == null || query == '') return; if (query == null || query == '') return;
this.$router.push('/search?q=' + encodeURIComponent(query)); this.$router.push('/search?q=' + encodeURIComponent(query));
}, },
onOthelloInvited() { onReversiInvited() {
this.hasGameInvitation = true; this.hasGameInvitation = true;
}, },
onOthelloNoInvites() { onReversiNoInvites() {
this.hasGameInvitation = false; this.hasGameInvitation = false;
}, },
dark() { dark() {

View File

@ -1,7 +1,7 @@
<template> <template>
<mk-ui> <mk-ui>
<span slot="header">%fa:gamepad%オセロ</span> <span slot="header">%fa:gamepad%リバーシ</span>
<mk-othello v-if="!fetching" :init-game="game" @gamed="onGamed"/> <mk-reversi v-if="!fetching" :init-game="game" @gamed="onGamed"/>
</mk-ui> </mk-ui>
</template> </template>
@ -23,7 +23,7 @@ export default Vue.extend({
this.fetch(); this.fetch();
}, },
mounted() { mounted() {
document.title = 'Misskey オセロ'; document.title = 'Misskey リバーシ';
document.documentElement.style.background = '#fff'; document.documentElement.style.background = '#fff';
}, },
methods: { methods: {
@ -33,7 +33,7 @@ export default Vue.extend({
Progress.start(); Progress.start();
this.fetching = true; this.fetching = true;
(this as any).api('othello/games/show', { (this as any).api('reversi/games/show', {
gameId: this.$route.params.game gameId: this.$route.params.game
}).then(game => { }).then(game => {
this.game = game; this.game = game;
@ -43,7 +43,7 @@ export default Vue.extend({
}); });
}, },
onGamed(game) { onGamed(game) {
history.pushState(null, null, '/othello/' + game.id); history.pushState(null, null, '/reversi/' + game.id);
} }
} }
}); });

View File

@ -1,132 +1,85 @@
<template> <template>
<mk-ui> <mk-ui>
<span slot="header">%fa:cog%%i18n:@settings%</span> <span slot="header">%fa:cog%%i18n:@settings%</span>
<main> <main :data-darkmode="$store.state.device.darkmode">
<p v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></p> <div class="signin-as" v-html="'%i18n:@signed-in-as%'.replace('{}', '<b>' + name + '</b>')"></div>
<div> <div>
<x-profile/> <x-profile/>
<md-card> <ui-card>
<md-card-header> <div slot="title">%fa:palette% %i18n:@design%</div>
<div class="md-title">%fa:palette% %i18n:@design%</div>
</md-card-header> <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>
<md-card-content>
<div> <div>
<md-switch v-model="darkmode">%i18n:@dark-mode%</md-switch> <div>%i18n:@timeline%</div>
<ui-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</ui-switch>
<ui-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</ui-switch>
<ui-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</ui-switch>
</div> </div>
<div> <div>
<md-switch v-model="$store.state.settings.circleIcons" @change="onChangeCircleIcons">%i18n:@circle-icons%</md-switch> <div>%i18n:@post-style%</div>
<ui-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</ui-radio>
<ui-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</ui-radio>
</div> </div>
</ui-card>
<div> <ui-card>
<div class="md-body-2">%i18n:@timeline%</div> <div slot="title">%fa:cog% %i18n:@behavior%</div>
<ui-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</ui-switch>
<ui-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</ui-switch>
<ui-switch v-model="loadRawImages">%i18n:@load-raw-images%</ui-switch>
<ui-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</ui-switch>
<ui-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</ui-switch>
</ui-card>
<div> <ui-card>
<md-switch v-model="$store.state.settings.showReplyTarget" @change="onChangeShowReplyTarget">%i18n:@show-reply-target%</md-switch> <div slot="title">%fa:language% %i18n:@lang%</div>
</div>
<div> <ui-select v-model="lang" placeholder="%i18n:@auto%">
<md-switch v-model="$store.state.settings.showMyRenotes" @change="onChangeShowMyRenotes">%i18n:@show-my-renotes%</md-switch> <optgroup label="%i18n:@recommended%">
</div> <option value="">%i18n:@auto%</option>
</optgroup>
<div> <optgroup label="%i18n:@specify-language%">
<md-switch v-model="$store.state.settings.showRenotedMyNotes" @change="onChangeShowRenotedMyNotes">%i18n:@show-renoted-my-notes%</md-switch> <option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</option>
</div> </optgroup>
</div> </ui-select>
<span>%fa:info-circle% %i18n:@lang-tip%</span>
</ui-card>
<div> <ui-card>
<div class="md-body-2">%i18n:@post-style%</div> <div slot="title">%fa:B twitter% %i18n:@twitter%</div>
<md-radio v-model="postStyle" value="standard">%i18n:@post-style-standard%</md-radio>
<md-radio v-model="postStyle" value="smart">%i18n:@post-style-smart%</md-radio>
</div>
</md-card-content>
</md-card>
<md-card>
<md-card-header>
<div class="md-title">%fa:cog% %i18n:@behavior%</div>
</md-card-header>
<md-card-content>
<div>
<md-switch v-model="$store.state.settings.fetchOnScroll" @change="onChangeFetchOnScroll">%i18n:@fetch-on-scroll%</md-switch>
</div>
<div>
<md-switch v-model="$store.state.settings.disableViaMobile" @change="onChangeDisableViaMobile">%i18n:@disable-via-mobile%</md-switch>
</div>
<div>
<md-switch v-model="loadRawImages">%i18n:@load-raw-images%</md-switch>
</div>
<div>
<md-switch v-model="$store.state.settings.loadRemoteMedia" @change="onChangeLoadRemoteMedia">%i18n:@load-remote-media%</md-switch>
</div>
<div>
<md-switch v-model="lightmode">%i18n:@i-am-under-limited-internet%</md-switch>
</div>
</md-card-content>
</md-card>
<md-card>
<md-card-header>
<div class="md-title">%fa:language% %i18n:@lang%</div>
</md-card-header>
<md-card-content>
<md-field>
<md-select v-model="lang" placeholder="%i18n:@auto%">
<md-optgroup label="%i18n:@recommended%">
<md-option value="">%i18n:@auto%</md-option>
</md-optgroup>
<md-optgroup label="%i18n:@specify-language%">
<md-option v-for="x in langs" :value="x[0]" :key="x[0]">{{ x[1] }}</md-option>
</md-optgroup>
</md-select>
</md-field>
<span class="md-helper-text">%fa:info-circle% %i18n:@lang-tip%</span>
</md-card-content>
</md-card>
<md-card>
<md-card-header>
<div class="md-title">%fa:B twitter% %i18n:@twitter%</div>
</md-card-header>
<md-card-content>
<p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> <p class="account" v-if="$store.state.i.twitter"><a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
<p> <p>
<a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a> <a :href="`${apiUrl}/connect/twitter`" target="_blank">{{ $store.state.i.twitter ? '%i18n:@twitter-reconnect%' : '%i18n:@twitter-connect%' }}</a>
<span v-if="$store.state.i.twitter"> or </span> <span v-if="$store.state.i.twitter"> or </span>
<a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a> <a :href="`${apiUrl}/disconnect/twitter`" target="_blank" v-if="$store.state.i.twitter">%i18n:@twitter-disconnect%</a>
</p> </p>
</md-card-content> </ui-card>
</md-card>
<md-card> <ui-card>
<md-card-header> <div slot="title">%fa:sync-alt% %i18n:@update%</div>
<div class="md-title">%fa:sync-alt% %i18n:@update%</div>
</md-card-header>
<md-card-content>
<div>%i18n:@version% <i>{{ version }}</i></div> <div>%i18n:@version% <i>{{ version }}</i></div>
<template v-if="latestVersion !== undefined"> <template v-if="latestVersion !== undefined">
<div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div> <div>%i18n:@latest-version% <i>{{ latestVersion ? latestVersion : version }}</i></div>
</template> </template>
<md-button class="md-raised md-primary" @click="checkForUpdate" :disabled="checkingForUpdate"> <ui-button @click="checkForUpdate" :disabled="checkingForUpdate">
<template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template> <template v-if="checkingForUpdate">%i18n:@update-checking%<mk-ellipsis/></template>
<template v-else>%i18n:@check-for-updates%</template> <template v-else>%i18n:@check-for-updates%</template>
</md-button> </ui-button>
</md-card-content> </ui-card>
</md-card>
</div> </div>
<p><small>ver {{ version }} ({{ codename }})</small></p>
<footer>
<small>ver {{ version }} ({{ codename }})</small>
</footer>
</main> </main>
</mk-ui> </mk-ui>
</template> </template>
@ -222,6 +175,13 @@ export default Vue.extend({
}); });
}, },
onChangeILikeSushi(v) {
this.$store.dispatch('settings/set', {
key: 'iLikeSushi',
value: v
});
},
onChangeShowReplyTarget(v) { onChangeShowReplyTarget(v) {
this.$store.dispatch('settings/set', { this.$store.dispatch('settings/set', {
key: 'showReplyTarget', key: 'showReplyTarget',
@ -267,20 +227,22 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
root(isDark) root(isDark)
padding 0 16px
margin 0 auto margin 0 auto
max-width 500px max-width 500px
width 100% width 100%
> div > .signin-as
> * margin 16px
margin-bottom 16px padding 16px
> p
display block
margin 24px
text-align center text-align center
color isDark ? #cad2da : #a2a9b1 color isDark ? #49ab63 : #2c662d
background isDark ? #273c34 : #fcfff5
box-shadow 0 3px 1px -2px rgba(#000, 0.2), 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12)
> footer
margin 16px
text-align center
color isDark ? #c9d2e0 : #888
main[data-darkmode] main[data-darkmode]
root(true) root(true)

View File

@ -1,62 +1,49 @@
<template> <template>
<md-card> <ui-card>
<md-card-header> <div slot="title">%fa:user% %i18n:@title%</div>
<div class="md-title">%fa:pencil-alt% %i18n:@title%</div>
</md-card-header>
<md-card-content> <ui-form :disabled="saving">
<md-field> <ui-input v-model="name" :max="30">
<label>%i18n:@name%</label> <span>%i18n:@name%</span>
<md-input v-model="name" :disabled="saving" md-counter="30"/> </ui-input>
</md-field>
<md-field> <ui-input v-model="username" readonly>
<label>%i18n:@account%</label> <span>%i18n:@account%</span>
<span class="md-prefix">@</span> <span slot="prefix">@</span>
<md-input v-model="username" readonly></md-input> <span slot="suffix">@{{ host }}</span>
<span class="md-suffix">@{{ host }}</span> </ui-input>
</md-field>
<md-field> <ui-input v-model="location">
<md-icon>%fa:map-marker-alt%</md-icon> <span>%i18n:@location%</span>
<label>%i18n:@location%</label> <span slot="prefix">%fa:map-marker-alt%</span>
<md-input v-model="location" :disabled="saving"/> </ui-input>
</md-field>
<md-field> <ui-input v-model="birthday" type="date">
<md-icon>%fa:birthday-cake%</md-icon> <span>%i18n:@birthday%</span>
<label>%i18n:@birthday%</label> <span slot="prefix">%fa:birthday-cake%</span>
<md-input type="date" v-model="birthday" :disabled="saving"/> </ui-input>
</md-field>
<md-field> <ui-textarea v-model="description" :max="500">
<label>%i18n:@description%</label> <span>%i18n:@description%</span>
<md-textarea v-model="description" :disabled="saving" md-counter="500"/> </ui-textarea>
</md-field>
<md-field> <ui-input type="file" @change="onAvatarChange">
<label>%i18n:@avatar%</label> <span>%i18n:@avatar%</span>
<md-file @md-change="onAvatarChange"/> <span slot="icon">%fa:image%</span>
</md-field> <span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
</ui-input>
<md-field> <ui-input type="file" @change="onBannerChange">
<label>%i18n:@banner%</label> <span>%i18n:@banner%</span>
<md-file @md-change="onBannerChange"/> <span slot="icon">%fa:image%</span>
</md-field> <span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
</ui-input>
<md-dialog-alert <ui-switch v-model="isCat">%i18n:@is-cat%</ui-switch>
:md-active.sync="uploading"
md-content="%18n:!@uploading%"/>
<div> <ui-button @click="save">%i18n:@save%</ui-button>
<md-switch v-model="isCat">%i18n:@is-cat%</md-switch> </ui-form>
</div> </ui-card>
</md-card-content>
<md-card-actions>
<md-button class="md-primary" :disabled="saving" @click="save">%i18n:@save%</md-button>
</md-card-actions>
</md-card>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -77,7 +64,8 @@ export default Vue.extend({
isBot: false, isBot: false,
isCat: false, isCat: false,
saving: false, saving: false,
uploading: false avatarUploading: false,
bannerUploading: false
}; };
}, },
@ -95,7 +83,7 @@ export default Vue.extend({
methods: { methods: {
onAvatarChange([file]) { onAvatarChange([file]) {
this.uploading = true; this.avatarUploading = true;
const data = new FormData(); const data = new FormData();
data.append('file', file); data.append('file', file);
@ -108,16 +96,16 @@ export default Vue.extend({
.then(response => response.json()) .then(response => response.json())
.then(f => { .then(f => {
this.avatarId = f.id; this.avatarId = f.id;
this.uploading = false; this.avatarUploading = false;
}) })
.catch(e => { .catch(e => {
this.uploading = false; this.avatarUploading = false;
alert('%18n:!@upload-failed%'); alert('%18n:!@upload-failed%');
}); });
}, },
onBannerChange([file]) { onBannerChange([file]) {
this.uploading = true; this.bannerUploading = true;
const data = new FormData(); const data = new FormData();
data.append('file', file); data.append('file', file);
@ -130,10 +118,10 @@ export default Vue.extend({
.then(response => response.json()) .then(response => response.json())
.then(f => { .then(f => {
this.bannerId = f.id; this.bannerId = f.id;
this.uploading = false; this.bannerUploading = false;
}) })
.catch(e => { .catch(e => {
this.uploading = false; this.bannerUploading = false;
alert('%18n:!@upload-failed%'); alert('%18n:!@upload-failed%');
}); });
}, },

View 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>

View File

@ -1,57 +1,26 @@
<template> <template>
<div class="signup"> <div class="signup">
<h1>Misskeyをはじめる</h1> <h1>📦 始めましょう</h1>
<p>いつでもどこからでもMisskeyを利用できますもちろん無料です</p>
<div class="form">
<p>新規登録</p>
<div>
<mk-signup/> <mk-signup/>
</div> </div>
</div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
export default Vue.extend({ export default Vue.extend({});
mounted() {
document.documentElement.style.background = '#293946';
}
});
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.signup .signup
padding 16px padding 32px
margin 0 auto margin 0 auto
max-width 500px max-width 500px
h1 h1
margin 0 margin 0
padding 8px padding 8px 0 0 0
font-size 1.5em font-size 1.5em
font-weight normal font-weight bold
color #c3c6ca color #444
& + p
margin 0 0 16px 0
padding 0 8px 0 8px
color #949fa9
.form
background #fff
border solid 1px rgba(#000, 0.2)
border-radius 8px
overflow hidden
> p
margin 0
padding 12px 20px
color #555
background #f5f5f5
border-bottom solid 1px #ddd
> div
padding 16px
</style> </style>

View File

@ -0,0 +1,81 @@
<template>
<mk-ui>
<span slot="header">%fa:hashtag%{{ $route.params.tag }}</span>
<main>
<p v-if="!fetching && empty">%fa:search%{{ q }}に関する投稿は見つかりませんでした</p>
<mk-notes ref="timeline" :more="existMore ? more : null"/>
</main>
</mk-ui>
</template>
<script lang="ts">
import Vue from 'vue';
import Progress from '../../../common/scripts/loading';
const limit = 20;
export default Vue.extend({
data() {
return {
fetching: true,
moreFetching: false,
existMore: false,
offset: 0,
empty: false
};
},
watch: {
$route: 'fetch'
},
mounted() {
this.$nextTick(() => {
this.fetch();
});
},
methods: {
fetch() {
this.fetching = true;
Progress.start();
(this.$refs.timeline as any).init(() => new Promise((res, rej) => {
(this as any).api('notes/search_by_tag', {
limit: limit + 1,
offset: this.offset,
tag: this.$route.params.tag
}).then(notes => {
if (notes.length == 0) this.empty = true;
if (notes.length == limit + 1) {
notes.pop();
this.existMore = true;
}
res(notes);
this.fetching = false;
Progress.done();
}, rej);
}));
},
more() {
this.offset += limit;
const promise = (this as any).api('notes/search_by_tag', {
limit: limit + 1,
offset: this.offset,
tag: this.$route.params.tag
});
promise.then(notes => {
if (notes.length == limit + 1) {
notes.pop();
} else {
this.existMore = false;
}
notes.forEach(n => (this.$refs.timeline as any).append(n));
this.moreFetching = false;
});
return promise;
}
}
});
</script>

View File

@ -1,28 +1,22 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<div> <div>
<h1><b>Misskey</b>へようこそ</h1> <img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" alt="Misskey">
<p>Twitter風ミニブログSNSMisskeyへようこそ共有したいことを投稿したりタイムラインでみんなの投稿を読むこともできます<br><a href="/signup">アカウントを作成する</a></p> <p class="host">{{ host }}</p>
<div class="form"> <div class="about">
<p>%fa:lock% ログイン</p> <h2>{{ name || 'unidentified' }}</h2>
<div> <p v-html="description || '%i18n:common.about%'"></p>
<form @submit.prevent="onSubmit"> <router-link class="signup" to="/signup">新規登録</router-link>
<input v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" placeholder="ユーザー名" autofocus required @change="onUsernameChange"/>
<input v-model="password" type="password" placeholder="パスワード" required/>
<input v-if="user && user.twoFactorEnabled" v-model="token" type="number" placeholder="トークン" required/>
<button type="submit" :disabled="signing">{{ signing ? 'ログインしています' : 'ログイン' }}</button>
</form>
<div>
<a :href="`${apiUrl}/signin/twitter`">Twitterでログイン</a>
</div>
</div> </div>
<div class="login">
<mk-signin :with-avatar="false"/>
</div> </div>
<div class="tl"> <div class="tl">
<p>%fa:comments R% タイムラインを見てみる</p>
<mk-welcome-timeline/> <mk-welcome-timeline/>
</div> </div>
<div class="users"> <div class="stats" v-if="stats">
<mk-avatar class="avatar" v-for="user in users" :key="user.id" :user="user"/> <span>%fa:user% {{ stats.originalUsersCount | number }}</span>
<span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span>
</div> </div>
<footer> <footer>
<small>{{ copyright }}</small> <small>{{ copyright }}</small>
@ -33,107 +27,72 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import { apiUrl, copyright } from '../../../config'; import { apiUrl, copyright, host, name, description } from '../../../config';
export default Vue.extend({ export default Vue.extend({
data() { data() {
return { return {
signing: false,
user: null,
username: '',
password: '',
token: '',
apiUrl, apiUrl,
copyright, copyright,
users: [] stats: null,
host,
name,
description
}; };
}, },
mounted() { created() {
(this as any).api('users', { (this as any).api('stats').then(stats => {
sort: '+follower', this.stats = stats;
limit: 20
}).then(users => {
this.users = users;
}); });
},
methods: {
onUsernameChange() {
(this as any).api('users/show', {
username: this.username
}).then(user => {
this.user = user;
});
},
onSubmit() {
this.signing = true;
(this as any).api('signin', {
username: this.username,
password: this.password,
token: this.user && this.user.twoFactorEnabled ? this.token : undefined
}).then(() => {
location.reload();
}).catch(() => {
alert('something happened');
this.signing = false;
});
}
} }
}); });
</script> </script>
<style lang="stylus" scoped> <style lang="stylus" scoped>
.welcome .welcome
background linear-gradient(to bottom, #1e1d65, #bd6659) text-align center
//background #fff
> div > div
padding 16px padding 32px
margin 0 auto margin 0 auto
max-width 500px max-width 500px
h1 > img
margin 0 display block
padding 8px max-width 200px
font-size 1.5em margin 0 auto
font-weight normal
color #cacac3
& + p > .host
margin 0 0 16px 0 display block
padding 0 8px 0 8px text-align center
color #949fa9 padding 6px 12px
line-height 32px
font-weight bold
color #333
background rgba(#000, 0.035)
border-radius 6px
.form > .about
margin-bottom 16px margin-top 16px
padding 16px
color #555
background #fff background #fff
border solid 1px rgba(#000, 0.2) border-radius 6px
border-radius 8px
overflow hidden > h2
margin 0
> p > p
margin 0 margin 8px
padding 12px 20px
color #555
background #f5f5f5
border-bottom solid 1px #ddd
> div > .signup
font-weight bold
> .login
margin 16px 0
> form > form
padding 16px
border-bottom solid 1px #ddd
input
display block
padding 12px
margin 0 0 16px 0
width 100%
font-size 1em
color rgba(#000, 0.7)
background #fff
outline none
border solid 1px #ddd
border-radius 4px
button button
display block display block
@ -156,40 +115,27 @@ export default Vue.extend({
border-color #444 border-color #444
box-shadow 0 1px 3px rgba(#000, 0.075), inset 0 0 5px rgba(#000, 0.2) box-shadow 0 1px 3px rgba(#000, 0.075), inset 0 0 5px rgba(#000, 0.2)
> div
padding 16px
text-align center
> .tl > .tl
background #fff > *
border solid 1px rgba(#000, 0.2)
border-radius 8px
overflow hidden
> p
margin 0
padding 12px 20px
color #555
background #f5f5f5
border-bottom solid 1px #ddd
> .mk-welcome-timeline
max-height 300px max-height 300px
border-radius 6px
overflow auto overflow auto
-webkit-overflow-scrolling touch
> .users > .stats
margin 12px 0 0 0 margin 16px 0
padding 8px
font-size 14px
color #444
background rgba(#000, 0.1)
border-radius 6px
> * > *
display inline-block margin 0 8px
margin 4px
width 38px
height 38px
border-radius 6px
> footer > footer
text-align center text-align center
color #fff color #444
> small > small
display block display block

View File

@ -56,7 +56,7 @@ export default define({
left 92px left 92px
margin 0 margin 0
line-height 100px line-height 100px
color #fff !important // !important is for md color #fff
font-weight bold font-weight bold
text-shadow 0 0 8px rgba(#000, 0.5) text-shadow 0 0 8px rgba(#000, 0.5)

View File

@ -18,7 +18,8 @@ const defaultSettings = {
showRenotedMyNotes: true, showRenotedMyNotes: true,
loadRemoteMedia: true, loadRemoteMedia: true,
disableViaMobile: false, disableViaMobile: false,
memo: null memo: null,
iLikeSushi: false
}; };
const defaultDeviceSettings = { const defaultDeviceSettings = {

View File

@ -4,11 +4,39 @@
"start_url": "/", "start_url": "/",
"display": "standalone", "display": "standalone",
"background_color": "#313a42", "background_color": "#313a42",
"icons": { "icons": [
"16": "/assets/favicon/16.png", {
"32": "/assets/favicon/32.png", "src": "/assets/icons/16.png",
"64": "/assets/favicon/64.png", "sizes": "16x16",
"128": "/assets/favicon/128.png", "type": "image/png"
"256": "/assets/favicon/256.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}"
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -19,9 +19,10 @@ import generateVars from '../vars';
const langs = Object.keys(locales); 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 id = param.type.match(/^id\((.+?)\)|^id/);
const entity = param.type.match(/^entity\((.+?)\)/); const entity = param.type.match(/^entity\((.+?)\)/);
const isObject = /^object/.test(param.type); const isObject = /^object/.test(param.type);
@ -57,7 +58,7 @@ const parseParam = param => {
return param; return param;
}; };
const sortParams = params => { const sortParams = (params: Array<{name: string}>) => {
params.sort((a, b) => { params.sort((a, b) => {
if (a.name < b.name) if (a.name < b.name)
return -1; return -1;
@ -68,14 +69,15 @@ const sortParams = params => {
return params; return params;
}; };
const extractDefs = params => { // WIP type
let defs = []; const extractDefs = (params: any[]) => {
let defs: any[] = [];
params.forEach(param => { params.forEach(param => {
if (param.def) { if (param.def) {
defs.push({ defs.push({
name: param.defName, 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); const childDefs = extractDefs(param.def);
@ -109,8 +111,10 @@ gulp.task('doc:api:endpoints', async () => {
path: ep.endpoint path: ep.endpoint
}, },
desc: ep.desc, desc: ep.desc,
// @ts-ignore
params: sortParams(ep.params.map(p => parseParam(p))), params: sortParams(ep.params.map(p => parseParam(p))),
paramDefs: extractDefs(ep.params), paramDefs: extractDefs(ep.params),
// @ts-ignore
res: ep.res ? sortParams(ep.res.map(p => parseParam(p))) : null, res: ep.res ? sortParams(ep.res.map(p => parseParam(p))) : null,
resDefs: ep.res ? extractDefs(ep.res) : null, resDefs: ep.res ? extractDefs(ep.res) : null,
}; };
@ -155,7 +159,8 @@ gulp.task('doc:api:entities', async () => {
const vars = { const vars = {
name: entity.name, name: entity.name,
desc: entity.desc, 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), propDefs: extractDefs(entity.props),
}; };
langs.forEach(lang => { langs.forEach(lang => {

View File

@ -8,8 +8,8 @@ import * as glob from 'glob';
import * as gulp from 'gulp'; import * as gulp from 'gulp';
import * as pug from 'pug'; import * as pug from 'pug';
import * as mkdirp from 'mkdirp'; import * as mkdirp from 'mkdirp';
import stylus = require('gulp-stylus'); const stylus = require('gulp-stylus');
import cssnano = require('gulp-cssnano'); const cssnano = require('gulp-cssnano');
import I18nReplacer from '../../build/i18n'; import I18nReplacer from '../../build/i18n';
import fa from '../../build/fa'; import fa from '../../build/fa';

View File

@ -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['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; vars['config'] = config;

View File

@ -1,13 +0,0 @@
/* SEE: https://vuematerial.io/themes/configuration */
@import '../const.json';
@import "~vue-material/dist/theme/engine";
@include md-register-theme("default", (
primary: $themeColor,
accent: $themeColor
));
@import "~vue-material/dist/components/MdButton/theme";
@import "~vue-material/dist/components/MdField/theme";

View File

@ -15,6 +15,9 @@ export type Source = {
*/ */
url: string; url: string;
}; };
name?: string;
description?: string;
welcome_bg_url?: string;
url: string; url: string;
port: number; port: number;
https?: { [x: string]: string }; https?: { [x: string]: string };
@ -57,7 +60,7 @@ export type Source = {
hook_secret: string; hook_secret: string;
username: string; username: string;
}; };
othello_ai?: { reversi_ai?: {
id: string; id: string;
i: string; i: string;
}; };

View File

@ -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);

View File

@ -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);
});
}

Some files were not shown because too many files have changed in this diff Show More