summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorこぴなたみぽ <Syuilotan@yahoo.co.jp>2017-06-15 04:52:33 +0900
committerGitHub <noreply@github.com>2017-06-15 04:52:33 +0900
commitcb4b120548a53700bca8baf52bfe42fe8e5f95ef (patch)
treeb0e4116756c0a9d6b7a06f4a5012fc91b6b5bc75
parentchore(package): update @types/mongodb to version 2.2.4 (diff)
parentMerge pull request #552 from syuilo/greenkeeper/@types/node-7.0.31 (diff)
downloadmisskey-cb4b120548a53700bca8baf52bfe42fe8e5f95ef.tar.gz
misskey-cb4b120548a53700bca8baf52bfe42fe8e5f95ef.tar.bz2
misskey-cb4b120548a53700bca8baf52bfe42fe8e5f95ef.zip
Merge branch 'master' into greenkeeper/@types/mongodb-2.2.4
-rw-r--r--.gitignore1
-rw-r--r--README.md4
-rw-r--r--locales/en.yml11
-rw-r--r--locales/ja.yml9
-rw-r--r--package.json299
-rw-r--r--src/api/common/read-messaging-message.ts64
-rw-r--r--src/api/endpoints/messaging/messages.ts31
-rw-r--r--src/api/endpoints/messaging/messages/create.ts4
-rw-r--r--src/api/endpoints/meta.ts10
-rw-r--r--src/api/endpoints/users/search.ts2
-rw-r--r--src/api/models/messaging-message.ts5
-rw-r--r--src/api/stream/messaging.ts42
-rw-r--r--src/api/stream/server.ts20
-rw-r--r--src/api/streaming.ts6
-rw-r--r--src/index.ts6
-rw-r--r--src/utils/stats.ts27
-rw-r--r--src/web/app/base.pug2
-rw-r--r--src/web/app/base.styl7
-rw-r--r--src/web/app/common/scripts/bytes-to-size.js4
-rw-r--r--src/web/app/common/scripts/get-cat.js1
-rw-r--r--src/web/app/common/scripts/get-kao.js5
-rw-r--r--src/web/app/common/scripts/home-stream.js18
-rw-r--r--src/web/app/common/scripts/messaging-stream.js52
-rw-r--r--src/web/app/common/scripts/server-stream.js14
-rw-r--r--src/web/app/common/scripts/stream.js15
-rw-r--r--src/web/app/common/scripts/text-compiler.js2
-rw-r--r--src/web/app/common/tags/activity-table.tag17
-rw-r--r--src/web/app/common/tags/error.tag36
-rw-r--r--src/web/app/common/tags/messaging/message.tag5
-rw-r--r--src/web/app/common/tags/messaging/room.tag93
-rw-r--r--src/web/app/desktop/script.js14
-rw-r--r--src/web/app/desktop/tags/home-widgets/activity.tag205
-rw-r--r--src/web/app/desktop/tags/home-widgets/server.tag510
-rw-r--r--src/web/app/desktop/tags/home.tag1
-rw-r--r--src/web/app/desktop/tags/index.js1
-rw-r--r--src/web/app/desktop/tags/notifications.tag5
-rw-r--r--src/web/app/desktop/tags/post-form.tag10
-rw-r--r--src/web/app/init.js13
-rw-r--r--src/web/app/mobile/tags/drive.tag2
-rw-r--r--src/web/app/mobile/tags/page/drive.tag2
-rw-r--r--src/web/app/mobile/tags/post-form.tag10
-rw-r--r--src/web/app/mobile/tags/ui-header.tag41
-rw-r--r--src/web/app/mobile/tags/ui-nav.tag55
-rw-r--r--src/web/app/mobile/tags/user.tag2
44 files changed, 1316 insertions, 367 deletions
diff --git a/.gitignore b/.gitignore
index 6573bcb1e1..42b1bde94f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ npm-debug.log
*.pem
run.bat
api-docs.json
+package-lock.json
diff --git a/README.md b/README.md
index 2192eff51c..9d2d38149c 100644
--- a/README.md
+++ b/README.md
@@ -16,10 +16,10 @@ Key features
----------------------------------------------------------------
* Automatically updated timeline
* Private messages
-* Free 1GB storage
+* Free 1GB storage for each all users
* Mobile device support (smartphone, tablet, etc)
* Web API for third-party applications
-* Twitter integration
+* No ads
and more! You can touch with your own eyes at https://misskey.xyz/.
diff --git a/locales/en.yml b/locales/en.yml
index 74ff52979c..a01392809e 100644
--- a/locales/en.yml
+++ b/locales/en.yml
@@ -45,6 +45,8 @@ common:
mk-messaging-room:
empty: "No conversations"
+ more: "More"
+ no-history: "There is no more history"
resize-form: "Drag to resize"
new-message: "New message"
@@ -52,7 +54,7 @@ common:
no-apps: "No apps"
mk-error:
- title: "Cannot connect to the server"
+ title: "Unable to connect to the server"
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
thanks: "Thank you for using Misskey."
@@ -219,7 +221,7 @@ desktop:
attach-media-from-local: "Attach media from your pc"
attach-media-from-drive: "Attach media from the drive"
attach-cancel: "Cancel attachment"
- insert-the-cat: "Insert a cat"
+ insert-a-kao: "v(‘ω’)v"
create-poll: "Create a poll"
text-remain: "{} chars remaining"
@@ -243,8 +245,13 @@ desktop:
title: "Notifications"
settings: "Notification settings"
+ mk-server-home-widget:
+ title: "Server info"
+ toggle: "Toggle views"
+
mk-activity-home-widget:
title: "Activity"
+ toggle: "Toggle views"
mk-user-recommendation-home-widget:
title: "Recommended users"
diff --git a/locales/ja.yml b/locales/ja.yml
index dda0da5856..d55e690e91 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -45,6 +45,8 @@ common:
mk-messaging-room:
empty: "このユーザーと話したことはありません"
+ more: "もっと読む"
+ no-history: "これより過去の履歴はありません"
resize-form: "ドラッグしてフォームの広さを調整"
new-message: "新しいメッセージがあります"
@@ -219,7 +221,7 @@ desktop:
attach-media-from-local: "PCからメディアを添付"
attach-media-from-drive: "ドライブからメディアを添付"
attach-cancel: "添付取り消し"
- insert-the-cat: "猫挿入"
+ insert-a-kao: "v(‘ω’)v"
create-poll: "投票を作成"
text-remain: "のこり{}文字"
@@ -243,8 +245,13 @@ desktop:
title: "通知"
settings: "通知の設定"
+ mk-server-home-widget:
+ title: "サーバー情報"
+ toggle: "表示を切り替え"
+
mk-activity-home-widget:
title: "アクティビティ"
+ toggle: "表示を切り替え"
mk-user-recommendation-home-widget:
title: "おすすめユーザー"
diff --git a/package.json b/package.json
index aaa5359801..fb703b5548 100644
--- a/package.json
+++ b/package.json
@@ -1,150 +1,153 @@
{
- "name": "misskey",
- "author": "syuilo <i@syuilo.com>",
- "version": "0.0.2027",
- "license": "MIT",
- "description": "A miniblog-based SNS",
- "bugs": "https://github.com/syuilo/misskey/issues",
- "repository": "https://github.com/syuilo/misskey.git",
- "main": "./built/index.js",
- "private": true,
- "scripts": {
- "config": "node ./tools/init.js",
- "start": "node ./built",
- "debug": "DEBUG=misskey:* node ./built",
- "swagger": "node ./swagger.js",
- "build": "gulp build",
- "rebuild": "gulp rebuild",
- "clean": "gulp clean",
- "cleanall": "gulp cleanall",
- "lint": "gulp lint",
- "test": "gulp test"
- },
- "devDependencies": {
- "@types/bcryptjs": "2.4.0",
- "@types/body-parser": "1.16.3",
- "@types/chai": "4.0.0",
- "@types/chai-http": "0.0.30",
- "@types/chalk": "0.4.31",
- "@types/compression": "0.0.33",
- "@types/cors": "2.8.1",
- "@types/debug": "0.0.29",
- "@types/deep-equal": "1.0.0",
- "@types/elasticsearch": "5.0.13",
- "@types/event-stream": "3.3.31",
- "@types/express": "4.0.35",
- "@types/gm": "1.17.31",
- "@types/gulp": "4.0.3",
- "@types/gulp-mocha": "0.0.30",
- "@types/gulp-rename": "0.0.32",
- "@types/gulp-replace": "0.0.30",
- "@types/gulp-tslint": "3.6.31",
- "@types/gulp-typescript": "2.13.0",
- "@types/gulp-uglify": "0.0.30",
- "@types/gulp-util": "3.0.31",
- "@types/inquirer": "0.0.34",
- "@types/is-root": "1.0.0",
- "@types/is-url": "1.2.28",
- "@types/js-yaml": "3.5.30",
- "@types/mocha": "2.2.41",
- "@types/mongodb": "2.2.4",
- "@types/monk": "1.0.5",
- "@types/morgan": "1.7.32",
- "@types/ms": "0.7.29",
- "@types/multer": "0.0.34",
- "@types/node": "7.0.29",
- "@types/ratelimiter": "2.1.28",
- "@types/redis": "2.6.0",
- "@types/request": "0.0.43",
- "@types/rimraf": "0.0.28",
- "@types/riot": "2.6.2",
- "@types/serve-favicon": "2.2.28",
- "@types/uuid": "3.0.0",
- "@types/webpack": "2.2.15",
- "@types/webpack-stream": "3.2.7",
- "@types/websocket": "0.0.33",
- "chai": "4.0.2",
- "chai-http": "3.0.0",
- "css-loader": "0.28.4",
- "event-stream": "3.3.4",
- "gulp": "3.9.1",
- "gulp-cssnano": "2.1.2",
- "gulp-imagemin": "3.3.0",
- "gulp-mocha": "4.3.1",
- "gulp-pug": "3.3.0",
- "gulp-rename": "1.2.2",
- "gulp-replace": "0.5.4",
- "gulp-tslint": "8.1.1",
- "gulp-typescript": "3.1.7",
- "gulp-uglify": "3.0.0",
- "gulp-util": "3.0.8",
- "mocha": "3.4.2",
- "riot-tag-loader": "1.0.0",
- "string-replace-webpack-plugin": "0.1.3",
- "style-loader": "0.18.2",
- "stylus": "0.54.5",
- "stylus-loader": "3.0.1",
- "swagger-jsdoc": "1.9.4",
- "tslint": "5.4.3",
- "uglify-es": "3.0.15",
- "uglify-es-webpack-plugin": "0.0.2",
- "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
- "webpack": "2.6.1"
- },
- "dependencies": {
- "accesses": "2.5.0",
- "animejs": "2.0.2",
- "autwh": "0.0.1",
- "bcryptjs": "2.4.3",
- "body-parser": "1.17.2",
- "cafy": "2.4.0",
- "chalk": "1.1.3",
- "compression": "1.6.2",
- "cors": "2.8.3",
- "cropperjs": "1.0.0-rc.2",
- "crypto": "0.0.3",
- "debug": "2.6.8",
- "deep-equal": "1.0.1",
- "deepcopy": "0.6.3",
- "download": "6.2.2",
- "elasticsearch": "13.0.1",
- "escape-regexp": "0.0.1",
- "express": "4.15.3",
- "file-type": "5.0.0",
- "fuckadblock": "3.2.1",
- "gm": "1.23.0",
- "inquirer": "3.1.0",
- "is-root": "1.0.0",
- "is-url": "1.2.2",
- "js-yaml": "3.8.4",
- "mongodb": "2.2.28",
- "monk": "6.0.0",
- "morgan": "1.8.2",
- "ms": "2.0.0",
- "multer": "1.3.0",
- "nprogress": "0.2.0",
- "page": "1.7.1",
- "pictograph": "2.0.4",
- "prominence": "0.2.0",
- "pug": "2.0.0-rc.2",
- "ratelimiter": "3.0.3",
- "recaptcha-promise": "0.1.2",
- "reconnecting-websocket": "3.0.5",
- "redis": "2.7.1",
- "request": "2.81.0",
- "rimraf": "2.6.1",
- "riot": "3.5.1",
- "rndstr": "1.0.0",
- "s-age": "1.1.0",
- "serve-favicon": "2.4.3",
- "summaly": "2.0.3",
- "syuilo-password-strength": "0.0.1",
- "tcp-port-used": "0.1.2",
- "textarea-caret": "3.0.2",
- "ts-node": "3.0.6",
- "typescript": "2.3.4",
- "uuid": "3.0.1",
- "vhost": "3.0.2",
- "websocket": "1.0.24"
- }
+ "name": "misskey",
+ "author": "syuilo <i@syuilo.com>",
+ "version": "0.0.2097",
+ "license": "MIT",
+ "description": "A miniblog-based SNS",
+ "bugs": "https://github.com/syuilo/misskey/issues",
+ "repository": "https://github.com/syuilo/misskey.git",
+ "main": "./built/index.js",
+ "private": true,
+ "scripts": {
+ "config": "node ./tools/init.js",
+ "start": "node ./built",
+ "debug": "DEBUG=misskey:* node ./built",
+ "swagger": "node ./swagger.js",
+ "build": "gulp build",
+ "rebuild": "gulp rebuild",
+ "clean": "gulp clean",
+ "cleanall": "gulp cleanall",
+ "lint": "gulp lint",
+ "test": "gulp test"
+ },
+ "devDependencies": {
+ "@types/bcryptjs": "2.4.0",
+ "@types/body-parser": "1.16.3",
+ "@types/chai": "4.0.0",
+ "@types/chai-http": "0.0.30",
+ "@types/chalk": "0.4.31",
+ "@types/compression": "0.0.33",
+ "@types/cors": "2.8.1",
+ "@types/debug": "0.0.29",
+ "@types/deep-equal": "1.0.0",
+ "@types/elasticsearch": "5.0.13",
+ "@types/event-stream": "3.3.31",
+ "@types/express": "4.0.35",
+ "@types/gm": "1.17.31",
+ "@types/gulp": "4.0.3",
+ "@types/gulp-mocha": "0.0.30",
+ "@types/gulp-rename": "0.0.32",
+ "@types/gulp-replace": "0.0.30",
+ "@types/gulp-tslint": "3.6.31",
+ "@types/gulp-typescript": "2.13.0",
+ "@types/gulp-uglify": "0.0.30",
+ "@types/gulp-util": "3.0.31",
+ "@types/inquirer": "0.0.34",
+ "@types/is-root": "1.0.0",
+ "@types/is-url": "1.2.28",
+ "@types/js-yaml": "3.5.30",
+ "@types/mocha": "2.2.41",
+ "@types/mongodb": "2.2.4",
+ "@types/monk": "1.0.5",
+ "@types/morgan": "1.7.32",
+ "@types/ms": "0.7.29",
+ "@types/multer": "0.0.34",
+ "@types/node": "7.0.31",
+ "@types/ratelimiter": "2.1.28",
+ "@types/redis": "2.6.0",
+ "@types/request": "0.0.43",
+ "@types/rimraf": "0.0.28",
+ "@types/riot": "2.6.2",
+ "@types/serve-favicon": "2.2.28",
+ "@types/uuid": "3.0.0",
+ "@types/webpack": "2.2.15",
+ "@types/webpack-stream": "3.2.7",
+ "@types/websocket": "0.0.33",
+ "chai": "4.0.2",
+ "chai-http": "3.0.0",
+ "css-loader": "0.28.4",
+ "event-stream": "3.3.4",
+ "gulp": "3.9.1",
+ "gulp-cssnano": "2.1.2",
+ "gulp-imagemin": "3.3.0",
+ "gulp-mocha": "4.3.1",
+ "gulp-pug": "3.3.0",
+ "gulp-rename": "1.2.2",
+ "gulp-replace": "0.5.4",
+ "gulp-tslint": "8.1.1",
+ "gulp-typescript": "3.1.7",
+ "gulp-uglify": "3.0.0",
+ "gulp-util": "3.0.8",
+ "mocha": "3.4.2",
+ "riot-tag-loader": "1.0.0",
+ "string-replace-webpack-plugin": "0.1.3",
+ "style-loader": "0.18.2",
+ "stylus": "0.54.5",
+ "stylus-loader": "3.0.1",
+ "swagger-jsdoc": "1.9.4",
+ "tslint": "5.4.3",
+ "uglify-es": "3.0.15",
+ "uglify-es-webpack-plugin": "0.0.2",
+ "uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
+ "webpack": "2.6.1"
+ },
+ "dependencies": {
+ "accesses": "2.5.0",
+ "animejs": "2.0.2",
+ "autwh": "0.0.1",
+ "bcryptjs": "2.4.3",
+ "body-parser": "1.17.2",
+ "cafy": "2.4.0",
+ "chalk": "1.1.3",
+ "compression": "1.6.2",
+ "cors": "2.8.3",
+ "cropperjs": "1.0.0-rc.2",
+ "crypto": "0.0.3",
+ "debug": "2.6.8",
+ "deep-equal": "1.0.1",
+ "deepcopy": "0.6.3",
+ "diskusage": "^0.2.2",
+ "download": "6.2.2",
+ "elasticsearch": "13.0.1",
+ "escape-regexp": "0.0.1",
+ "express": "4.15.3",
+ "file-type": "5.1.1",
+ "fuckadblock": "3.2.1",
+ "gm": "1.23.0",
+ "inquirer": "3.1.0",
+ "is-root": "1.0.0",
+ "is-url": "1.2.2",
+ "js-yaml": "3.8.4",
+ "mongodb": "2.2.28",
+ "monk": "6.0.0",
+ "morgan": "1.8.2",
+ "ms": "2.0.0",
+ "multer": "1.3.0",
+ "nprogress": "0.2.0",
+ "os-utils": "0.0.14",
+ "page": "1.7.1",
+ "pictograph": "2.0.4",
+ "prominence": "0.2.0",
+ "pug": "2.0.0-rc.2",
+ "ratelimiter": "3.0.3",
+ "recaptcha-promise": "0.1.2",
+ "reconnecting-websocket": "3.0.5",
+ "redis": "2.7.1",
+ "request": "2.81.0",
+ "rimraf": "2.6.1",
+ "riot": "3.6.0",
+ "rndstr": "1.0.0",
+ "s-age": "1.1.0",
+ "serve-favicon": "2.4.3",
+ "summaly": "2.0.3",
+ "syuilo-password-strength": "0.0.1",
+ "tcp-port-used": "0.1.2",
+ "textarea-caret": "3.0.2",
+ "ts-node": "3.0.6",
+ "typescript": "2.3.4",
+ "uuid": "3.0.1",
+ "vhost": "3.0.2",
+ "websocket": "1.0.24",
+ "xev": "^2.0.0"
+ }
}
diff --git a/src/api/common/read-messaging-message.ts b/src/api/common/read-messaging-message.ts
new file mode 100644
index 0000000000..3257ec8b07
--- /dev/null
+++ b/src/api/common/read-messaging-message.ts
@@ -0,0 +1,64 @@
+import * as mongo from 'mongodb';
+import Message from '../models/messaging-message';
+import { IMessagingMessage as IMessage } from '../models/messaging-message';
+import publishUserStream from '../event';
+import { publishMessagingStream } from '../event';
+
+/**
+ * Mark as read message(s)
+ */
+export default (
+ user: string | mongo.ObjectID,
+ otherparty: string | mongo.ObjectID,
+ message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[]
+) => new Promise<any>(async (resolve, reject) => {
+
+ const userId = mongo.ObjectID.prototype.isPrototypeOf(user)
+ ? user
+ : new mongo.ObjectID(user);
+
+ const otherpartyId = mongo.ObjectID.prototype.isPrototypeOf(otherparty)
+ ? otherparty
+ : new mongo.ObjectID(otherparty);
+
+ const ids: mongo.ObjectID[] = Array.isArray(message)
+ ? mongo.ObjectID.prototype.isPrototypeOf(message[0])
+ ? (message as mongo.ObjectID[])
+ : typeof message[0] === 'string'
+ ? (message as string[]).map(m => new mongo.ObjectID(m))
+ : (message as IMessage[]).map(m => m._id)
+ : mongo.ObjectID.prototype.isPrototypeOf(message)
+ ? [(message as mongo.ObjectID)]
+ : typeof message === 'string'
+ ? [new mongo.ObjectID(message)]
+ : [(message as IMessage)._id];
+
+ // Update documents
+ await Message.update({
+ _id: { $in: ids },
+ user_id: otherpartyId,
+ recipient_id: userId,
+ is_read: false
+ }, {
+ $set: {
+ is_read: true
+ }
+ }, {
+ multi: true
+ });
+
+ // Publish event
+ publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString()));
+
+ // Calc count of my unread messages
+ const count = await Message
+ .count({
+ recipient_id: userId,
+ is_read: false
+ });
+
+ if (count == 0) {
+ // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
+ publishUserStream(userId, 'read_all_messaging_messages');
+ }
+});
diff --git a/src/api/endpoints/messaging/messages.ts b/src/api/endpoints/messaging/messages.ts
index b3a5c57f6c..7b270924eb 100644
--- a/src/api/endpoints/messaging/messages.ts
+++ b/src/api/endpoints/messaging/messages.ts
@@ -5,8 +5,7 @@ import $ from 'cafy';
import Message from '../../models/messaging-message';
import User from '../../models/user';
import serialize from '../../serializers/messaging-message';
-import publishUserStream from '../../event';
-import { publishMessagingStream } from '../../event';
+import read from '../../common/read-messaging-message';
/**
* Get messages
@@ -98,32 +97,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
// Mark as read all
if (markAsRead) {
- const ids = messages
- .filter(m => m.is_read == false)
- .filter(m => m.recipient_id.equals(user._id))
- .map(m => m._id);
-
- // Update documents
- await Message.update({
- _id: { $in: ids }
- }, {
- $set: { is_read: true }
- }, {
- multi: true
- });
-
- // Publish event
- publishMessagingStream(recipient._id, user._id, 'read', ids.map(id => id.toString()));
-
- const count = await Message
- .count({
- recipient_id: user._id,
- is_read: false
- });
-
- if (count == 0) {
- // 全ての(いままで未読だった)メッセージを(これで)読みましたよというイベントを発行
- publishUserStream(user._id, 'read_all_messaging_messages');
- }
+ read(user._id, recipient._id, messages);
}
});
diff --git a/src/api/endpoints/messaging/messages/create.ts b/src/api/endpoints/messaging/messages/create.ts
index 05f9cda4cb..8af55d850c 100644
--- a/src/api/endpoints/messaging/messages/create.ts
+++ b/src/api/endpoints/messaging/messages/create.ts
@@ -93,13 +93,13 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj);
publishUserStream(message.recipient_id, 'messaging_message', messageObj);
- // 5秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
+ // 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
setTimeout(async () => {
const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true });
if (!freshMessage.is_read) {
publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj);
}
- }, 5000);
+ }, 3000);
// Register to search database
if (message.text && config.elasticsearch.enable) {
diff --git a/src/api/endpoints/meta.ts b/src/api/endpoints/meta.ts
index 98f812abd5..a3f1d50329 100644
--- a/src/api/endpoints/meta.ts
+++ b/src/api/endpoints/meta.ts
@@ -1,6 +1,7 @@
/**
* Module dependencies
*/
+import * as os from 'os';
import version from '../../version';
import config from '../../conf';
@@ -41,6 +42,13 @@ module.exports = (params) => new Promise(async (res, rej) => {
res({
maintainer: config.maintainer,
version: version,
- secure: config.https.enable
+ secure: config.https.enable,
+ machine: os.hostname(),
+ os: os.platform(),
+ node: process.version,
+ cpu: {
+ model: os.cpus()[0].model,
+ cores: os.cpus().length
+ }
});
});
diff --git a/src/api/endpoints/users/search.ts b/src/api/endpoints/users/search.ts
index a3f2fb796d..73a5db47e2 100644
--- a/src/api/endpoints/users/search.ts
+++ b/src/api/endpoints/users/search.ts
@@ -42,7 +42,7 @@ async function byNative(res, rej, me, query, offset, max) {
const users = await User
.find({
$or: [{
- username_lower: new RegExp(escapedQuery.toLowerCase())
+ username_lower: new RegExp(escapedQuery.replace('@', '').toLowerCase())
}, {
name: new RegExp(escapedQuery)
}]
diff --git a/src/api/models/messaging-message.ts b/src/api/models/messaging-message.ts
index 81aee2cab8..18afa57e44 100644
--- a/src/api/models/messaging-message.ts
+++ b/src/api/models/messaging-message.ts
@@ -1,7 +1,12 @@
+import * as mongo from 'mongodb';
import db from '../../db/mongodb';
export default db.get('messaging_messages') as any; // fuck type definition
+export interface IMessagingMessage {
+ _id: mongo.ObjectID;
+}
+
export function isValidText(text: string): boolean {
return text.length <= 1000 && text.trim() != '';
}
diff --git a/src/api/stream/messaging.ts b/src/api/stream/messaging.ts
index 71bf7a34c6..3f505cfafa 100644
--- a/src/api/stream/messaging.ts
+++ b/src/api/stream/messaging.ts
@@ -1,8 +1,6 @@
-import * as mongodb from 'mongodb';
import * as websocket from 'websocket';
import * as redis from 'redis';
-import Message from '../models/messaging-message';
-import { publishMessagingStream } from '../event';
+import read from '../common/read-messaging-message';
export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
const otherparty = request.resourceURL.query.otherparty;
@@ -18,42 +16,8 @@ export default function messagingStream(request: websocket.request, connection:
switch (msg.type) {
case 'read':
- if (!msg.id) {
- return;
- }
-
- const id = new mongodb.ObjectID(msg.id);
-
- // Fetch message
- // SELECT _id, user_id, is_read
- const message = await Message.findOne({
- _id: id,
- recipient_id: user._id
- }, {
- fields: {
- _id: true,
- user_id: true,
- is_read: true
- }
- });
-
- if (message == null) {
- return;
- }
-
- if (message.is_read) {
- return;
- }
-
- // Update documents
- await Message.update({
- _id: id
- }, {
- $set: { is_read: true }
- });
-
- // Publish event
- publishMessagingStream(message.user_id, user._id, 'read', id.toString());
+ if (!msg.id) return;
+ read(user._id, otherparty, msg.id);
break;
}
});
diff --git a/src/api/stream/server.ts b/src/api/stream/server.ts
new file mode 100644
index 0000000000..6de5337499
--- /dev/null
+++ b/src/api/stream/server.ts
@@ -0,0 +1,20 @@
+import * as websocket from 'websocket';
+import Xev from 'xev';
+
+const ev = new Xev();
+
+export default function homeStream(request: websocket.request, connection: websocket.connection): void {
+ const onStats = stats => {
+ connection.send(JSON.stringify({
+ type: 'stats',
+ body: stats
+ }));
+ };
+
+ ev.addListener('stats', onStats);
+
+ connection.on('close', () => {
+ console.log('yooo');
+ ev.removeListener('stats', onStats);
+ });
+}
diff --git a/src/api/streaming.ts b/src/api/streaming.ts
index e1d79481d3..c71132100c 100644
--- a/src/api/streaming.ts
+++ b/src/api/streaming.ts
@@ -8,6 +8,7 @@ import isNativeToken from './common/is-native-token';
import homeStream from './stream/home';
import messagingStream from './stream/messaging';
+import serverStream from './stream/server';
module.exports = (server: http.Server) => {
/**
@@ -20,6 +21,11 @@ module.exports = (server: http.Server) => {
ws.on('request', async (request) => {
const connection = request.accept();
+ if (request.resourceURL.pathname === '/server') {
+ serverStream(request, connection);
+ return;
+ }
+
const user = await authenticate(connection, request.resourceURL.query.i);
if (user == null) {
diff --git a/src/index.ts b/src/index.ts
index 0b568b5d44..da83dde108 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -12,17 +12,20 @@ import * as chalk from 'chalk';
// import portUsed = require('tcp-port-used');
import isRoot = require('is-root');
import { master } from 'accesses';
+import Xev from 'xev';
import Logger from './utils/logger';
import ProgressBar from './utils/cli/progressbar';
import EnvironmentInfo from './utils/environmentInfo';
import MachineInfo from './utils/machineInfo';
import DependencyInfo from './utils/dependencyInfo';
+import stats from './utils/stats';
import { Config, path as configPath } from './config';
import loadConfig from './config';
const clusterLog = debug('misskey:cluster');
+const ev = new Xev();
process.title = 'Misskey';
@@ -35,6 +38,9 @@ main();
function main() {
if (cluster.isMaster) {
masterMain();
+
+ ev.mount();
+ stats();
} else {
workerMain();
}
diff --git a/src/utils/stats.ts b/src/utils/stats.ts
new file mode 100644
index 0000000000..cfb710f5e1
--- /dev/null
+++ b/src/utils/stats.ts
@@ -0,0 +1,27 @@
+import * as os from 'os';
+const osUtils = require('os-utils');
+import * as diskusage from 'diskusage';
+import Xev from 'xev';
+
+const ev = new Xev();
+
+/**
+ * Report stats regularly
+ */
+export default function() {
+ setInterval(() => {
+ osUtils.cpuUsage(cpuUsage => {
+ const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/');
+ ev.emit('stats', {
+ cpu_usage: cpuUsage,
+ mem: {
+ total: os.totalmem(),
+ free: os.freemem()
+ },
+ disk,
+ os_uptime: os.uptime(),
+ process_uptime: process.uptime()
+ });
+ });
+ }, 1000);
+}
diff --git a/src/web/app/base.pug b/src/web/app/base.pug
index f7da0574bd..b1ca80deb9 100644
--- a/src/web/app/base.pug
+++ b/src/web/app/base.pug
@@ -20,7 +20,7 @@ html
script
include ./../../../built/web/assets/safe.js
- script(src='https://use.fontawesome.com/22aba0df4f.js' async)
+ script(src='https://use.fontawesome.com/db921426cb.js' async)
body
noscript: p
diff --git a/src/web/app/base.styl b/src/web/app/base.styl
index 9fdb39a16c..81c039f0a3 100644
--- a/src/web/app/base.styl
+++ b/src/web/app/base.styl
@@ -30,14 +30,7 @@ html
cursor progress !important
#error
- position fixed
- z-index 32768
- top 0
- left 0
- width 100%
- height 100%
padding 32px
- background #1269e2
color #fff
hr
diff --git a/src/web/app/common/scripts/bytes-to-size.js b/src/web/app/common/scripts/bytes-to-size.js
index e143387141..af0268dbd0 100644
--- a/src/web/app/common/scripts/bytes-to-size.js
+++ b/src/web/app/common/scripts/bytes-to-size.js
@@ -1,6 +1,6 @@
-export default bytes => {
+export default (bytes, digits = 0) => {
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if (bytes == 0) return '0Byte';
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
- return Math.round(bytes / Math.pow(1024, i), 2) + sizes[i];
+ return (bytes / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i];
};
diff --git a/src/web/app/common/scripts/get-cat.js b/src/web/app/common/scripts/get-cat.js
deleted file mode 100644
index cad42c88c8..0000000000
--- a/src/web/app/common/scripts/get-cat.js
+++ /dev/null
@@ -1 +0,0 @@
-export default () => '(=^・・^=)';
diff --git a/src/web/app/common/scripts/get-kao.js b/src/web/app/common/scripts/get-kao.js
new file mode 100644
index 0000000000..0b77ee285a
--- /dev/null
+++ b/src/web/app/common/scripts/get-kao.js
@@ -0,0 +1,5 @@
+export default () => [
+ '(=^・・^=)',
+ 'v(‘ω’)v',
+ '🐡( '-' 🐡 )フグパンチ!!!!'
+][Math.floor(Math.random() * 3)];
diff --git a/src/web/app/common/scripts/home-stream.js b/src/web/app/common/scripts/home-stream.js
new file mode 100644
index 0000000000..24f13cd291
--- /dev/null
+++ b/src/web/app/common/scripts/home-stream.js
@@ -0,0 +1,18 @@
+'use strict';
+
+import Stream from './stream';
+
+/**
+ * Home stream connection
+ */
+class Connection extends Stream {
+ constructor(me) {
+ super('', {
+ i: me.token
+ });
+
+ this.on('i_updated', me.update);
+ }
+}
+
+export default Connection;
diff --git a/src/web/app/common/scripts/messaging-stream.js b/src/web/app/common/scripts/messaging-stream.js
index 50d41c2be9..261525d5f6 100644
--- a/src/web/app/common/scripts/messaging-stream.js
+++ b/src/web/app/common/scripts/messaging-stream.js
@@ -1,42 +1,22 @@
-const ReconnectingWebSocket = require('reconnecting-websocket');
-import * as riot from 'riot';
-import CONFIG from './config';
+'use strict';
-class Connection {
- constructor(me, otherparty) {
- // BIND -----------------------------------
- this.onOpen = this.onOpen.bind(this);
- this.onMessage = this.onMessage.bind(this);
- this.close = this.close.bind(this);
- // ----------------------------------------
-
- this.event = riot.observable();
- this.me = me;
-
- const host = CONFIG.apiUrl.replace('http', 'ws');
- this.socket = new ReconnectingWebSocket(`${host}/messaging?i=${me.token}&otherparty=${otherparty}`);
- this.socket.addEventListener('open', this.onOpen);
- this.socket.addEventListener('message', this.onMessage);
- }
+import Stream from './stream';
- onOpen() {
- this.socket.send(JSON.stringify({
- i: this.me.token
- }));
- }
-
- onMessage(message) {
- try {
- const msg = JSON.parse(message.data);
- if (msg.type) this.event.trigger(msg.type, msg.body);
- } catch(e) {
- // noop
- }
- }
+/**
+ * Messaging stream connection
+ */
+class Connection extends Stream {
+ constructor(me, otherparty) {
+ super('messaging', {
+ i: me.token,
+ otherparty
+ });
- close() {
- this.socket.removeEventListener('open', this.onOpen);
- this.socket.removeEventListener('message', this.onMessage);
+ this.on('_connected_', () => {
+ this.send({
+ i: me.token
+ });
+ });
}
}
diff --git a/src/web/app/common/scripts/server-stream.js b/src/web/app/common/scripts/server-stream.js
new file mode 100644
index 0000000000..a1c466b35d
--- /dev/null
+++ b/src/web/app/common/scripts/server-stream.js
@@ -0,0 +1,14 @@
+'use strict';
+
+import Stream from './stream';
+
+/**
+ * Server stream connection
+ */
+class Connection extends Stream {
+ constructor() {
+ super('server');
+ }
+}
+
+export default Connection;
diff --git a/src/web/app/common/scripts/stream.js b/src/web/app/common/scripts/stream.js
index ac3dd67153..981118b5de 100644
--- a/src/web/app/common/scripts/stream.js
+++ b/src/web/app/common/scripts/stream.js
@@ -5,10 +5,10 @@ import * as riot from 'riot';
import CONFIG from './config';
/**
- * Home stream connection
+ * Misskey stream connection
*/
class Connection {
- constructor(me) {
+ constructor(endpoint, params) {
// BIND -----------------------------------
this.onOpen = this.onOpen.bind(this);
this.onClose = this.onClose.bind(this);
@@ -20,16 +20,19 @@ class Connection {
riot.observable(this);
this.state = 'initializing';
- this.me = me;
this.buffer = [];
const host = CONFIG.apiUrl.replace('http', 'ws');
- this.socket = new ReconnectingWebSocket(`${host}?i=${me.token}`);
+ const query = params
+ ? Object.keys(params)
+ .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
+ .join('&')
+ : null;
+
+ this.socket = new ReconnectingWebSocket(`${host}/${endpoint}${query ? '?' + query : ''}`);
this.socket.addEventListener('open', this.onOpen);
this.socket.addEventListener('close', this.onClose);
this.socket.addEventListener('message', this.onMessage);
-
- this.on('i_updated', me.update);
}
/**
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index 20ed1877da..0a9b8022df 100644
--- a/src/web/app/common/scripts/text-compiler.js
+++ b/src/web/app/common/scripts/text-compiler.js
@@ -22,7 +22,7 @@ export default (tokens, shouldBreak) => {
case 'bold':
return `<strong>${escape(token.bold)}</strong>`;
case 'url':
- return `<mk-url href="${escape(token.content)}" target="_blank"/>`;
+ return `<mk-url href="${escape(token.content)}" target="_blank"></mk-url>`;
case 'link':
return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`;
case 'mention':
diff --git a/src/web/app/common/tags/activity-table.tag b/src/web/app/common/tags/activity-table.tag
index 49c5fe26cd..2ecfc3b110 100644
--- a/src/web/app/common/tags/activity-table.tag
+++ b/src/web/app/common/tags/activity-table.tag
@@ -2,7 +2,15 @@
<svg if={ data } ref="canvas" viewBox="0 0 53 7" preserveAspectRatio="none">
<rect each={ data } width="1" height="1"
riot-x={ x } riot-y={ date.weekday }
- fill={ color }></rect>
+ rx="1" ry="1"
+ fill={ color }
+ style="transform: scale({ v });"/>
+ <rect class="today" width="1" height="1"
+ riot-x={ data[data.length - 1].x } riot-y={ data[data.length - 1].date.weekday }
+ rx="1" ry="1"
+ fill="none"
+ stroke-width="0.1"
+ stroke="#f73520"/>
</svg>
<style>
:scope
@@ -16,7 +24,6 @@
> rect
transform-origin center
- transform scale(0.8)
</style>
<script>
@@ -33,9 +40,9 @@
let x = 0;
data.reverse().forEach(d => {
d.x = x;
- let v = d.total / this.peak;
- if (v > 1) v = 1;
- d.color = `hsl(180, ${v * 100}%, ${15 + ((1 - v) * 80)}%)`;
+ d.v = d.total / this.peak;
+ if (d.v > 1) d.v = 1;
+ d.color = `hsl(170, ${d.v * 100}%, ${15 + ((1 - d.v) * 80)}%)`;
d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
if (d.date.weekday == 6) x++;
});
diff --git a/src/web/app/common/tags/error.tag b/src/web/app/common/tags/error.tag
index c7491d1c43..e4e0272a49 100644
--- a/src/web/app/common/tags/error.tag
+++ b/src/web/app/common/tags/error.tag
@@ -1,36 +1,25 @@
<mk-error>
- <!--i: i.fa.fa-times-circle-->
<img src="/assets/error.jpg" alt=""/>
<h1>%i18n:common.tags.mk-error.title%</h1>
<p class="text">%i18n:common.tags.mk-error.description%</p>
<p class="thanks">%i18n:common.tags.mk-error.thanks%</p>
<style>
:scope
- position fixed
- z-index 100000
- top 0
- left 0
+ display block
width 100%
- height 100%
+ padding 32px 18px
text-align center
- background #f8f8f8
-
- > i
- display block
- margin-top 64px
- font-size 5em
- color #6998a0
> img
display block
height 200px
- margin 64px auto 0 auto
+ margin 0 auto
pointer-events none
user-select none
> h1
display block
- margin 32px auto 16px auto
+ margin 1.25em auto 0.65em auto
font-size 1.5em
color #555
@@ -43,13 +32,26 @@
> .thanks
display block
- margin 32px auto 0 auto
- padding 32px 0 32px 0
+ margin 2em auto 0 auto
+ padding 2em 0 0 0
max-width 600px
font-size 0.9em
font-style oblique
color #aaa
border-top solid 1px #eee
+ @media (max-width 500px)
+ padding 24px 18px
+ font-size 80%
+
+ > img
+ height 150px
+
</style>
+ <script>
+ this.on('mount', () => {
+ document.title = 'Oops!';
+ document.documentElement.style.background = '#f8f8f8';
+ });
+ </script>
</mk-error>
diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag
index ec8138da04..d6db9070e2 100644
--- a/src/web/app/common/tags/messaging/message.tag
+++ b/src/web/app/common/tags/messaging/message.tag
@@ -1,4 +1,7 @@
-<mk-messaging-message data-is-me={ message.is_me }><a class="avatar-anchor" href={ '/' + message.user.username } title={ message.user.username } target="_blank"><img class="avatar" src={ message.user.avatar_url + '?thumbnail&size=64' } alt=""/></a>
+<mk-messaging-message data-is-me={ message.is_me }>
+ <a class="avatar-anchor" href={ '/' + message.user.username } title={ message.user.username } target="_blank">
+ <img class="avatar" src={ message.user.avatar_url + '?thumbnail&size=80' } alt=""/>
+ </a>
<div class="content-container">
<div class="balloon">
<p class="read" if={ message.is_me && message.is_read }>%i18n:common.tags.mk-messaging-message.is-read%</p>
diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag
index bd49a4d782..b1082e26be 100644
--- a/src/web/app/common/tags/messaging/room.tag
+++ b/src/web/app/common/tags/messaging/room.tag
@@ -2,6 +2,10 @@
<div class="stream">
<p class="init" if={ init }><i class="fa fa-spinner fa-spin"></i>%i18n:common.loading%</p>
<p class="empty" if={ !init && messages.length == 0 }><i class="fa fa-info-circle"></i>%i18n:common.tags.mk-messaging-room.empty%</p>
+ <p class="no-history" if={ !init && messages.length > 0 && !moreMessagesIsInStock }><i class="fa fa-flag"></i>%i18n:common.tags.mk-messaging-room.no-history%</p>
+ <button class="more { fetching: fetchingMoreMessages }" if={ moreMessagesIsInStock } onclick={ fetchMoreMessages } disabled={ fetchingMoreMessages }>
+ <i class="fa fa-spinner fa-pulse fa-fw" if={ fetchingMoreMessages }></i>{ fetchingMoreMessages ? '%i18n:common.loading%' : '%i18n:common.tags.mk-messaging-room.more%' }
+ </button>
<virtual each={ message, i in messages }>
<mk-messaging-message message={ message }/>
<p class="date" if={ i != messages.length - 1 && message._date != messages[i + 1]._date }><span>{ messages[i + 1]._datetext }</span></p>
@@ -20,6 +24,17 @@
max-width 600px
margin 0 auto
+ > .init
+ width 100%
+ margin 0
+ padding 16px 8px 8px 8px
+ text-align center
+ font-size 0.8em
+ color rgba(0, 0, 0, 0.4)
+
+ i
+ margin-right 4px
+
> .empty
width 100%
margin 0
@@ -42,6 +57,27 @@
i
margin-right 4px
+ > .more
+ display block
+ margin 16px auto
+ padding 0 12px
+ line-height 24px
+ color #fff
+ background rgba(0, 0, 0, 0.3)
+ border-radius 12px
+
+ &:hover
+ background rgba(0, 0, 0, 0.4)
+
+ &:active
+ background rgba(0, 0, 0, 0.5)
+
+ &.fetching
+ cursor wait
+
+ > i
+ margin-right 4px
+
> .message
// something
@@ -90,6 +126,9 @@
padding 8px 0
text-align center
+ &:empty
+ display none
+
> p
display inline-block
margin 0
@@ -137,24 +176,21 @@
this.connection = new MessagingStreamConnection(this.I, this.user.id);
this.on('mount', () => {
- this.connection.event.on('message', this.onMessage);
- this.connection.event.on('read', this.onRead);
+ this.connection.on('message', this.onMessage);
+ this.connection.on('read', this.onRead);
document.addEventListener('visibilitychange', this.onVisibilitychange);
- this.api('messaging/messages', {
- user_id: this.user.id
- }).then(messages => {
+ this.fetchMessages().then(() => {
this.init = false;
- this.messages = messages.reverse();
this.update();
this.scrollToBottom();
});
});
this.on('unmount', () => {
- this.connection.event.off('message', this.onMessage);
- this.connection.event.off('read', this.onRead);
+ this.connection.off('message', this.onMessage);
+ this.connection.off('read', this.onRead);
this.connection.close();
document.removeEventListener('visibilitychange', this.onVisibilitychange);
@@ -174,10 +210,10 @@
this.messages.push(message);
if (message.user_id != this.I.id && !document.hidden) {
- this.connection.socket.send(JSON.stringify({
+ this.connection.send({
type: 'read',
id: message.id
- }));
+ });
}
this.update();
@@ -201,6 +237,39 @@
});
};
+ this.fetchMoreMessages = () => {
+ this.update({
+ fetchingMoreMessages: true
+ });
+ this.fetchMessages().then(() => {
+ this.update({
+ fetchingMoreMessages: false
+ });
+ });
+ };
+
+ this.fetchMessages = () => new Promise((resolve, reject) => {
+ const max = this.moreMessagesIsInStock ? 20 : 10;
+
+ this.api('messaging/messages', {
+ user_id: this.user.id,
+ limit: max + 1,
+ max_id: this.moreMessagesIsInStock ? this.messages[0].id : undefined
+ }).then(messages => {
+ if (messages.length == max + 1) {
+ this.moreMessagesIsInStock = true;
+ messages.pop();
+ } else {
+ this.moreMessagesIsInStock = false;
+ }
+
+ this.messages.unshift.apply(this.messages, messages.reverse());
+ this.update();
+
+ resolve();
+ });
+ });
+
this.isBottom = () => {
const asobi = 32;
const current = this.isNaked
@@ -239,10 +308,10 @@
if (document.hidden) return;
this.messages.forEach(message => {
if (message.user_id !== this.I.id && !message.is_read) {
- this.connection.socket.send(JSON.stringify({
+ this.connection.send({
type: 'read',
id: message.id
- }));
+ });
}
});
};
diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.js
index f9f0896ddd..2e81147943 100644
--- a/src/web/app/desktop/script.js
+++ b/src/web/app/desktop/script.js
@@ -74,4 +74,18 @@ function registerNotifications(stream) {
});
setTimeout(n.close.bind(n), 6000);
});
+
+ stream.on('unread_messaging_message', message => {
+ const n = new Notification(`${message.user.name}さんからメッセージ:`, {
+ body: message.text, // TODO: getMessagingMessageSummary(message),
+ icon: message.user.avatar_url + '?thumbnail&size=64'
+ });
+ n.onclick = () => {
+ n.close();
+ riot.mount(document.body.appendChild(document.createElement('mk-messaging-room-window')), {
+ user: message.user
+ });
+ };
+ setTimeout(n.close.bind(n), 7000);
+ });
}
diff --git a/src/web/app/desktop/tags/home-widgets/activity.tag b/src/web/app/desktop/tags/home-widgets/activity.tag
index ba201e1c29..29ce8760fd 100644
--- a/src/web/app/desktop/tags/home-widgets/activity.tag
+++ b/src/web/app/desktop/tags/home-widgets/activity.tag
@@ -1,11 +1,9 @@
<mk-activity-home-widget>
<p class="title"><i class="fa fa-bar-chart"></i>%i18n:desktop.tags.mk-activity-home-widget.title%</p>
+ <button onclick={ toggle } title="%i18n:desktop.tags.mk-activity-home-widget.toggle%"><i class="fa fa-sort"></i></button>
<p class="initializing" if={ initializing }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:common.loading%<mk-ellipsis/></p>
- <svg if={ !initializing } ref="canvas" viewBox="0 0 21 7" preserveAspectRatio="none">
- <rect each={ data } width="1" height="1"
- riot-x={ x } riot-y={ date.weekday }
- fill={ color }></rect>
- </svg>
+ <mk-activity-home-widget-calender if={ !initializing && view == 0 } data={ [].concat(data) }/>
+ <mk-activity-home-widget-chart if={ !initializing && view == 1 } data={ [].concat(data) }/>
<style>
:scope
display block
@@ -24,6 +22,23 @@
> i
margin-right 4px
+ > button
+ position absolute
+ z-index 2
+ top 0
+ right 0
+ padding 0
+ width 42px
+ font-size 0.9em
+ line-height 42px
+ color #ccc
+
+ &:hover
+ color #aaa
+
+ &:active
+ color #999
+
> .initializing
margin 0
padding 16px
@@ -33,44 +48,182 @@
> i
margin-right 4px
- > svg
- display block
- padding 10px
- width 100%
-
- > rect
- transform-origin center
- transform scale(0.8)
-
</style>
<script>
this.mixin('i');
this.mixin('api');
this.initializing = true;
+ this.view = 0;
this.on('mount', () => {
this.api('aggregation/users/activity', {
user_id: this.I.id,
limit: 20 * 7
}).then(data => {
- data.forEach(d => d.total = d.posts + d.replies + d.reposts);
- this.peak = Math.max.apply(null, data.map(d => d.total)) / 2;
- let x = 0;
- data.reverse().forEach(d => {
- d.x = x;
- let v = d.total / this.peak;
- if (v > 1) v = 1;
- d.color = `hsl(180, ${v * 100}%, ${15 + ((1 - v) * 80)}%)`;
- d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
- if (d.date.weekday == 6) x++;
- });
-
this.update({
initializing: false,
data
});
});
});
+
+ this.toggle = () => {
+ this.view++;
+ if (this.view == 2) this.view = 0;
+ };
</script>
</mk-activity-home-widget>
+
+<mk-activity-home-widget-calender>
+ <svg viewBox="0 0 21 7" preserveAspectRatio="none">
+ <rect each={ data } class="day"
+ width="1" height="1"
+ riot-x={ x } riot-y={ date.weekday }
+ rx="1" ry="1"
+ fill="transparent">
+ <title>{ date.year }/{ date.month }/{ date.day }<br/>Post: { posts }, Reply: { replies }, Repost: { reposts }</title>
+ </rect>
+ <rect each={ data }
+ width="1" height="1"
+ riot-x={ x } riot-y={ date.weekday }
+ rx="1" ry="1"
+ fill={ color }
+ style="pointer-events: none; transform: scale({ v });"/>
+ <rect class="today"
+ width="1" height="1"
+ riot-x={ data[data.length - 1].x } riot-y={ data[data.length - 1].date.weekday }
+ rx="1" ry="1"
+ fill="none"
+ stroke-width="0.1"
+ stroke="#f73520"/>
+ </svg>
+ <style>
+ :scope
+ display block
+
+ > svg
+ display block
+ padding 10px
+ width 100%
+
+ > rect
+ transform-origin center
+
+ &.day
+ &:hover
+ fill rgba(0, 0, 0, 0.05)
+
+ </style>
+ <script>
+ this.data = this.opts.data;
+ this.data.forEach(d => d.total = d.posts + d.replies + d.reposts);
+ const peak = Math.max.apply(null, this.data.map(d => d.total));
+
+ let x = 0;
+ this.data.reverse().forEach(d => {
+ d.x = x;
+ d.v = d.total / (peak / 2);
+ if (d.v > 1) d.v = 1;
+ d.color = `hsl(170, ${d.v * 100}%, ${15 + ((1 - d.v) * 80)}%)`;
+ d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
+ if (d.date.weekday == 6) x++;
+ });
+ </script>
+</mk-activity-home-widget-calender>
+
+<mk-activity-home-widget-chart>
+ <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none" onmousedown={ onMousedown }>
+ <title>Black ... Total<br/>Blue ... Posts<br/>Red ... Replies<br/>Green ... Reposts</title>
+ <polyline
+ riot-points={ pointsPost }
+ fill="none"
+ stroke-width="1"
+ stroke="#41ddde"/>
+ <polyline
+ riot-points={ pointsReply }
+ fill="none"
+ stroke-width="1"
+ stroke="#f7796c"/>
+ <polyline
+ riot-points={ pointsRepost }
+ fill="none"
+ stroke-width="1"
+ stroke="#a1de41"/>
+ <polyline
+ riot-points={ pointsTotal }
+ fill="none"
+ stroke-width="1"
+ stroke="#555"
+ stroke-dasharray="2 2"/>
+ </svg>
+ <style>
+ :scope
+ display block
+
+ > svg
+ display block
+ padding 10px
+ width 100%
+ cursor all-scroll
+ </style>
+ <script>
+ this.viewBoxX = 140;
+ this.viewBoxY = 60;
+ this.zoom = 1;
+ this.pos = 0;
+
+ this.data = this.opts.data.reverse();
+ this.data.forEach(d => d.total = d.posts + d.replies + d.reposts);
+ const peak = Math.max.apply(null, this.data.map(d => d.total));
+
+ this.on('mount', () => {
+ this.render();
+ });
+
+ this.render = () => {
+ this.update({
+ pointsPost: this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.posts / peak)) * this.viewBoxY}`).join(' '),
+ pointsReply: this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '),
+ pointsRepost: this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.reposts / peak)) * this.viewBoxY}`).join(' '),
+ pointsTotal: this.data.map((d, i) => `${(i * this.zoom) + this.pos},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ')
+ });
+ };
+
+ this.onMousedown = e => {
+ e.preventDefault();
+
+ const clickX = e.clientX;
+ const clickY = e.clientY;
+ const baseZoom = this.zoom;
+ const basePos = this.pos;
+
+ // 動かした時
+ dragListen(me => {
+ let moveLeft = me.clientX - clickX;
+ let moveTop = me.clientY - clickY;
+
+ this.zoom = baseZoom + (-moveTop / 20);
+ this.pos = basePos + moveLeft;
+ if (this.zoom < 1) this.zoom = 1;
+ if (this.pos > 0) this.pos = 0;
+ if (this.pos < -(((this.data.length - 1) * this.zoom) - this.viewBoxX)) this.pos = -(((this.data.length - 1) * this.zoom) - this.viewBoxX);
+
+ this.render();
+ });
+ };
+
+ function dragListen(fn) {
+ window.addEventListener('mousemove', fn);
+ window.addEventListener('mouseleave', dragClear.bind(null, fn));
+ window.addEventListener('mouseup', dragClear.bind(null, fn));
+ }
+
+ function dragClear(fn) {
+ window.removeEventListener('mousemove', fn);
+ window.removeEventListener('mouseleave', dragClear);
+ window.removeEventListener('mouseup', dragClear);
+ }
+ </script>
+</mk-activity-home-widget-chart>
+
diff --git a/src/web/app/desktop/tags/home-widgets/server.tag b/src/web/app/desktop/tags/home-widgets/server.tag
new file mode 100644
index 0000000000..bc8f313d53
--- /dev/null
+++ b/src/web/app/desktop/tags/home-widgets/server.tag
@@ -0,0 +1,510 @@
+<mk-server-home-widget>
+ <p class="title"><i class="fa fa-server"></i>%i18n:desktop.tags.mk-server-home-widget.title%</p>
+ <button onclick={ toggle } title="%i18n:desktop.tags.mk-server-home-widget.toggle%"><i class="fa fa-sort"></i></button>
+ <p class="initializing" if={ initializing }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:common.loading%<mk-ellipsis/></p>
+ <mk-server-home-widget-cpu-and-memory-usage if={ !initializing } show={ view == 0 } connection={ connection }/>
+ <mk-server-home-widget-cpu if={ !initializing } show={ view == 1 } connection={ connection } meta={ meta }/>
+ <mk-server-home-widget-memory if={ !initializing } show={ view == 2 } connection={ connection }/>
+ <mk-server-home-widget-disk if={ !initializing } show={ view == 3 } connection={ connection }/>
+ <mk-server-home-widget-uptimes if={ !initializing } show={ view == 4 } connection={ connection }/>
+ <mk-server-home-widget-info if={ !initializing } show={ view == 5 } connection={ connection } meta={ meta }/>
+ <style>
+ :scope
+ display block
+ background #fff
+
+ > .title
+ z-index 1
+ margin 0
+ padding 0 16px
+ line-height 42px
+ font-size 0.9em
+ font-weight bold
+ color #888
+ box-shadow 0 1px rgba(0, 0, 0, 0.07)
+
+ > i
+ margin-right 4px
+
+ > button
+ position absolute
+ z-index 2
+ top 0
+ right 0
+ padding 0
+ width 42px
+ font-size 0.9em
+ line-height 42px
+ color #ccc
+
+ &:hover
+ color #aaa
+
+ &:active
+ color #999
+
+ > .initializing
+ margin 0
+ padding 16px
+ text-align center
+ color #aaa
+
+ > i
+ margin-right 4px
+
+ </style>
+ <script>
+ import Connection from '../../../common/scripts/server-stream';
+
+ this.mixin('api');
+
+ this.initializing = true;
+ this.view = 0;
+ this.connection = new Connection();
+
+ this.on('mount', () => {
+ this.api('meta').then(meta => {
+ this.update({
+ initializing: false,
+ meta
+ });
+ });
+ });
+
+ this.on('unmount', () => {
+ this.connection.close();
+ });
+
+ this.toggle = () => {
+ this.view++;
+ if (this.view == 6) this.view = 0;
+ };
+ </script>
+</mk-server-home-widget>
+
+<mk-server-home-widget-cpu-and-memory-usage>
+ <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
+ <defs>
+ <linearGradient id={ cpuGradientId } x1="0" x2="0" y1="1" y2="0">
+ <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
+ <stop offset="33%" stop-color="hsl(120, 80%, 70%)"></stop>
+ <stop offset="66%" stop-color="hsl(60, 80%, 70%)"></stop>
+ <stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
+ </linearGradient>
+ <mask id={ cpuMaskId } x="0" y="0" riot-width={ viewBoxX } riot-height={ viewBoxY }>
+ <polygon
+ riot-points={ cpuPolygonPoints }
+ fill="#fff"
+ fill-opacity="0.5"/>
+ <polyline
+ riot-points={ cpuPolylinePoints }
+ fill="none"
+ stroke="#fff"
+ stroke-width="1"/>
+ </mask>
+ </defs>
+ <rect
+ x="-1" y="-1"
+ riot-width={ viewBoxX + 2 } riot-height={ viewBoxY + 2 }
+ style="stroke: none; fill: url(#{ cpuGradientId }); mask: url(#{ cpuMaskId })"/>
+ <text x="1" y="5">CPU <tspan>{ cpuP }%</tspan></text>
+ </svg>
+ <svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
+ <defs>
+ <linearGradient id={ memGradientId } x1="0" x2="0" y1="1" y2="0">
+ <stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop>
+ <stop offset="33%" stop-color="hsl(120, 80%, 70%)"></stop>
+ <stop offset="66%" stop-color="hsl(60, 80%, 70%)"></stop>
+ <stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop>
+ </linearGradient>
+ <mask id={ memMaskId } x="0" y="0" riot-width={ viewBoxX } riot-height={ viewBoxY }>
+ <polygon
+ riot-points={ memPolygonPoints }
+ fill="#fff"
+ fill-opacity="0.5"/>
+ <polyline
+ riot-points={ memPolylinePoints }
+ fill="none"
+ stroke="#fff"
+ stroke-width="1"/>
+ </mask>
+ </defs>
+ <rect
+ x="-1" y="-1"
+ riot-width={ viewBoxX + 2 } riot-height={ viewBoxY + 2 }
+ style="stroke: none; fill: url(#{ memGradientId }); mask: url(#{ memMaskId })"/>
+ <text x="1" y="5">MEM <tspan>{ memP }%</tspan></text>
+ </svg>
+ <style>
+ :scope
+ display block
+
+ > svg
+ display block
+ padding 10px
+ width 50%
+ float left
+
+ &:first-child
+ padding-right 5px
+
+ &:last-child
+ padding-left 5px
+
+ > text
+ font-size 5px
+ fill rgba(0, 0, 0, 0.55)
+
+ > tspan
+ opacity 0.5
+
+ &:after
+ content ""
+ display block
+ clear both
+ </style>
+ <script>
+ import uuid from '../../../common/scripts/uuid';
+
+ this.viewBoxX = 50;
+ this.viewBoxY = 30;
+ this.stats = [];
+ this.connection = this.opts.connection;
+ this.cpuGradientId = uuid();
+ this.cpuMaskId = uuid();
+ this.memGradientId = uuid();
+ this.memMaskId = uuid();
+
+ this.on('mount', () => {
+ this.connection.on('stats', this.onStats);
+ });
+
+ this.on('unmount', () => {
+ this.connection.off('stats', this.onStats);
+ });
+
+ this.onStats = stats => {
+ stats.mem.used = stats.mem.total - stats.mem.free;
+ this.stats.push(stats);
+ if (this.stats.length > 50) this.stats.shift();
+
+ const cpuPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - s.cpu_usage) * this.viewBoxY}`).join(' ');
+ const memPolylinePoints = this.stats.map((s, i) => `${this.viewBoxX - ((this.stats.length - 1) - i)},${(1 - (s.mem.used / s.mem.total)) * this.viewBoxY}`).join(' ');
+
+ const cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ cpuPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
+ const memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${ this.viewBoxY } ${ memPolylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
+
+ const cpuP = (stats.cpu_usage * 100).toFixed(0);
+ const memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
+
+ this.update({
+ cpuPolylinePoints,
+ memPolylinePoints,
+ cpuPolygonPoints,
+ memPolygonPoints,
+ cpuP,
+ memP
+ });
+ };
+ </script>
+</mk-server-home-widget-cpu-and-memory-usage>
+
+<mk-server-home-widget-cpu>
+ <mk-server-home-widget-pie ref="pie"/>
+ <div>
+ <p><i class="fa fa-microchip"></i>CPU</p>
+ <p>{ cores } Cores</p>
+ <p>{ model }</p>
+ </div>
+ <style>
+ :scope
+ display block
+
+ > mk-server-home-widget-pie
+ padding 10px
+ height 100px
+ float left
+
+ > div
+ float left
+ width calc(100% - 100px)
+ padding 10px 10px 10px 0
+
+ > p
+ margin 0
+ font-size 12px
+ color #505050
+
+ &:first-child
+ font-weight bold
+
+ > i
+ margin-right 4px
+
+ &:after
+ content ""
+ display block
+ clear both
+
+ </style>
+ <script>
+ this.cores = this.opts.meta.cpu.cores;
+ this.model = this.opts.meta.cpu.model;
+ this.connection = this.opts.connection;
+
+ this.on('mount', () => {
+ this.connection.on('stats', this.onStats);
+ });
+
+ this.on('unmount', () => {
+ this.connection.off('stats', this.onStats);
+ });
+
+ this.onStats = stats => {
+ this.refs.pie.render(stats.cpu_usage);
+ };
+ </script>
+</mk-server-home-widget-cpu>
+
+<mk-server-home-widget-memory>
+ <mk-server-home-widget-pie ref="pie"/>
+ <div>
+ <p><i class="fa fa-flask"></i>Memory</p>
+ <p>Total: { bytesToSize(total, 1) }</p>
+ <p>Used: { bytesToSize(used, 1) }</p>
+ <p>Free: { bytesToSize(free, 1) }</p>
+ </div>
+ <style>
+ :scope
+ display block
+
+ > mk-server-home-widget-pie
+ padding 10px
+ height 100px
+ float left
+
+ > div
+ float left
+ width calc(100% - 100px)
+ padding 10px 10px 10px 0
+
+ > p
+ margin 0
+ font-size 12px
+ color #505050
+
+ &:first-child
+ font-weight bold
+
+ > i
+ margin-right 4px
+
+ &:after
+ content ""
+ display block
+ clear both
+
+ </style>
+ <script>
+ import bytesToSize from '../../../common/scripts/bytes-to-size';
+
+ this.connection = this.opts.connection;
+ this.bytesToSize = bytesToSize;
+
+ this.on('mount', () => {
+ this.connection.on('stats', this.onStats);
+ });
+
+ this.on('unmount', () => {
+ this.connection.off('stats', this.onStats);
+ });
+
+ this.onStats = stats => {
+ stats.mem.used = stats.mem.total - stats.mem.free;
+ this.refs.pie.render(stats.mem.used / stats.mem.total);
+
+ this.update({
+ total: stats.mem.total,
+ used: stats.mem.used,
+ free: stats.mem.free
+ });
+ };
+ </script>
+</mk-server-home-widget-memory>
+
+<mk-server-home-widget-disk>
+ <mk-server-home-widget-pie ref="pie"/>
+ <div>
+ <p><i class="fa fa-hdd-o"></i>Storage</p>
+ <p>Total: { bytesToSize(total, 1) }</p>
+ <p>Available: { bytesToSize(available, 1) }</p>
+ <p>Used: { bytesToSize(used, 1) }</p>
+ </div>
+ <style>
+ :scope
+ display block
+
+ > mk-server-home-widget-pie
+ padding 10px
+ height 100px
+ float left
+
+ > div
+ float left
+ width calc(100% - 100px)
+ padding 10px 10px 10px 0
+
+ > p
+ margin 0
+ font-size 12px
+ color #505050
+
+ &:first-child
+ font-weight bold
+
+ > i
+ margin-right 4px
+
+ &:after
+ content ""
+ display block
+ clear both
+
+ </style>
+ <script>
+ import bytesToSize from '../../../common/scripts/bytes-to-size';
+
+ this.connection = this.opts.connection;
+ this.bytesToSize = bytesToSize;
+
+ this.on('mount', () => {
+ this.connection.on('stats', this.onStats);
+ });
+
+ this.on('unmount', () => {
+ this.connection.off('stats', this.onStats);
+ });
+
+ this.onStats = stats => {
+ stats.disk.used = stats.disk.total - stats.disk.free;
+
+ this.refs.pie.render(stats.disk.used / stats.disk.total);
+
+ this.update({
+ total: stats.disk.total,
+ used: stats.disk.used,
+ available: stats.disk.available
+ });
+ };
+ </script>
+</mk-server-home-widget-disk>
+
+<mk-server-home-widget-uptimes>
+ <p>Uptimes</p>
+ <p>Process: { process ? process.toFixed(0) : '---' }s</p>
+ <p>OS: { os ? os.toFixed(0) : '---' }s</p>
+ <style>
+ :scope
+ display block
+ padding 10px 14px
+
+ > p
+ margin 0
+ font-size 12px
+ color #505050
+
+ &:first-child
+ font-weight bold
+
+ </style>
+ <script>
+ this.connection = this.opts.connection;
+
+ this.on('mount', () => {
+ this.connection.on('stats', this.onStats);
+ });
+
+ this.on('unmount', () => {
+ this.connection.off('stats', this.onStats);
+ });
+
+ this.onStats = stats => {
+ this.update({
+ process: stats.process_uptime,
+ os: stats.os_uptime
+ });
+ };
+ </script>
+</mk-server-home-widget-uptimes>
+
+<mk-server-home-widget-info>
+ <p>Maintainer: <b>{ meta.maintainer }</b></p>
+ <p>Machine: { meta.machine }</p>
+ <p>Node: { meta.node }</p>
+ <style>
+ :scope
+ display block
+ padding 10px 14px
+
+ > p
+ margin 0
+ font-size 12px
+ color #505050
+
+ </style>
+ <script>
+ this.meta = this.opts.meta;
+ </script>
+</mk-server-home-widget-info>
+
+<mk-server-home-widget-pie>
+ <svg viewBox="0 0 1 1" preserveAspectRatio="none">
+ <circle
+ riot-r={ r }
+ cx="50%" cy="50%"
+ fill="none"
+ stroke-width="0.1"
+ stroke="rgba(0, 0, 0, 0.05)"/>
+ <circle
+ riot-r={ r }
+ cx="50%" cy="50%"
+ riot-stroke-dasharray={ Math.PI * (r * 2) }
+ riot-stroke-dashoffset={ strokeDashoffset }
+ fill="none"
+ stroke-width="0.1"
+ riot-stroke={ color }/>
+ <text x="50%" y="50%" dy="0.05" text-anchor="middle">{ (p * 100).toFixed(0) }%</text>
+ </svg>
+ <style>
+ :scope
+ display block
+
+ > svg
+ display block
+ height 100%
+
+ > circle
+ transform-origin center
+ transform rotate(-90deg)
+ transition stroke-dashoffset 0.5s ease
+
+ > text
+ font-size 0.15px
+ fill rgba(0, 0, 0, 0.6)
+
+ </style>
+ <script>
+ this.r = 0.4;
+
+ this.render = p => {
+ const color = `hsl(${180 - (p * 180)}, 80%, 70%)`;
+ const strokeDashoffset = (1 - p) * (Math.PI * (this.r * 2));
+
+ this.update({
+ p,
+ color,
+ strokeDashoffset
+ });
+ };
+ </script>
+</mk-server-home-widget-pie>
diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag
index 0e4a2ced10..37b2d3cf7e 100644
--- a/src/web/app/desktop/tags/home.tag
+++ b/src/web/app/desktop/tags/home.tag
@@ -77,6 +77,7 @@
'notifications',
'user-recommendation',
'recommended-polls',
+ 'server',
'donation',
'nav',
'tips'
diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js
index 1e0ebd44cf..177ba41293 100644
--- a/src/web/app/desktop/tags/index.js
+++ b/src/web/app/desktop/tags/index.js
@@ -45,6 +45,7 @@ require('./home-widgets/version.tag');
require('./home-widgets/recommended-polls.tag');
require('./home-widgets/trends.tag');
require('./home-widgets/activity.tag');
+require('./home-widgets/server.tag');
require('./timeline.tag');
require('./messaging/window.tag');
require('./messaging/room-window.tag');
diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag
index ce24ac4375..21e4fe7fa5 100644
--- a/src/web/app/desktop/tags/notifications.tag
+++ b/src/web/app/desktop/tags/notifications.tag
@@ -63,7 +63,7 @@
<p class="date" if={ i != notifications.length - 1 && notification._date != notifications[i + 1]._date }><span><i class="fa fa-angle-up"></i>{ notification._datetext }</span><span><i class="fa fa-angle-down"></i>{ notifications[i + 1]._datetext }</span></p>
</virtual>
</div>
- <button class="more" if={ moreNotifications } onclick={ fetchMoreNotifications } disabled={ fetchingMoreNotifications }>
+ <button class="more { fetching: fetchingMoreNotifications }" if={ moreNotifications } onclick={ fetchMoreNotifications } disabled={ fetchingMoreNotifications }>
<i class="fa fa-spinner fa-pulse fa-fw" if={ fetchingMoreNotifications }></i>{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:desktop.tags.mk-notifications.more%' }
</button>
<p class="empty" if={ notifications.length == 0 && !loading }>ありません!</p>
@@ -184,6 +184,9 @@
&:active
background rgba(0, 0, 0, 0.05)
+ &.fetching
+ cursor wait
+
> i
margin-right 4px
diff --git a/src/web/app/desktop/tags/post-form.tag b/src/web/app/desktop/tags/post-form.tag
index 024cd095e5..6a363d67cd 100644
--- a/src/web/app/desktop/tags/post-form.tag
+++ b/src/web/app/desktop/tags/post-form.tag
@@ -16,7 +16,7 @@
<mk-uploader ref="uploader"/>
<button ref="upload" title="%i18n:desktop.tags.mk-post-form.attach-media-from-local%" onclick={ selectFile }><i class="fa fa-upload"></i></button>
<button ref="drive" title="%i18n:desktop.tags.mk-post-form.attach-media-from-drive%" onclick={ selectFileFromDrive }><i class="fa fa-cloud"></i></button>
- <button class="cat" title="%i18n:desktop.tags.mk-post-form.insert-the-cat%" onclick={ cat }><i class="fa fa-smile-o"></i></button>
+ <button class="kao" title="%i18n:desktop.tags.mk-post-form.insert-a-kao%" onclick={ kao }><i class="fa fa-smile-o"></i></button>
<button class="poll" title="%i18n:desktop.tags.mk-post-form.create-poll%" onclick={ addPoll }><i class="fa fa-pie-chart"></i></button>
<p class="text-count { over: refs.text.value.length > 1000 }">{ '%i18n:desktop.tags.mk-post-form.text-remain%'.replace('{}', 1000 - refs.text.value.length) }</p>
<button class={ wait: wait } ref="submit" disabled={ wait || (refs.text.value.length == 0 && files.length == 0 && !poll && !repost) } onclick={ post }>
@@ -258,7 +258,7 @@
[ref='upload']
[ref='drive']
- .cat
+ .kao
.poll
display inline-block
cursor pointer
@@ -306,7 +306,7 @@
</style>
<script>
- import getCat from '../../common/scripts/get-cat';
+ import getKao from '../../common/scripts/get-kao';
import notify from '../scripts/notify';
import Autocomplete from '../scripts/autocomplete';
@@ -500,8 +500,8 @@
});
};
- this.cat = () => {
- this.refs.text.value += getCat();
+ this.kao = () => {
+ this.refs.text.value += getKao();
};
this.on('update', () => {
diff --git a/src/web/app/init.js b/src/web/app/init.js
index 17f9a2d09e..44391b8fcb 100644
--- a/src/web/app/init.js
+++ b/src/web/app/init.js
@@ -8,7 +8,8 @@ import * as riot from 'riot';
import api from './common/scripts/api';
import signout from './common/scripts/signout';
import checkForUpdate from './common/scripts/check-for-update';
-import Connection from './common/scripts/stream';
+import Connection from './common/scripts/home-stream';
+import Progress from './common/scripts/loading';
import mixin from './common/mixins';
import generateDefaultUserdata from './common/scripts/generate-default-userdata';
import CONFIG from './common/scripts/config';
@@ -95,7 +96,7 @@ export default callback => {
});
}
- // Init stream connection
+ // Init home stream connection
const stream = me ? new Connection(me) : null;
// ミックスイン初期化
@@ -147,9 +148,10 @@ function fetchme(token, cb) {
me.data ? done() : init();
});
}, () => { // When failure
- // Display error screen
- riot.mount(document.body.appendChild(
- document.createElement('mk-error')));
+ // Render the error screen
+ document.body.innerHTML = '<mk-error />';
+ riot.mount('*');
+ Progress.done();
});
function done() {
@@ -173,6 +175,7 @@ function panic(e) {
console.error(e);
// Display blue screen
+ document.documentElement.style.background = '#1269e2';
document.body.innerHTML =
'<div id="error">'
+ '<h1>:( 致命的な問題が発生しました。</h1>'
diff --git a/src/web/app/mobile/tags/drive.tag b/src/web/app/mobile/tags/drive.tag
index bafa5a4fca..e19325091d 100644
--- a/src/web/app/mobile/tags/drive.tag
+++ b/src/web/app/mobile/tags/drive.tag
@@ -39,7 +39,7 @@
</button>
</div>
<div class="empty" if={ files.length == 0 && folders.length == 0 && !fetching }>
- <p if={ !folder == null }>%i18n:mobile.tags.mk-drive.nothing-in-drive%</p>
+ <p if={ folder == null }>%i18n:mobile.tags.mk-drive.nothing-in-drive%</p>
<p if={ folder != null }>%i18n:mobile.tags.mk-drive.folder-is-empty%</p>
</div>
</div>
diff --git a/src/web/app/mobile/tags/page/drive.tag b/src/web/app/mobile/tags/page/drive.tag
index a5ebf480d5..1169e3b9eb 100644
--- a/src/web/app/mobile/tags/page/drive.tag
+++ b/src/web/app/mobile/tags/page/drive.tag
@@ -63,7 +63,7 @@
document.title = title;
// TODO: escape html characters in file.name
- ui.trigger('title', '<mk-file-type-icon class="icon"/>' + file.name);
+ ui.trigger('title', '<mk-file-type-icon class="icon"></mk-file-type-icon>' + file.name);
riot.mount('mk-file-type-icon', {
type: file.type
});
diff --git a/src/web/app/mobile/tags/post-form.tag b/src/web/app/mobile/tags/post-form.tag
index 239473703b..28c7796840 100644
--- a/src/web/app/mobile/tags/post-form.tag
+++ b/src/web/app/mobile/tags/post-form.tag
@@ -23,7 +23,7 @@
<mk-uploader ref="uploader"/>
<button ref="upload" onclick={ selectFile }><i class="fa fa-upload"></i></button>
<button ref="drive" onclick={ selectFileFromDrive }><i class="fa fa-cloud"></i></button>
- <button class="cat" onclick={ cat }><i class="fa fa-smile-o"></i></button>
+ <button class="kao" onclick={ kao }><i class="fa fa-smile-o"></i></button>
<button class="poll" onclick={ addPoll }><i class="fa fa-pie-chart"></i></button>
<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
</div>
@@ -165,7 +165,7 @@
> [ref='upload']
> [ref='drive']
- .cat
+ .kao
.poll
display inline-block
padding 0
@@ -182,7 +182,7 @@
</style>
<script>
- import getCat from '../../common/scripts/get-cat';
+ import getKao from '../../common/scripts/get-kao';
this.mixin('api');
@@ -285,8 +285,8 @@
this.unmount();
};
- this.cat = () => {
- this.refs.text.value += getCat();
+ this.kao = () => {
+ this.refs.text.value += getKao();
};
</script>
</mk-post-form>
diff --git a/src/web/app/mobile/tags/ui-header.tag b/src/web/app/mobile/tags/ui-header.tag
index 264fa022d8..10b44b2153 100644
--- a/src/web/app/mobile/tags/ui-header.tag
+++ b/src/web/app/mobile/tags/ui-header.tag
@@ -4,6 +4,7 @@
<div class="backdrop"></div>
<div class="content">
<button class="nav" onclick={ parent.toggleDrawer }><i class="fa fa-bars"></i></button>
+ <i class="fa fa-circle" if={ hasUnreadMessagingMessages }></i>
<h1 ref="title">Misskey</h1>
<button if={ func } onclick={ func }><i class="fa fa-{ funcIcon }"></i></button>
</div>
@@ -74,6 +75,14 @@
> i
transition all 0.2s ease
+ > i
+ position absolute
+ top 8px
+ left 8px
+ pointer-events none
+ font-size 10px
+ color $theme-color
+
> button:last-child
display block
position absolute
@@ -90,14 +99,46 @@
<script>
import ui from '../scripts/ui-event';
+ this.mixin('api');
+ this.mixin('stream');
+
this.func = null;
this.funcIcon = null;
+ this.on('mount', () => {
+ this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+ this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+
+ // Fetch count of unread messaging messages
+ this.api('messaging/unread').then(res => {
+ if (res.count > 0) {
+ this.update({
+ hasUnreadMessagingMessages: true
+ });
+ }
+ });
+ });
+
this.on('unmount', () => {
+ this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+ this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+
ui.off('title', this.setTitle);
ui.off('func', this.setFunc);
});
+ this.onReadAllMessagingMessages = () => {
+ this.update({
+ hasUnreadMessagingMessages: false
+ });
+ };
+
+ this.onUnreadMessagingMessage = () => {
+ this.update({
+ hasUnreadMessagingMessages: true
+ });
+ };
+
this.setTitle = title => {
this.refs.title.innerHTML = title;
};
diff --git a/src/web/app/mobile/tags/ui-nav.tag b/src/web/app/mobile/tags/ui-nav.tag
index 964eab9a58..76c43ade66 100644
--- a/src/web/app/mobile/tags/ui-nav.tag
+++ b/src/web/app/mobile/tags/ui-nav.tag
@@ -7,18 +7,18 @@
</a>
<div class="links">
<ul>
- <li class="home"><a href="/"><i class="icon fa fa-home"></i>%i18n:mobile.tags.mk-ui-nav.home%<i class="angle fa fa-angle-right"></i></a></li>
- <li class="notifications"><a href="/i/notifications"><i class="icon fa fa-bell-o"></i>%i18n:mobile.tags.mk-ui-nav.notifications%<i class="angle fa fa-angle-right"></i></a></li>
- <li class="messaging"><a href="/i/messaging"><i class="icon fa fa-comments-o"></i>%i18n:mobile.tags.mk-ui-nav.messaging%<i class="angle fa fa-angle-right"></i></a></li>
+ <li><a href="/"><i class="fa fa-home"></i>%i18n:mobile.tags.mk-ui-nav.home%<i class="fa fa-angle-right"></i></a></li>
+ <li><a href="/i/notifications"><i class="fa fa-bell-o"></i>%i18n:mobile.tags.mk-ui-nav.notifications%<i class="fa fa-angle-right"></i></a></li>
+ <li><a href="/i/messaging"><i class="fa fa-comments-o"></i>%i18n:mobile.tags.mk-ui-nav.messaging%<i class="i fa fa-circle" if={ hasUnreadMessagingMessages }></i><i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
- <li class="settings"><a onclick={ search }><i class="icon fa fa-search"></i>%i18n:mobile.tags.mk-ui-nav.search%<i class="angle fa fa-angle-right"></i></a></li>
+ <li><a onclick={ search }><i class="fa fa-search"></i>%i18n:mobile.tags.mk-ui-nav.search%<i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
- <li class="settings"><a href="/i/drive"><i class="icon fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="angle fa fa-angle-right"></i></a></li>
+ <li><a href="/i/drive"><i class="fa fa-cloud"></i>%i18n:mobile.tags.mk-ui-nav.drive%<i class="fa fa-angle-right"></i></a></li>
</ul>
<ul>
- <li class="settings"><a href="/i/settings"><i class="icon fa fa-cog"></i>%i18n:mobile.tags.mk-ui-nav.settings%<i class="angle fa fa-angle-right"></i></a></li>
+ <li><a href="/i/settings"><i class="fa fa-cog"></i>%i18n:mobile.tags.mk-ui-nav.settings%<i class="fa fa-angle-right"></i></a></li>
</ul>
</div>
<a href={ CONFIG.aboutUrl }><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
@@ -94,10 +94,16 @@
color #777
text-decoration none
- > .icon
+ > i:first-child
margin-right 0.5em
- > .angle
+ > .i
+ margin-left 6px
+ vertical-align super
+ font-size 10px
+ color $theme-color
+
+ > i:last-child
position absolute
top 0
right 0
@@ -120,6 +126,39 @@
<script>
this.mixin('i');
this.mixin('page');
+ this.mixin('api');
+ this.mixin('stream');
+
+ this.on('mount', () => {
+ this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+ this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+
+ // Fetch count of unread messaging messages
+ this.api('messaging/unread').then(res => {
+ if (res.count > 0) {
+ this.update({
+ hasUnreadMessagingMessages: true
+ });
+ }
+ });
+ });
+
+ this.on('unmount', () => {
+ this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+ this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+ });
+
+ this.onReadAllMessagingMessages = () => {
+ this.update({
+ hasUnreadMessagingMessages: false
+ });
+ };
+
+ this.onUnreadMessagingMessage = () => {
+ this.update({
+ hasUnreadMessagingMessages: true
+ });
+ };
this.search = () => {
const query = window.prompt('%i18n:mobile.tags.mk-ui-nav.search%');
diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag
index 9f36e60741..81eb6ba2e4 100644
--- a/src/web/app/mobile/tags/user.tag
+++ b/src/web/app/mobile/tags/user.tag
@@ -5,7 +5,7 @@
<div class="body">
<div class="top">
<a class="avatar">
- <img src={ user.avatar_url + '?thumbnail&size=160' } alt="avatar"/>
+ <img src={ user.avatar_url + '?thumbnail&size=200' } alt="avatar"/>
</a>
<mk-follow-button if={ SIGNIN && I.id != user.id } user={ user }/>
</div>