diff --git a/.gitignore b/.gitignore index 6573bcb1e..42b1bde94 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 2192eff51..9d2d38149 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 74ff52979..a01392809 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 dda0da585..d55e690e9 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 aaa535980..fb703b554 100644 --- a/package.json +++ b/package.json @@ -1,150 +1,153 @@ { - "name": "misskey", - "author": "syuilo ", - "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 ", + "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 000000000..3257ec8b0 --- /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(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 b3a5c57f6..7b270924e 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 05f9cda4c..8af55d850 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 98f812abd..a3f1d5032 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 a3f2fb796..73a5db47e 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 81aee2cab..18afa57e4 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 71bf7a34c..3f505cfaf 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 000000000..6de533749 --- /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 e1d79481d..c71132100 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 0b568b5d4..da83dde10 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 000000000..cfb710f5e --- /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 f7da0574b..b1ca80deb 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 9fdb39a16..81c039f0a 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 e14338714..af0268dbd 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 cad42c88c..000000000 --- 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 000000000..0b77ee285 --- /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 000000000..24f13cd29 --- /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 50d41c2be..261525d5f 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 { +import Stream from './stream'; + +/** + * Messaging stream connection + */ +class Connection extends Stream { constructor(me, otherparty) { - // BIND ----------------------------------- - this.onOpen = this.onOpen.bind(this); - this.onMessage = this.onMessage.bind(this); - this.close = this.close.bind(this); - // ---------------------------------------- + super('messaging', { + i: me.token, + otherparty + }); - 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); - } - - 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); + 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 000000000..a1c466b35 --- /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 ac3dd6715..981118b5d 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 20ed1877d..0a9b8022d 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 `${escape(token.bold)}`; case 'url': - return ``; + return ``; case 'link': return `${escape(token.title)}`; case 'mention': diff --git a/src/web/app/common/tags/activity-table.tag b/src/web/app/common/tags/activity-table.tag index 49c5fe26c..2ecfc3b11 100644 --- a/src/web/app/common/tags/activity-table.tag +++ b/src/web/app/common/tags/activity-table.tag @@ -2,7 +2,15 @@ + rx="1" ry="1" + fill={ color } + style="transform: scale({ v });"/> + diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag index ec8138da0..d6db9070e 100644 --- a/src/web/app/common/tags/messaging/message.tag +++ b/src/web/app/common/tags/messaging/message.tag @@ -1,4 +1,7 @@ - + + + +

%i18n:common.tags.mk-messaging-message.is-read%

diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag index bd49a4d78..b1082e26b 100644 --- a/src/web/app/common/tags/messaging/room.tag +++ b/src/web/app/common/tags/messaging/room.tag @@ -2,6 +2,10 @@

%i18n:common.loading%

%i18n:common.tags.mk-messaging-room.empty%

+

0 && !moreMessagesIsInStock }>%i18n:common.tags.mk-messaging-room.no-history%

+

{ messages[i + 1]._datetext }

@@ -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 f9f0896dd..2e8114794 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 ba201e1c2..29ce8760f 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 @@

%i18n:desktop.tags.mk-activity-home-widget.title%

+

%i18n:common.loading%

- - - + + + +
+ + + + + { date.year }/{ date.month }/{ date.day }
Post: { posts }, Reply: { replies }, Repost: { reposts }
+
+ + +
+ - +
+ + + + Black ... Total
Blue ... Posts
Red ... Replies
Green ... Reposts
+ + + + +
+ + +
+ 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 000000000..bc8f313d5 --- /dev/null +++ b/src/web/app/desktop/tags/home-widgets/server.tag @@ -0,0 +1,510 @@ + +

%i18n:desktop.tags.mk-server-home-widget.title%

+ +

%i18n:common.loading%

+ + + + + + + + +
+ + + + + + + + + + + + + + + + + CPU { cpuP }% + + + + + + + + + + + + + + + + MEM { memP }% + + + + + + + +
+

CPU

+

{ cores } Cores

+

{ model }

+
+ + +
+ + + +
+

Memory

+

Total: { bytesToSize(total, 1) }

+

Used: { bytesToSize(used, 1) }

+

Free: { bytesToSize(free, 1) }

+
+ + +
+ + + +
+

Storage

+

Total: { bytesToSize(total, 1) }

+

Available: { bytesToSize(available, 1) }

+

Used: { bytesToSize(used, 1) }

+
+ + +
+ + +

Uptimes

+

Process: { process ? process.toFixed(0) : '---' }s

+

OS: { os ? os.toFixed(0) : '---' }s

+ + +
+ + +

Maintainer: { meta.maintainer }

+

Machine: { meta.machine }

+

Node: { meta.node }

+ + +
+ + + + + + { (p * 100).toFixed(0) }% + + + + diff --git a/src/web/app/desktop/tags/home.tag b/src/web/app/desktop/tags/home.tag index 0e4a2ced1..37b2d3cf7 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 1e0ebd44c..177ba4129 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 ce24ac437..21e4fe7fa 100644 --- a/src/web/app/desktop/tags/notifications.tag +++ b/src/web/app/desktop/tags/notifications.tag @@ -63,7 +63,7 @@

{ notification._datetext }{ notifications[i + 1]._datetext }

-

ありません!

@@ -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 024cd095e..6a363d67c 100644 --- a/src/web/app/desktop/tags/post-form.tag +++ b/src/web/app/desktop/tags/post-form.tag @@ -16,7 +16,7 @@ - +

{ '%i18n:desktop.tags.mk-post-form.text-remain%'.replace('{}', 1000 - refs.text.value.length) }

+

Misskey

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