Merge branch 'master' into greenkeeper/@types/mongodb-2.2.4
This commit is contained in:
commit
cb4b120548
44 changed files with 1323 additions and 374 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,3 +7,4 @@ npm-debug.log
|
||||||
*.pem
|
*.pem
|
||||||
run.bat
|
run.bat
|
||||||
api-docs.json
|
api-docs.json
|
||||||
|
package-lock.json
|
||||||
|
|
|
@ -16,10 +16,10 @@ Key features
|
||||||
----------------------------------------------------------------
|
----------------------------------------------------------------
|
||||||
* Automatically updated timeline
|
* Automatically updated timeline
|
||||||
* Private messages
|
* Private messages
|
||||||
* Free 1GB storage
|
* Free 1GB storage for each all users
|
||||||
* Mobile device support (smartphone, tablet, etc)
|
* Mobile device support (smartphone, tablet, etc)
|
||||||
* Web API for third-party applications
|
* Web API for third-party applications
|
||||||
* Twitter integration
|
* No ads
|
||||||
|
|
||||||
and more! You can touch with your own eyes at https://misskey.xyz/.
|
and more! You can touch with your own eyes at https://misskey.xyz/.
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,8 @@ common:
|
||||||
|
|
||||||
mk-messaging-room:
|
mk-messaging-room:
|
||||||
empty: "No conversations"
|
empty: "No conversations"
|
||||||
|
more: "More"
|
||||||
|
no-history: "There is no more history"
|
||||||
resize-form: "Drag to resize"
|
resize-form: "Drag to resize"
|
||||||
new-message: "New message"
|
new-message: "New message"
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ common:
|
||||||
no-apps: "No apps"
|
no-apps: "No apps"
|
||||||
|
|
||||||
mk-error:
|
mk-error:
|
||||||
title: "Cannot connect to the server"
|
title: "Unable to connect to the server"
|
||||||
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
description: "インターネット回線に問題があるか、サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。"
|
||||||
thanks: "Thank you for using Misskey."
|
thanks: "Thank you for using Misskey."
|
||||||
|
|
||||||
|
@ -219,7 +221,7 @@ desktop:
|
||||||
attach-media-from-local: "Attach media from your pc"
|
attach-media-from-local: "Attach media from your pc"
|
||||||
attach-media-from-drive: "Attach media from the drive"
|
attach-media-from-drive: "Attach media from the drive"
|
||||||
attach-cancel: "Cancel attachment"
|
attach-cancel: "Cancel attachment"
|
||||||
insert-the-cat: "Insert a cat"
|
insert-a-kao: "v(‘ω’)v"
|
||||||
create-poll: "Create a poll"
|
create-poll: "Create a poll"
|
||||||
text-remain: "{} chars remaining"
|
text-remain: "{} chars remaining"
|
||||||
|
|
||||||
|
@ -243,8 +245,13 @@ desktop:
|
||||||
title: "Notifications"
|
title: "Notifications"
|
||||||
settings: "Notification settings"
|
settings: "Notification settings"
|
||||||
|
|
||||||
|
mk-server-home-widget:
|
||||||
|
title: "Server info"
|
||||||
|
toggle: "Toggle views"
|
||||||
|
|
||||||
mk-activity-home-widget:
|
mk-activity-home-widget:
|
||||||
title: "Activity"
|
title: "Activity"
|
||||||
|
toggle: "Toggle views"
|
||||||
|
|
||||||
mk-user-recommendation-home-widget:
|
mk-user-recommendation-home-widget:
|
||||||
title: "Recommended users"
|
title: "Recommended users"
|
||||||
|
|
|
@ -45,6 +45,8 @@ common:
|
||||||
|
|
||||||
mk-messaging-room:
|
mk-messaging-room:
|
||||||
empty: "このユーザーと話したことはありません"
|
empty: "このユーザーと話したことはありません"
|
||||||
|
more: "もっと読む"
|
||||||
|
no-history: "これより過去の履歴はありません"
|
||||||
resize-form: "ドラッグしてフォームの広さを調整"
|
resize-form: "ドラッグしてフォームの広さを調整"
|
||||||
new-message: "新しいメッセージがあります"
|
new-message: "新しいメッセージがあります"
|
||||||
|
|
||||||
|
@ -219,7 +221,7 @@ desktop:
|
||||||
attach-media-from-local: "PCからメディアを添付"
|
attach-media-from-local: "PCからメディアを添付"
|
||||||
attach-media-from-drive: "ドライブからメディアを添付"
|
attach-media-from-drive: "ドライブからメディアを添付"
|
||||||
attach-cancel: "添付取り消し"
|
attach-cancel: "添付取り消し"
|
||||||
insert-the-cat: "猫挿入"
|
insert-a-kao: "v(‘ω’)v"
|
||||||
create-poll: "投票を作成"
|
create-poll: "投票を作成"
|
||||||
text-remain: "のこり{}文字"
|
text-remain: "のこり{}文字"
|
||||||
|
|
||||||
|
@ -243,8 +245,13 @@ desktop:
|
||||||
title: "通知"
|
title: "通知"
|
||||||
settings: "通知の設定"
|
settings: "通知の設定"
|
||||||
|
|
||||||
|
mk-server-home-widget:
|
||||||
|
title: "サーバー情報"
|
||||||
|
toggle: "表示を切り替え"
|
||||||
|
|
||||||
mk-activity-home-widget:
|
mk-activity-home-widget:
|
||||||
title: "アクティビティ"
|
title: "アクティビティ"
|
||||||
|
toggle: "表示を切り替え"
|
||||||
|
|
||||||
mk-user-recommendation-home-widget:
|
mk-user-recommendation-home-widget:
|
||||||
title: "おすすめユーザー"
|
title: "おすすめユーザー"
|
||||||
|
|
299
package.json
299
package.json
|
@ -1,150 +1,153 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "0.0.2027",
|
"version": "0.0.2097",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "A miniblog-based SNS",
|
"description": "A miniblog-based SNS",
|
||||||
"bugs": "https://github.com/syuilo/misskey/issues",
|
"bugs": "https://github.com/syuilo/misskey/issues",
|
||||||
"repository": "https://github.com/syuilo/misskey.git",
|
"repository": "https://github.com/syuilo/misskey.git",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"config": "node ./tools/init.js",
|
"config": "node ./tools/init.js",
|
||||||
"start": "node ./built",
|
"start": "node ./built",
|
||||||
"debug": "DEBUG=misskey:* node ./built",
|
"debug": "DEBUG=misskey:* node ./built",
|
||||||
"swagger": "node ./swagger.js",
|
"swagger": "node ./swagger.js",
|
||||||
"build": "gulp build",
|
"build": "gulp build",
|
||||||
"rebuild": "gulp rebuild",
|
"rebuild": "gulp rebuild",
|
||||||
"clean": "gulp clean",
|
"clean": "gulp clean",
|
||||||
"cleanall": "gulp cleanall",
|
"cleanall": "gulp cleanall",
|
||||||
"lint": "gulp lint",
|
"lint": "gulp lint",
|
||||||
"test": "gulp test"
|
"test": "gulp test"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcryptjs": "2.4.0",
|
"@types/bcryptjs": "2.4.0",
|
||||||
"@types/body-parser": "1.16.3",
|
"@types/body-parser": "1.16.3",
|
||||||
"@types/chai": "4.0.0",
|
"@types/chai": "4.0.0",
|
||||||
"@types/chai-http": "0.0.30",
|
"@types/chai-http": "0.0.30",
|
||||||
"@types/chalk": "0.4.31",
|
"@types/chalk": "0.4.31",
|
||||||
"@types/compression": "0.0.33",
|
"@types/compression": "0.0.33",
|
||||||
"@types/cors": "2.8.1",
|
"@types/cors": "2.8.1",
|
||||||
"@types/debug": "0.0.29",
|
"@types/debug": "0.0.29",
|
||||||
"@types/deep-equal": "1.0.0",
|
"@types/deep-equal": "1.0.0",
|
||||||
"@types/elasticsearch": "5.0.13",
|
"@types/elasticsearch": "5.0.13",
|
||||||
"@types/event-stream": "3.3.31",
|
"@types/event-stream": "3.3.31",
|
||||||
"@types/express": "4.0.35",
|
"@types/express": "4.0.35",
|
||||||
"@types/gm": "1.17.31",
|
"@types/gm": "1.17.31",
|
||||||
"@types/gulp": "4.0.3",
|
"@types/gulp": "4.0.3",
|
||||||
"@types/gulp-mocha": "0.0.30",
|
"@types/gulp-mocha": "0.0.30",
|
||||||
"@types/gulp-rename": "0.0.32",
|
"@types/gulp-rename": "0.0.32",
|
||||||
"@types/gulp-replace": "0.0.30",
|
"@types/gulp-replace": "0.0.30",
|
||||||
"@types/gulp-tslint": "3.6.31",
|
"@types/gulp-tslint": "3.6.31",
|
||||||
"@types/gulp-typescript": "2.13.0",
|
"@types/gulp-typescript": "2.13.0",
|
||||||
"@types/gulp-uglify": "0.0.30",
|
"@types/gulp-uglify": "0.0.30",
|
||||||
"@types/gulp-util": "3.0.31",
|
"@types/gulp-util": "3.0.31",
|
||||||
"@types/inquirer": "0.0.34",
|
"@types/inquirer": "0.0.34",
|
||||||
"@types/is-root": "1.0.0",
|
"@types/is-root": "1.0.0",
|
||||||
"@types/is-url": "1.2.28",
|
"@types/is-url": "1.2.28",
|
||||||
"@types/js-yaml": "3.5.30",
|
"@types/js-yaml": "3.5.30",
|
||||||
"@types/mocha": "2.2.41",
|
"@types/mocha": "2.2.41",
|
||||||
"@types/mongodb": "2.2.4",
|
"@types/mongodb": "2.2.4",
|
||||||
"@types/monk": "1.0.5",
|
"@types/monk": "1.0.5",
|
||||||
"@types/morgan": "1.7.32",
|
"@types/morgan": "1.7.32",
|
||||||
"@types/ms": "0.7.29",
|
"@types/ms": "0.7.29",
|
||||||
"@types/multer": "0.0.34",
|
"@types/multer": "0.0.34",
|
||||||
"@types/node": "7.0.29",
|
"@types/node": "7.0.31",
|
||||||
"@types/ratelimiter": "2.1.28",
|
"@types/ratelimiter": "2.1.28",
|
||||||
"@types/redis": "2.6.0",
|
"@types/redis": "2.6.0",
|
||||||
"@types/request": "0.0.43",
|
"@types/request": "0.0.43",
|
||||||
"@types/rimraf": "0.0.28",
|
"@types/rimraf": "0.0.28",
|
||||||
"@types/riot": "2.6.2",
|
"@types/riot": "2.6.2",
|
||||||
"@types/serve-favicon": "2.2.28",
|
"@types/serve-favicon": "2.2.28",
|
||||||
"@types/uuid": "3.0.0",
|
"@types/uuid": "3.0.0",
|
||||||
"@types/webpack": "2.2.15",
|
"@types/webpack": "2.2.15",
|
||||||
"@types/webpack-stream": "3.2.7",
|
"@types/webpack-stream": "3.2.7",
|
||||||
"@types/websocket": "0.0.33",
|
"@types/websocket": "0.0.33",
|
||||||
"chai": "4.0.2",
|
"chai": "4.0.2",
|
||||||
"chai-http": "3.0.0",
|
"chai-http": "3.0.0",
|
||||||
"css-loader": "0.28.4",
|
"css-loader": "0.28.4",
|
||||||
"event-stream": "3.3.4",
|
"event-stream": "3.3.4",
|
||||||
"gulp": "3.9.1",
|
"gulp": "3.9.1",
|
||||||
"gulp-cssnano": "2.1.2",
|
"gulp-cssnano": "2.1.2",
|
||||||
"gulp-imagemin": "3.3.0",
|
"gulp-imagemin": "3.3.0",
|
||||||
"gulp-mocha": "4.3.1",
|
"gulp-mocha": "4.3.1",
|
||||||
"gulp-pug": "3.3.0",
|
"gulp-pug": "3.3.0",
|
||||||
"gulp-rename": "1.2.2",
|
"gulp-rename": "1.2.2",
|
||||||
"gulp-replace": "0.5.4",
|
"gulp-replace": "0.5.4",
|
||||||
"gulp-tslint": "8.1.1",
|
"gulp-tslint": "8.1.1",
|
||||||
"gulp-typescript": "3.1.7",
|
"gulp-typescript": "3.1.7",
|
||||||
"gulp-uglify": "3.0.0",
|
"gulp-uglify": "3.0.0",
|
||||||
"gulp-util": "3.0.8",
|
"gulp-util": "3.0.8",
|
||||||
"mocha": "3.4.2",
|
"mocha": "3.4.2",
|
||||||
"riot-tag-loader": "1.0.0",
|
"riot-tag-loader": "1.0.0",
|
||||||
"string-replace-webpack-plugin": "0.1.3",
|
"string-replace-webpack-plugin": "0.1.3",
|
||||||
"style-loader": "0.18.2",
|
"style-loader": "0.18.2",
|
||||||
"stylus": "0.54.5",
|
"stylus": "0.54.5",
|
||||||
"stylus-loader": "3.0.1",
|
"stylus-loader": "3.0.1",
|
||||||
"swagger-jsdoc": "1.9.4",
|
"swagger-jsdoc": "1.9.4",
|
||||||
"tslint": "5.4.3",
|
"tslint": "5.4.3",
|
||||||
"uglify-es": "3.0.15",
|
"uglify-es": "3.0.15",
|
||||||
"uglify-es-webpack-plugin": "0.0.2",
|
"uglify-es-webpack-plugin": "0.0.2",
|
||||||
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
|
"uglify-js": "git+https://github.com/mishoo/UglifyJS2.git#harmony",
|
||||||
"webpack": "2.6.1"
|
"webpack": "2.6.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accesses": "2.5.0",
|
"accesses": "2.5.0",
|
||||||
"animejs": "2.0.2",
|
"animejs": "2.0.2",
|
||||||
"autwh": "0.0.1",
|
"autwh": "0.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"body-parser": "1.17.2",
|
"body-parser": "1.17.2",
|
||||||
"cafy": "2.4.0",
|
"cafy": "2.4.0",
|
||||||
"chalk": "1.1.3",
|
"chalk": "1.1.3",
|
||||||
"compression": "1.6.2",
|
"compression": "1.6.2",
|
||||||
"cors": "2.8.3",
|
"cors": "2.8.3",
|
||||||
"cropperjs": "1.0.0-rc.2",
|
"cropperjs": "1.0.0-rc.2",
|
||||||
"crypto": "0.0.3",
|
"crypto": "0.0.3",
|
||||||
"debug": "2.6.8",
|
"debug": "2.6.8",
|
||||||
"deep-equal": "1.0.1",
|
"deep-equal": "1.0.1",
|
||||||
"deepcopy": "0.6.3",
|
"deepcopy": "0.6.3",
|
||||||
"download": "6.2.2",
|
"diskusage": "^0.2.2",
|
||||||
"elasticsearch": "13.0.1",
|
"download": "6.2.2",
|
||||||
"escape-regexp": "0.0.1",
|
"elasticsearch": "13.0.1",
|
||||||
"express": "4.15.3",
|
"escape-regexp": "0.0.1",
|
||||||
"file-type": "5.0.0",
|
"express": "4.15.3",
|
||||||
"fuckadblock": "3.2.1",
|
"file-type": "5.1.1",
|
||||||
"gm": "1.23.0",
|
"fuckadblock": "3.2.1",
|
||||||
"inquirer": "3.1.0",
|
"gm": "1.23.0",
|
||||||
"is-root": "1.0.0",
|
"inquirer": "3.1.0",
|
||||||
"is-url": "1.2.2",
|
"is-root": "1.0.0",
|
||||||
"js-yaml": "3.8.4",
|
"is-url": "1.2.2",
|
||||||
"mongodb": "2.2.28",
|
"js-yaml": "3.8.4",
|
||||||
"monk": "6.0.0",
|
"mongodb": "2.2.28",
|
||||||
"morgan": "1.8.2",
|
"monk": "6.0.0",
|
||||||
"ms": "2.0.0",
|
"morgan": "1.8.2",
|
||||||
"multer": "1.3.0",
|
"ms": "2.0.0",
|
||||||
"nprogress": "0.2.0",
|
"multer": "1.3.0",
|
||||||
"page": "1.7.1",
|
"nprogress": "0.2.0",
|
||||||
"pictograph": "2.0.4",
|
"os-utils": "0.0.14",
|
||||||
"prominence": "0.2.0",
|
"page": "1.7.1",
|
||||||
"pug": "2.0.0-rc.2",
|
"pictograph": "2.0.4",
|
||||||
"ratelimiter": "3.0.3",
|
"prominence": "0.2.0",
|
||||||
"recaptcha-promise": "0.1.2",
|
"pug": "2.0.0-rc.2",
|
||||||
"reconnecting-websocket": "3.0.5",
|
"ratelimiter": "3.0.3",
|
||||||
"redis": "2.7.1",
|
"recaptcha-promise": "0.1.2",
|
||||||
"request": "2.81.0",
|
"reconnecting-websocket": "3.0.5",
|
||||||
"rimraf": "2.6.1",
|
"redis": "2.7.1",
|
||||||
"riot": "3.5.1",
|
"request": "2.81.0",
|
||||||
"rndstr": "1.0.0",
|
"rimraf": "2.6.1",
|
||||||
"s-age": "1.1.0",
|
"riot": "3.6.0",
|
||||||
"serve-favicon": "2.4.3",
|
"rndstr": "1.0.0",
|
||||||
"summaly": "2.0.3",
|
"s-age": "1.1.0",
|
||||||
"syuilo-password-strength": "0.0.1",
|
"serve-favicon": "2.4.3",
|
||||||
"tcp-port-used": "0.1.2",
|
"summaly": "2.0.3",
|
||||||
"textarea-caret": "3.0.2",
|
"syuilo-password-strength": "0.0.1",
|
||||||
"ts-node": "3.0.6",
|
"tcp-port-used": "0.1.2",
|
||||||
"typescript": "2.3.4",
|
"textarea-caret": "3.0.2",
|
||||||
"uuid": "3.0.1",
|
"ts-node": "3.0.6",
|
||||||
"vhost": "3.0.2",
|
"typescript": "2.3.4",
|
||||||
"websocket": "1.0.24"
|
"uuid": "3.0.1",
|
||||||
}
|
"vhost": "3.0.2",
|
||||||
|
"websocket": "1.0.24",
|
||||||
|
"xev": "^2.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
64
src/api/common/read-messaging-message.ts
Normal file
64
src/api/common/read-messaging-message.ts
Normal file
|
@ -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');
|
||||||
|
}
|
||||||
|
});
|
|
@ -5,8 +5,7 @@ import $ from 'cafy';
|
||||||
import Message from '../../models/messaging-message';
|
import Message from '../../models/messaging-message';
|
||||||
import User from '../../models/user';
|
import User from '../../models/user';
|
||||||
import serialize from '../../serializers/messaging-message';
|
import serialize from '../../serializers/messaging-message';
|
||||||
import publishUserStream from '../../event';
|
import read from '../../common/read-messaging-message';
|
||||||
import { publishMessagingStream } from '../../event';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get messages
|
* Get messages
|
||||||
|
@ -98,32 +97,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
|
|
||||||
// Mark as read all
|
// Mark as read all
|
||||||
if (markAsRead) {
|
if (markAsRead) {
|
||||||
const ids = messages
|
read(user._id, recipient._id, 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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -93,13 +93,13 @@ module.exports = (params, user) => new Promise(async (res, rej) => {
|
||||||
publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj);
|
publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj);
|
||||||
publishUserStream(message.recipient_id, 'messaging_message', messageObj);
|
publishUserStream(message.recipient_id, 'messaging_message', messageObj);
|
||||||
|
|
||||||
// 5秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
// 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true });
|
const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true });
|
||||||
if (!freshMessage.is_read) {
|
if (!freshMessage.is_read) {
|
||||||
publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj);
|
publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj);
|
||||||
}
|
}
|
||||||
}, 5000);
|
}, 3000);
|
||||||
|
|
||||||
// Register to search database
|
// Register to search database
|
||||||
if (message.text && config.elasticsearch.enable) {
|
if (message.text && config.elasticsearch.enable) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/**
|
/**
|
||||||
* Module dependencies
|
* Module dependencies
|
||||||
*/
|
*/
|
||||||
|
import * as os from 'os';
|
||||||
import version from '../../version';
|
import version from '../../version';
|
||||||
import config from '../../conf';
|
import config from '../../conf';
|
||||||
|
|
||||||
|
@ -41,6 +42,13 @@ module.exports = (params) => new Promise(async (res, rej) => {
|
||||||
res({
|
res({
|
||||||
maintainer: config.maintainer,
|
maintainer: config.maintainer,
|
||||||
version: version,
|
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
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -42,7 +42,7 @@ async function byNative(res, rej, me, query, offset, max) {
|
||||||
const users = await User
|
const users = await User
|
||||||
.find({
|
.find({
|
||||||
$or: [{
|
$or: [{
|
||||||
username_lower: new RegExp(escapedQuery.toLowerCase())
|
username_lower: new RegExp(escapedQuery.replace('@', '').toLowerCase())
|
||||||
}, {
|
}, {
|
||||||
name: new RegExp(escapedQuery)
|
name: new RegExp(escapedQuery)
|
||||||
}]
|
}]
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
import * as mongo from 'mongodb';
|
||||||
import db from '../../db/mongodb';
|
import db from '../../db/mongodb';
|
||||||
|
|
||||||
export default db.get('messaging_messages') as any; // fuck type definition
|
export default db.get('messaging_messages') as any; // fuck type definition
|
||||||
|
|
||||||
|
export interface IMessagingMessage {
|
||||||
|
_id: mongo.ObjectID;
|
||||||
|
}
|
||||||
|
|
||||||
export function isValidText(text: string): boolean {
|
export function isValidText(text: string): boolean {
|
||||||
return text.length <= 1000 && text.trim() != '';
|
return text.length <= 1000 && text.trim() != '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import * as mongodb from 'mongodb';
|
|
||||||
import * as websocket from 'websocket';
|
import * as websocket from 'websocket';
|
||||||
import * as redis from 'redis';
|
import * as redis from 'redis';
|
||||||
import Message from '../models/messaging-message';
|
import read from '../common/read-messaging-message';
|
||||||
import { publishMessagingStream } from '../event';
|
|
||||||
|
|
||||||
export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
|
export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void {
|
||||||
const otherparty = request.resourceURL.query.otherparty;
|
const otherparty = request.resourceURL.query.otherparty;
|
||||||
|
@ -18,42 +16,8 @@ export default function messagingStream(request: websocket.request, connection:
|
||||||
|
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case 'read':
|
case 'read':
|
||||||
if (!msg.id) {
|
if (!msg.id) return;
|
||||||
return;
|
read(user._id, otherparty, msg.id);
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
20
src/api/stream/server.ts
Normal file
20
src/api/stream/server.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ import isNativeToken from './common/is-native-token';
|
||||||
|
|
||||||
import homeStream from './stream/home';
|
import homeStream from './stream/home';
|
||||||
import messagingStream from './stream/messaging';
|
import messagingStream from './stream/messaging';
|
||||||
|
import serverStream from './stream/server';
|
||||||
|
|
||||||
module.exports = (server: http.Server) => {
|
module.exports = (server: http.Server) => {
|
||||||
/**
|
/**
|
||||||
|
@ -20,6 +21,11 @@ module.exports = (server: http.Server) => {
|
||||||
ws.on('request', async (request) => {
|
ws.on('request', async (request) => {
|
||||||
const connection = request.accept();
|
const connection = request.accept();
|
||||||
|
|
||||||
|
if (request.resourceURL.pathname === '/server') {
|
||||||
|
serverStream(request, connection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const user = await authenticate(connection, request.resourceURL.query.i);
|
const user = await authenticate(connection, request.resourceURL.query.i);
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
|
|
|
@ -12,17 +12,20 @@ import * as chalk from 'chalk';
|
||||||
// import portUsed = require('tcp-port-used');
|
// import portUsed = require('tcp-port-used');
|
||||||
import isRoot = require('is-root');
|
import isRoot = require('is-root');
|
||||||
import { master } from 'accesses';
|
import { master } from 'accesses';
|
||||||
|
import Xev from 'xev';
|
||||||
|
|
||||||
import Logger from './utils/logger';
|
import Logger from './utils/logger';
|
||||||
import ProgressBar from './utils/cli/progressbar';
|
import ProgressBar from './utils/cli/progressbar';
|
||||||
import EnvironmentInfo from './utils/environmentInfo';
|
import EnvironmentInfo from './utils/environmentInfo';
|
||||||
import MachineInfo from './utils/machineInfo';
|
import MachineInfo from './utils/machineInfo';
|
||||||
import DependencyInfo from './utils/dependencyInfo';
|
import DependencyInfo from './utils/dependencyInfo';
|
||||||
|
import stats from './utils/stats';
|
||||||
|
|
||||||
import { Config, path as configPath } from './config';
|
import { Config, path as configPath } from './config';
|
||||||
import loadConfig from './config';
|
import loadConfig from './config';
|
||||||
|
|
||||||
const clusterLog = debug('misskey:cluster');
|
const clusterLog = debug('misskey:cluster');
|
||||||
|
const ev = new Xev();
|
||||||
|
|
||||||
process.title = 'Misskey';
|
process.title = 'Misskey';
|
||||||
|
|
||||||
|
@ -35,6 +38,9 @@ main();
|
||||||
function main() {
|
function main() {
|
||||||
if (cluster.isMaster) {
|
if (cluster.isMaster) {
|
||||||
masterMain();
|
masterMain();
|
||||||
|
|
||||||
|
ev.mount();
|
||||||
|
stats();
|
||||||
} else {
|
} else {
|
||||||
workerMain();
|
workerMain();
|
||||||
}
|
}
|
||||||
|
|
27
src/utils/stats.ts
Normal file
27
src/utils/stats.ts
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ html
|
||||||
script
|
script
|
||||||
include ./../../../built/web/assets/safe.js
|
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
|
body
|
||||||
noscript: p
|
noscript: p
|
||||||
|
|
|
@ -30,14 +30,7 @@ html
|
||||||
cursor progress !important
|
cursor progress !important
|
||||||
|
|
||||||
#error
|
#error
|
||||||
position fixed
|
|
||||||
z-index 32768
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
|
||||||
height 100%
|
|
||||||
padding 32px
|
padding 32px
|
||||||
background #1269e2
|
|
||||||
color #fff
|
color #fff
|
||||||
|
|
||||||
hr
|
hr
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
export default bytes => {
|
export default (bytes, digits = 0) => {
|
||||||
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
if (bytes == 0) return '0Byte';
|
if (bytes == 0) return '0Byte';
|
||||||
var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
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];
|
||||||
};
|
};
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
export default () => '(=^・・^=)';
|
|
5
src/web/app/common/scripts/get-kao.js
Normal file
5
src/web/app/common/scripts/get-kao.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export default () => [
|
||||||
|
'(=^・・^=)',
|
||||||
|
'v(‘ω’)v',
|
||||||
|
'🐡( '-' 🐡 )フグパンチ!!!!'
|
||||||
|
][Math.floor(Math.random() * 3)];
|
18
src/web/app/common/scripts/home-stream.js
Normal file
18
src/web/app/common/scripts/home-stream.js
Normal file
|
@ -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;
|
|
@ -1,42 +1,22 @@
|
||||||
const ReconnectingWebSocket = require('reconnecting-websocket');
|
'use strict';
|
||||||
import * as riot from 'riot';
|
|
||||||
import CONFIG from './config';
|
|
||||||
|
|
||||||
class Connection {
|
import Stream from './stream';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Messaging stream connection
|
||||||
|
*/
|
||||||
|
class Connection extends Stream {
|
||||||
constructor(me, otherparty) {
|
constructor(me, otherparty) {
|
||||||
// BIND -----------------------------------
|
super('messaging', {
|
||||||
this.onOpen = this.onOpen.bind(this);
|
i: me.token,
|
||||||
this.onMessage = this.onMessage.bind(this);
|
otherparty
|
||||||
this.close = this.close.bind(this);
|
});
|
||||||
// ----------------------------------------
|
|
||||||
|
|
||||||
this.event = riot.observable();
|
this.on('_connected_', () => {
|
||||||
this.me = me;
|
this.send({
|
||||||
|
i: me.token
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
close() {
|
|
||||||
this.socket.removeEventListener('open', this.onOpen);
|
|
||||||
this.socket.removeEventListener('message', this.onMessage);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
14
src/web/app/common/scripts/server-stream.js
Normal file
14
src/web/app/common/scripts/server-stream.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import Stream from './stream';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server stream connection
|
||||||
|
*/
|
||||||
|
class Connection extends Stream {
|
||||||
|
constructor() {
|
||||||
|
super('server');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Connection;
|
|
@ -5,10 +5,10 @@ import * as riot from 'riot';
|
||||||
import CONFIG from './config';
|
import CONFIG from './config';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Home stream connection
|
* Misskey stream connection
|
||||||
*/
|
*/
|
||||||
class Connection {
|
class Connection {
|
||||||
constructor(me) {
|
constructor(endpoint, params) {
|
||||||
// BIND -----------------------------------
|
// BIND -----------------------------------
|
||||||
this.onOpen = this.onOpen.bind(this);
|
this.onOpen = this.onOpen.bind(this);
|
||||||
this.onClose = this.onClose.bind(this);
|
this.onClose = this.onClose.bind(this);
|
||||||
|
@ -20,16 +20,19 @@ class Connection {
|
||||||
riot.observable(this);
|
riot.observable(this);
|
||||||
|
|
||||||
this.state = 'initializing';
|
this.state = 'initializing';
|
||||||
this.me = me;
|
|
||||||
this.buffer = [];
|
this.buffer = [];
|
||||||
|
|
||||||
const host = CONFIG.apiUrl.replace('http', 'ws');
|
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('open', this.onOpen);
|
||||||
this.socket.addEventListener('close', this.onClose);
|
this.socket.addEventListener('close', this.onClose);
|
||||||
this.socket.addEventListener('message', this.onMessage);
|
this.socket.addEventListener('message', this.onMessage);
|
||||||
|
|
||||||
this.on('i_updated', me.update);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default (tokens, shouldBreak) => {
|
||||||
case 'bold':
|
case 'bold':
|
||||||
return `<strong>${escape(token.bold)}</strong>`;
|
return `<strong>${escape(token.bold)}</strong>`;
|
||||||
case 'url':
|
case 'url':
|
||||||
return `<mk-url href="${escape(token.content)}" target="_blank"/>`;
|
return `<mk-url href="${escape(token.content)}" target="_blank"></mk-url>`;
|
||||||
case 'link':
|
case 'link':
|
||||||
return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`;
|
return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`;
|
||||||
case 'mention':
|
case 'mention':
|
||||||
|
|
|
@ -2,7 +2,15 @@
|
||||||
<svg if={ data } ref="canvas" viewBox="0 0 53 7" preserveAspectRatio="none">
|
<svg if={ data } ref="canvas" viewBox="0 0 53 7" preserveAspectRatio="none">
|
||||||
<rect each={ data } width="1" height="1"
|
<rect each={ data } width="1" height="1"
|
||||||
riot-x={ x } riot-y={ date.weekday }
|
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>
|
</svg>
|
||||||
<style>
|
<style>
|
||||||
:scope
|
:scope
|
||||||
|
@ -16,7 +24,6 @@
|
||||||
|
|
||||||
> rect
|
> rect
|
||||||
transform-origin center
|
transform-origin center
|
||||||
transform scale(0.8)
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
|
@ -33,9 +40,9 @@
|
||||||
let x = 0;
|
let x = 0;
|
||||||
data.reverse().forEach(d => {
|
data.reverse().forEach(d => {
|
||||||
d.x = x;
|
d.x = x;
|
||||||
let v = d.total / this.peak;
|
d.v = d.total / this.peak;
|
||||||
if (v > 1) v = 1;
|
if (d.v > 1) d.v = 1;
|
||||||
d.color = `hsl(180, ${v * 100}%, ${15 + ((1 - v) * 80)}%)`;
|
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();
|
d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
|
||||||
if (d.date.weekday == 6) x++;
|
if (d.date.weekday == 6) x++;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,36 +1,25 @@
|
||||||
<mk-error>
|
<mk-error>
|
||||||
<!--i: i.fa.fa-times-circle-->
|
|
||||||
<img src="/assets/error.jpg" alt=""/>
|
<img src="/assets/error.jpg" alt=""/>
|
||||||
<h1>%i18n:common.tags.mk-error.title%</h1>
|
<h1>%i18n:common.tags.mk-error.title%</h1>
|
||||||
<p class="text">%i18n:common.tags.mk-error.description%</p>
|
<p class="text">%i18n:common.tags.mk-error.description%</p>
|
||||||
<p class="thanks">%i18n:common.tags.mk-error.thanks%</p>
|
<p class="thanks">%i18n:common.tags.mk-error.thanks%</p>
|
||||||
<style>
|
<style>
|
||||||
:scope
|
:scope
|
||||||
position fixed
|
display block
|
||||||
z-index 100000
|
|
||||||
top 0
|
|
||||||
left 0
|
|
||||||
width 100%
|
width 100%
|
||||||
height 100%
|
padding 32px 18px
|
||||||
text-align center
|
text-align center
|
||||||
background #f8f8f8
|
|
||||||
|
|
||||||
> i
|
|
||||||
display block
|
|
||||||
margin-top 64px
|
|
||||||
font-size 5em
|
|
||||||
color #6998a0
|
|
||||||
|
|
||||||
> img
|
> img
|
||||||
display block
|
display block
|
||||||
height 200px
|
height 200px
|
||||||
margin 64px auto 0 auto
|
margin 0 auto
|
||||||
pointer-events none
|
pointer-events none
|
||||||
user-select none
|
user-select none
|
||||||
|
|
||||||
> h1
|
> h1
|
||||||
display block
|
display block
|
||||||
margin 32px auto 16px auto
|
margin 1.25em auto 0.65em auto
|
||||||
font-size 1.5em
|
font-size 1.5em
|
||||||
color #555
|
color #555
|
||||||
|
|
||||||
|
@ -43,13 +32,26 @@
|
||||||
|
|
||||||
> .thanks
|
> .thanks
|
||||||
display block
|
display block
|
||||||
margin 32px auto 0 auto
|
margin 2em auto 0 auto
|
||||||
padding 32px 0 32px 0
|
padding 2em 0 0 0
|
||||||
max-width 600px
|
max-width 600px
|
||||||
font-size 0.9em
|
font-size 0.9em
|
||||||
font-style oblique
|
font-style oblique
|
||||||
color #aaa
|
color #aaa
|
||||||
border-top solid 1px #eee
|
border-top solid 1px #eee
|
||||||
|
|
||||||
|
@media (max-width 500px)
|
||||||
|
padding 24px 18px
|
||||||
|
font-size 80%
|
||||||
|
|
||||||
|
> img
|
||||||
|
height 150px
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
this.on('mount', () => {
|
||||||
|
document.title = 'Oops!';
|
||||||
|
document.documentElement.style.background = '#f8f8f8';
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</mk-error>
|
</mk-error>
|
||||||
|
|
|
@ -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="content-container">
|
||||||
<div class="balloon">
|
<div class="balloon">
|
||||||
<p class="read" if={ message.is_me && message.is_read }>%i18n:common.tags.mk-messaging-message.is-read%</p>
|
<p class="read" if={ message.is_me && message.is_read }>%i18n:common.tags.mk-messaging-message.is-read%</p>
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
<div class="stream">
|
<div class="stream">
|
||||||
<p class="init" if={ init }><i class="fa fa-spinner fa-spin"></i>%i18n:common.loading%</p>
|
<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="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 }>
|
<virtual each={ message, i in messages }>
|
||||||
<mk-messaging-message message={ message }/>
|
<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>
|
<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
|
max-width 600px
|
||||||
margin 0 auto
|
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
|
> .empty
|
||||||
width 100%
|
width 100%
|
||||||
margin 0
|
margin 0
|
||||||
|
@ -42,6 +57,27 @@
|
||||||
i
|
i
|
||||||
margin-right 4px
|
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
|
> .message
|
||||||
// something
|
// something
|
||||||
|
|
||||||
|
@ -90,6 +126,9 @@
|
||||||
padding 8px 0
|
padding 8px 0
|
||||||
text-align center
|
text-align center
|
||||||
|
|
||||||
|
&:empty
|
||||||
|
display none
|
||||||
|
|
||||||
> p
|
> p
|
||||||
display inline-block
|
display inline-block
|
||||||
margin 0
|
margin 0
|
||||||
|
@ -137,24 +176,21 @@
|
||||||
this.connection = new MessagingStreamConnection(this.I, this.user.id);
|
this.connection = new MessagingStreamConnection(this.I, this.user.id);
|
||||||
|
|
||||||
this.on('mount', () => {
|
this.on('mount', () => {
|
||||||
this.connection.event.on('message', this.onMessage);
|
this.connection.on('message', this.onMessage);
|
||||||
this.connection.event.on('read', this.onRead);
|
this.connection.on('read', this.onRead);
|
||||||
|
|
||||||
document.addEventListener('visibilitychange', this.onVisibilitychange);
|
document.addEventListener('visibilitychange', this.onVisibilitychange);
|
||||||
|
|
||||||
this.api('messaging/messages', {
|
this.fetchMessages().then(() => {
|
||||||
user_id: this.user.id
|
|
||||||
}).then(messages => {
|
|
||||||
this.init = false;
|
this.init = false;
|
||||||
this.messages = messages.reverse();
|
|
||||||
this.update();
|
this.update();
|
||||||
this.scrollToBottom();
|
this.scrollToBottom();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('unmount', () => {
|
this.on('unmount', () => {
|
||||||
this.connection.event.off('message', this.onMessage);
|
this.connection.off('message', this.onMessage);
|
||||||
this.connection.event.off('read', this.onRead);
|
this.connection.off('read', this.onRead);
|
||||||
this.connection.close();
|
this.connection.close();
|
||||||
|
|
||||||
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
document.removeEventListener('visibilitychange', this.onVisibilitychange);
|
||||||
|
@ -174,10 +210,10 @@
|
||||||
|
|
||||||
this.messages.push(message);
|
this.messages.push(message);
|
||||||
if (message.user_id != this.I.id && !document.hidden) {
|
if (message.user_id != this.I.id && !document.hidden) {
|
||||||
this.connection.socket.send(JSON.stringify({
|
this.connection.send({
|
||||||
type: 'read',
|
type: 'read',
|
||||||
id: message.id
|
id: message.id
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
this.update();
|
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 = () => {
|
this.isBottom = () => {
|
||||||
const asobi = 32;
|
const asobi = 32;
|
||||||
const current = this.isNaked
|
const current = this.isNaked
|
||||||
|
@ -239,10 +308,10 @@
|
||||||
if (document.hidden) return;
|
if (document.hidden) return;
|
||||||
this.messages.forEach(message => {
|
this.messages.forEach(message => {
|
||||||
if (message.user_id !== this.I.id && !message.is_read) {
|
if (message.user_id !== this.I.id && !message.is_read) {
|
||||||
this.connection.socket.send(JSON.stringify({
|
this.connection.send({
|
||||||
type: 'read',
|
type: 'read',
|
||||||
id: message.id
|
id: message.id
|
||||||
}));
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -74,4 +74,18 @@ function registerNotifications(stream) {
|
||||||
});
|
});
|
||||||
setTimeout(n.close.bind(n), 6000);
|
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);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
<mk-activity-home-widget>
|
<mk-activity-home-widget>
|
||||||
<p class="title"><i class="fa fa-bar-chart"></i>%i18n:desktop.tags.mk-activity-home-widget.title%</p>
|
<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>
|
<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">
|
<mk-activity-home-widget-calender if={ !initializing && view == 0 } data={ [].concat(data) }/>
|
||||||
<rect each={ data } width="1" height="1"
|
<mk-activity-home-widget-chart if={ !initializing && view == 1 } data={ [].concat(data) }/>
|
||||||
riot-x={ x } riot-y={ date.weekday }
|
|
||||||
fill={ color }></rect>
|
|
||||||
</svg>
|
|
||||||
<style>
|
<style>
|
||||||
:scope
|
:scope
|
||||||
display block
|
display block
|
||||||
|
@ -24,6 +22,23 @@
|
||||||
> i
|
> i
|
||||||
margin-right 4px
|
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
|
> .initializing
|
||||||
margin 0
|
margin 0
|
||||||
padding 16px
|
padding 16px
|
||||||
|
@ -33,6 +48,60 @@
|
||||||
> i
|
> i
|
||||||
margin-right 4px
|
margin-right 4px
|
||||||
|
|
||||||
|
</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 => {
|
||||||
|
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
|
> svg
|
||||||
display block
|
display block
|
||||||
padding 10px
|
padding 10px
|
||||||
|
@ -40,37 +109,121 @@
|
||||||
|
|
||||||
> rect
|
> rect
|
||||||
transform-origin center
|
transform-origin center
|
||||||
transform scale(0.8)
|
|
||||||
|
&.day
|
||||||
|
&:hover
|
||||||
|
fill rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
this.mixin('i');
|
this.data = this.opts.data;
|
||||||
this.mixin('api');
|
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.initializing = true;
|
let x = 0;
|
||||||
|
this.data.reverse().forEach(d => {
|
||||||
this.on('mount', () => {
|
d.x = x;
|
||||||
this.api('aggregation/users/activity', {
|
d.v = d.total / (peak / 2);
|
||||||
user_id: this.I.id,
|
if (d.v > 1) d.v = 1;
|
||||||
limit: 20 * 7
|
d.color = `hsl(170, ${d.v * 100}%, ${15 + ((1 - d.v) * 80)}%)`;
|
||||||
}).then(data => {
|
d.date.weekday = (new Date(d.date.year, d.date.month - 1, d.date.day)).getDay();
|
||||||
data.forEach(d => d.total = d.posts + d.replies + d.reposts);
|
if (d.date.weekday == 6) x++;
|
||||||
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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</mk-activity-home-widget>
|
</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>
|
||||||
|
|
||||||
|
|
510
src/web/app/desktop/tags/home-widgets/server.tag
Normal file
510
src/web/app/desktop/tags/home-widgets/server.tag
Normal file
|
@ -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>
|
|
@ -77,6 +77,7 @@
|
||||||
'notifications',
|
'notifications',
|
||||||
'user-recommendation',
|
'user-recommendation',
|
||||||
'recommended-polls',
|
'recommended-polls',
|
||||||
|
'server',
|
||||||
'donation',
|
'donation',
|
||||||
'nav',
|
'nav',
|
||||||
'tips'
|
'tips'
|
||||||
|
|
|
@ -45,6 +45,7 @@ require('./home-widgets/version.tag');
|
||||||
require('./home-widgets/recommended-polls.tag');
|
require('./home-widgets/recommended-polls.tag');
|
||||||
require('./home-widgets/trends.tag');
|
require('./home-widgets/trends.tag');
|
||||||
require('./home-widgets/activity.tag');
|
require('./home-widgets/activity.tag');
|
||||||
|
require('./home-widgets/server.tag');
|
||||||
require('./timeline.tag');
|
require('./timeline.tag');
|
||||||
require('./messaging/window.tag');
|
require('./messaging/window.tag');
|
||||||
require('./messaging/room-window.tag');
|
require('./messaging/room-window.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>
|
<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>
|
</virtual>
|
||||||
</div>
|
</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%' }
|
<i class="fa fa-spinner fa-pulse fa-fw" if={ fetchingMoreNotifications }></i>{ fetchingMoreNotifications ? '%i18n:common.loading%' : '%i18n:desktop.tags.mk-notifications.more%' }
|
||||||
</button>
|
</button>
|
||||||
<p class="empty" if={ notifications.length == 0 && !loading }>ありません!</p>
|
<p class="empty" if={ notifications.length == 0 && !loading }>ありません!</p>
|
||||||
|
@ -184,6 +184,9 @@
|
||||||
&:active
|
&:active
|
||||||
background rgba(0, 0, 0, 0.05)
|
background rgba(0, 0, 0, 0.05)
|
||||||
|
|
||||||
|
&.fetching
|
||||||
|
cursor wait
|
||||||
|
|
||||||
> i
|
> i
|
||||||
margin-right 4px
|
margin-right 4px
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
<mk-uploader ref="uploader"/>
|
<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="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 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>
|
<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>
|
<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 }>
|
<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='upload']
|
||||||
[ref='drive']
|
[ref='drive']
|
||||||
.cat
|
.kao
|
||||||
.poll
|
.poll
|
||||||
display inline-block
|
display inline-block
|
||||||
cursor pointer
|
cursor pointer
|
||||||
|
@ -306,7 +306,7 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import getCat from '../../common/scripts/get-cat';
|
import getKao from '../../common/scripts/get-kao';
|
||||||
import notify from '../scripts/notify';
|
import notify from '../scripts/notify';
|
||||||
import Autocomplete from '../scripts/autocomplete';
|
import Autocomplete from '../scripts/autocomplete';
|
||||||
|
|
||||||
|
@ -500,8 +500,8 @@
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cat = () => {
|
this.kao = () => {
|
||||||
this.refs.text.value += getCat();
|
this.refs.text.value += getKao();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.on('update', () => {
|
this.on('update', () => {
|
||||||
|
|
|
@ -8,7 +8,8 @@ import * as riot from 'riot';
|
||||||
import api from './common/scripts/api';
|
import api from './common/scripts/api';
|
||||||
import signout from './common/scripts/signout';
|
import signout from './common/scripts/signout';
|
||||||
import checkForUpdate from './common/scripts/check-for-update';
|
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 mixin from './common/mixins';
|
||||||
import generateDefaultUserdata from './common/scripts/generate-default-userdata';
|
import generateDefaultUserdata from './common/scripts/generate-default-userdata';
|
||||||
import CONFIG from './common/scripts/config';
|
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;
|
const stream = me ? new Connection(me) : null;
|
||||||
|
|
||||||
// ミックスイン初期化
|
// ミックスイン初期化
|
||||||
|
@ -147,9 +148,10 @@ function fetchme(token, cb) {
|
||||||
me.data ? done() : init();
|
me.data ? done() : init();
|
||||||
});
|
});
|
||||||
}, () => { // When failure
|
}, () => { // When failure
|
||||||
// Display error screen
|
// Render the error screen
|
||||||
riot.mount(document.body.appendChild(
|
document.body.innerHTML = '<mk-error />';
|
||||||
document.createElement('mk-error')));
|
riot.mount('*');
|
||||||
|
Progress.done();
|
||||||
});
|
});
|
||||||
|
|
||||||
function done() {
|
function done() {
|
||||||
|
@ -173,6 +175,7 @@ function panic(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
// Display blue screen
|
// Display blue screen
|
||||||
|
document.documentElement.style.background = '#1269e2';
|
||||||
document.body.innerHTML =
|
document.body.innerHTML =
|
||||||
'<div id="error">'
|
'<div id="error">'
|
||||||
+ '<h1>:( 致命的な問題が発生しました。</h1>'
|
+ '<h1>:( 致命的な問題が発生しました。</h1>'
|
||||||
|
|
|
@ -39,7 +39,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="empty" if={ files.length == 0 && folders.length == 0 && !fetching }>
|
<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>
|
<p if={ folder != null }>%i18n:mobile.tags.mk-drive.folder-is-empty%</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
|
|
||||||
document.title = title;
|
document.title = title;
|
||||||
// TODO: escape html characters in file.name
|
// 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', {
|
riot.mount('mk-file-type-icon', {
|
||||||
type: file.type
|
type: file.type
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
<mk-uploader ref="uploader"/>
|
<mk-uploader ref="uploader"/>
|
||||||
<button ref="upload" onclick={ selectFile }><i class="fa fa-upload"></i></button>
|
<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 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>
|
<button class="poll" onclick={ addPoll }><i class="fa fa-pie-chart"></i></button>
|
||||||
<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
|
<input ref="file" type="file" accept="image/*" multiple="multiple" onchange={ changeFile }/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -165,7 +165,7 @@
|
||||||
|
|
||||||
> [ref='upload']
|
> [ref='upload']
|
||||||
> [ref='drive']
|
> [ref='drive']
|
||||||
.cat
|
.kao
|
||||||
.poll
|
.poll
|
||||||
display inline-block
|
display inline-block
|
||||||
padding 0
|
padding 0
|
||||||
|
@ -182,7 +182,7 @@
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<script>
|
<script>
|
||||||
import getCat from '../../common/scripts/get-cat';
|
import getKao from '../../common/scripts/get-kao';
|
||||||
|
|
||||||
this.mixin('api');
|
this.mixin('api');
|
||||||
|
|
||||||
|
@ -285,8 +285,8 @@
|
||||||
this.unmount();
|
this.unmount();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.cat = () => {
|
this.kao = () => {
|
||||||
this.refs.text.value += getCat();
|
this.refs.text.value += getKao();
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</mk-post-form>
|
</mk-post-form>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<div class="backdrop"></div>
|
<div class="backdrop"></div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<button class="nav" onclick={ parent.toggleDrawer }><i class="fa fa-bars"></i></button>
|
<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>
|
<h1 ref="title">Misskey</h1>
|
||||||
<button if={ func } onclick={ func }><i class="fa fa-{ funcIcon }"></i></button>
|
<button if={ func } onclick={ func }><i class="fa fa-{ funcIcon }"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -74,6 +75,14 @@
|
||||||
> i
|
> i
|
||||||
transition all 0.2s ease
|
transition all 0.2s ease
|
||||||
|
|
||||||
|
> i
|
||||||
|
position absolute
|
||||||
|
top 8px
|
||||||
|
left 8px
|
||||||
|
pointer-events none
|
||||||
|
font-size 10px
|
||||||
|
color $theme-color
|
||||||
|
|
||||||
> button:last-child
|
> button:last-child
|
||||||
display block
|
display block
|
||||||
position absolute
|
position absolute
|
||||||
|
@ -90,14 +99,46 @@
|
||||||
<script>
|
<script>
|
||||||
import ui from '../scripts/ui-event';
|
import ui from '../scripts/ui-event';
|
||||||
|
|
||||||
|
this.mixin('api');
|
||||||
|
this.mixin('stream');
|
||||||
|
|
||||||
this.func = null;
|
this.func = null;
|
||||||
this.funcIcon = 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.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('title', this.setTitle);
|
||||||
ui.off('func', this.setFunc);
|
ui.off('func', this.setFunc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.onReadAllMessagingMessages = () => {
|
||||||
|
this.update({
|
||||||
|
hasUnreadMessagingMessages: false
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
this.onUnreadMessagingMessage = () => {
|
||||||
|
this.update({
|
||||||
|
hasUnreadMessagingMessages: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
this.setTitle = title => {
|
this.setTitle = title => {
|
||||||
this.refs.title.innerHTML = title;
|
this.refs.title.innerHTML = title;
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,18 +7,18 @@
|
||||||
</a>
|
</a>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<ul>
|
<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><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 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><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 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/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>
|
||||||
<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>
|
||||||
<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>
|
||||||
<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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a href={ CONFIG.aboutUrl }><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
|
<a href={ CONFIG.aboutUrl }><p class="about">%i18n:mobile.tags.mk-ui-nav.about%</p></a>
|
||||||
|
@ -94,10 +94,16 @@
|
||||||
color #777
|
color #777
|
||||||
text-decoration none
|
text-decoration none
|
||||||
|
|
||||||
> .icon
|
> i:first-child
|
||||||
margin-right 0.5em
|
margin-right 0.5em
|
||||||
|
|
||||||
> .angle
|
> .i
|
||||||
|
margin-left 6px
|
||||||
|
vertical-align super
|
||||||
|
font-size 10px
|
||||||
|
color $theme-color
|
||||||
|
|
||||||
|
> i:last-child
|
||||||
position absolute
|
position absolute
|
||||||
top 0
|
top 0
|
||||||
right 0
|
right 0
|
||||||
|
@ -120,6 +126,39 @@
|
||||||
<script>
|
<script>
|
||||||
this.mixin('i');
|
this.mixin('i');
|
||||||
this.mixin('page');
|
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 = () => {
|
this.search = () => {
|
||||||
const query = window.prompt('%i18n:mobile.tags.mk-ui-nav.search%');
|
const query = window.prompt('%i18n:mobile.tags.mk-ui-nav.search%');
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div class="top">
|
<div class="top">
|
||||||
<a class="avatar">
|
<a class="avatar">
|
||||||
<img src={ user.avatar_url + '?thumbnail&size=160' } alt="avatar"/>
|
<img src={ user.avatar_url + '?thumbnail&size=200' } alt="avatar"/>
|
||||||
</a>
|
</a>
|
||||||
<mk-follow-button if={ SIGNIN && I.id != user.id } user={ user }/>
|
<mk-follow-button if={ SIGNIN && I.id != user.id } user={ user }/>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue