From 7172ccd72c2ef40d68a252d7ae5cf45ddc575116 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 27 Aug 2017 16:53:15 +0900 Subject: Use createIndex() instead of index() to avoid deprecation warnings --- src/api/models/access-token.ts | 4 ++-- src/api/models/app.ts | 6 +++--- src/api/models/drive-file.ts | 2 +- src/api/models/user.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) (limited to 'src/api') diff --git a/src/api/models/access-token.ts b/src/api/models/access-token.ts index 2a8a512ddc..9985be5013 100644 --- a/src/api/models/access-token.ts +++ b/src/api/models/access-token.ts @@ -2,7 +2,7 @@ import db from '../../db/mongodb'; const collection = db.get('access_tokens'); -(collection as any).index('token'); // fuck type definition -(collection as any).index('hash'); // fuck type definition +(collection as any).createIndex('token'); // fuck type definition +(collection as any).createIndex('hash'); // fuck type definition export default collection as any; // fuck type definition diff --git a/src/api/models/app.ts b/src/api/models/app.ts index bf5dc80c2c..68f2f448b0 100644 --- a/src/api/models/app.ts +++ b/src/api/models/app.ts @@ -2,9 +2,9 @@ import db from '../../db/mongodb'; const collection = db.get('apps'); -(collection as any).index('name_id'); // fuck type definition -(collection as any).index('name_id_lower'); // fuck type definition -(collection as any).index('secret'); // fuck type definition +(collection as any).createIndex('name_id'); // fuck type definition +(collection as any).createIndex('name_id_lower'); // fuck type definition +(collection as any).createIndex('secret'); // fuck type definition export default collection as any; // fuck type definition diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts index 4c7204b1f4..8d158cf563 100644 --- a/src/api/models/drive-file.ts +++ b/src/api/models/drive-file.ts @@ -2,7 +2,7 @@ import db from '../../db/mongodb'; const collection = db.get('drive_files'); -(collection as any).index('hash'); // fuck type definition +(collection as any).createIndex('hash'); // fuck type definition export default collection as any; // fuck type definition diff --git a/src/api/models/user.ts b/src/api/models/user.ts index cd16459891..9f8cf0161d 100644 --- a/src/api/models/user.ts +++ b/src/api/models/user.ts @@ -2,8 +2,8 @@ import db from '../../db/mongodb'; const collection = db.get('users'); -(collection as any).index('username'); // fuck type definition -(collection as any).index('token'); // fuck type definition +(collection as any).createIndex('username'); // fuck type definition +(collection as any).createIndex('token'); // fuck type definition export default collection as any; // fuck type definition -- cgit v1.2.3-freya From 3b77bc8299eee89a7945438863953a5cdd08e934 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 28 Aug 2017 00:03:57 +0900 Subject: Implement #734 --- CHANGELOG.md | 4 ++ locales/en.yml | 4 ++ locales/ja.yml | 4 ++ src/api/serializers/post.ts | 95 ++++++++++++++++++++++---------- src/web/app/desktop/tags/pages/post.tag | 34 ++++++++++-- src/web/app/desktop/tags/post-detail.tag | 76 ++++++++++--------------- 6 files changed, 133 insertions(+), 84 deletions(-) (limited to 'src/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index 72a584ddb0..95d21ac05f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog ========= 主に notable な changes を書いていきます +unreleased +---------- +* 投稿ページに次の投稿/前の投稿リンクを作成 (#734) + 2380 ---- アプリケーションが作れない問題を修正 diff --git a/locales/en.yml b/locales/en.yml index 55a588f99f..9bf6446641 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -231,6 +231,10 @@ desktop: attaches: "{} media attached" uploading-media: "Uploading {} media" + mk-post-page: + prev: "Previous post" + next: "Next post" + mk-timeline-post: reposted-by: "Reposted by {}" reply: "Reply" diff --git a/locales/ja.yml b/locales/ja.yml index e5b2beaed1..d2b282bff6 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -231,6 +231,10 @@ desktop: attaches: "添付: {}メディア" uploading-media: "{}個のメディアをアップロード中" + mk-post-page: + prev: "前の投稿" + next: "次の投稿" + mk-timeline-post: reposted-by: "{}がRepost" reply: "返信" diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index 3c96884dd1..13773bda9e 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -73,44 +73,79 @@ const self = ( )); } - if (_post.reply_to_id && opts.detail) { - // Populate reply to post - _post.reply_to = await self(_post.reply_to_id, me, { - detail: false + // When requested a detailed post data + if (opts.detail) { + // Get previous post info + const prev = await Post.findOne({ + user_id: _post.user_id, + _id: { + $lt: id + } + }, { + fields: { + _id: true + }, + sort: { + _id: -1 + } }); - } - - if (_post.repost_id && opts.detail) { - // Populate repost - _post.repost = await self(_post.repost_id, me, { - detail: _post.text == null + _post.prev = prev ? prev._id : null; + + // Get next post info + const next = await Post.findOne({ + user_id: _post.user_id, + _id: { + $gt: id + } + }, { + fields: { + _id: true + }, + sort: { + _id: 1 + } }); - } + _post.next = next ? next._id : null; - // Poll - if (me && _post.poll && opts.detail) { - const vote = await Vote - .findOne({ - user_id: me._id, - post_id: id + if (_post.reply_to_id) { + // Populate reply to post + _post.reply_to = await self(_post.reply_to_id, me, { + detail: false }); - - if (vote != null) { - _post.poll.choices.filter(c => c.id == vote.choice)[0].is_voted = true; } - } - // Fetch my reaction - if (me && opts.detail) { - const reaction = await Reaction - .findOne({ - user_id: me._id, - post_id: id, - deleted_at: { $exists: false } + if (_post.repost_id) { + // Populate repost + _post.repost = await self(_post.repost_id, me, { + detail: _post.text == null }); + } + + // Poll + if (me && _post.poll) { + const vote = await Vote + .findOne({ + user_id: me._id, + post_id: id + }); + + if (vote != null) { + _post.poll.choices.filter(c => c.id == vote.choice)[0].is_voted = true; + } + } - if (reaction) { - _post.my_reaction = reaction.reaction; + // Fetch my reaction + if (me) { + const reaction = await Reaction + .findOne({ + user_id: me._id, + post_id: id, + deleted_at: { $exists: false } + }); + + if (reaction) { + _post.my_reaction = reaction.reaction; + } } } diff --git a/src/web/app/desktop/tags/pages/post.tag b/src/web/app/desktop/tags/pages/post.tag index c91e98bbd4..f270b43ac2 100644 --- a/src/web/app/desktop/tags/pages/post.tag +++ b/src/web/app/desktop/tags/pages/post.tag @@ -1,7 +1,9 @@ -
+
+ %i18n:desktop.tags.mk-post-page.next% + %i18n:desktop.tags.mk-post-page.prev%
- - diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.js index 5dc4ef4546..1ee8dab42d 100644 --- a/src/web/app/common/tags/index.js +++ b/src/web/app/common/tags/index.js @@ -14,7 +14,6 @@ require('./forkit.tag'); require('./introduction.tag'); require('./copyright.tag'); require('./signin-history.tag'); -require('./api-info.tag'); require('./twitter-setting.tag'); require('./authorized-apps.tag'); require('./poll.tag'); diff --git a/src/web/app/desktop/scripts/password-dialog.js b/src/web/app/desktop/scripts/password-dialog.js new file mode 100644 index 0000000000..2bdc93e421 --- /dev/null +++ b/src/web/app/desktop/scripts/password-dialog.js @@ -0,0 +1,11 @@ +import * as riot from 'riot'; + +export default (title, onOk, onCancel) => { + const dialog = document.body.appendChild(document.createElement('mk-input-dialog')); + return riot.mount(dialog, { + title: title, + type: 'password', + onOk: onOk, + onCancel: onCancel + }); +}; diff --git a/src/web/app/desktop/tags/input-dialog.tag b/src/web/app/desktop/tags/input-dialog.tag index f343c4625a..78fd62ee8b 100644 --- a/src/web/app/desktop/tags/input-dialog.tag +++ b/src/web/app/desktop/tags/input-dialog.tag @@ -5,7 +5,7 @@
- +
@@ -126,6 +126,7 @@ this.placeholder = this.opts.placeholder; this.default = this.opts.default; this.allowEmpty = this.opts.allowEmpty != null ? this.opts.allowEmpty : true; + this.type = this.opts.type ? this.opts.type : 'text'; this.on('mount', () => { this.text = this.refs.window.refs.text; @@ -156,6 +157,10 @@ this.refs.window.close(); }; + this.onInput = () => { + this.update(); + }; + this.onKeydown = e => { if (e.which == 13) { // Enter e.preventDefault(); diff --git a/src/web/app/desktop/tags/settings.tag b/src/web/app/desktop/tags/settings.tag index a89cfda0e4..7fc6acb4a8 100644 --- a/src/web/app/desktop/tags/settings.tag +++ b/src/web/app/desktop/tags/settings.tag @@ -211,3 +211,33 @@ }; + + +

Token:{ I.token }

+

APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。

+

アカウントを乗っ取られてしまう可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。

+

万が一このトークンが漏れたりその可能性がある場合はトークンを再生成できます。(副作用として、ログインしているすべてのデバイスでログアウトが発生します)

+ + +
diff --git a/src/web/app/mobile/tags/page/settings/api.tag b/src/web/app/mobile/tags/page/settings/api.tag index 46419eb3db..25413e2d80 100644 --- a/src/web/app/mobile/tags/page/settings/api.tag +++ b/src/web/app/mobile/tags/page/settings/api.tag @@ -15,3 +15,22 @@ }); + + +

Token:{ I.token }

+

APIを利用するには、上記のトークンを「i」というキーでパラメータに付加してリクエストします。

+

アカウントを乗っ取られてしまう可能性があるため、このトークンは第三者に教えないでください(アプリなどにも入力しないでください)。

+

万が一このトークンが漏れたりその可能性がある場合はデスクトップ版Misskeyから再生成できます。

+ + +
-- cgit v1.2.3-freya From cdcd8e5c40197c807c69aea19899bc556c8b783b Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Mon, 28 Aug 2017 23:49:14 +0900 Subject: Update regenerate_token.ts --- src/api/endpoints/i/regenerate_token.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/endpoints/i/regenerate_token.ts b/src/api/endpoints/i/regenerate_token.ts index ccebbc8101..f96d10ebfc 100644 --- a/src/api/endpoints/i/regenerate_token.ts +++ b/src/api/endpoints/i/regenerate_token.ts @@ -37,6 +37,6 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { res(); - // Publish i updated event + // Publish event event(user._id, 'my_token_regenerated'); }); -- cgit v1.2.3-freya From 8f0e6a70cf22f1248b99bd3b300feed8b1b1efc8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 29 Aug 2017 00:20:47 +0900 Subject: #364 --- CHANGELOG.md | 1 + locales/en.yml | 9 ++++++++ locales/ja.yml | 9 ++++++++ src/api/endpoints.ts | 4 ++++ src/api/endpoints/i/change_password.ts | 42 ++++++++++++++++++++++++++++++++++ src/web/app/desktop/tags/settings.tag | 38 ++++++++++++++++++++++++++++-- 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 src/api/endpoints/i/change_password.ts (limited to 'src/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d18b1b7f6..4e49f9ca49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ChangeLog unlereased ---------- * New: トークンを再生成できるように (#497) +* New: パスワードを変更する機能 (#364) 2461 (2017/08/28) ----------------- diff --git a/locales/en.yml b/locales/en.yml index 950180278d..a24b8725ae 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -208,6 +208,12 @@ desktop: settings: "Settings" signout: "Sign out" + mk-password-setting: + reset: "Change your password" + enter-current-password: "Enter the current password" + enter-new-password: "Enter the new password" + changed: "Password updated successfully" + mk-post-form: post-placeholder: "What's happening?" reply-placeholder: "Reply to this post..." @@ -239,6 +245,9 @@ desktop: prev: "Previous post" next: "Next post" + mk-settings: + password: "Password" + mk-timeline-post: reposted-by: "Reposted by {}" reply: "Reply" diff --git a/locales/ja.yml b/locales/ja.yml index 2655eb4846..88e0b76d82 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -208,6 +208,12 @@ desktop: settings: "設定" signout: "サインアウト" + mk-password-setting: + reset: "パスワードを変更する" + enter-current-password: "現在のパスワードを入力してください" + enter-new-password: "新しいパスワードを入力してください" + changed: "パスワードを変更しました" + mk-post-form: post-placeholder: "いまどうしてる?" reply-placeholder: "この投稿への返信..." @@ -239,6 +245,9 @@ desktop: prev: "前の投稿" next: "次の投稿" + mk-settings: + password: "パスワード" + mk-timeline-post: reposted-by: "{}がRepost" reply: "返信" diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index a658c9a42e..c6661533e8 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -159,6 +159,10 @@ const endpoints: Endpoint[] = [ }, kind: 'account-write' }, + { + name: 'i/change_password', + withCredential: true + }, { name: 'i/regenerate_token', withCredential: true diff --git a/src/api/endpoints/i/change_password.ts b/src/api/endpoints/i/change_password.ts new file mode 100644 index 0000000000..faceded29d --- /dev/null +++ b/src/api/endpoints/i/change_password.ts @@ -0,0 +1,42 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import User from '../../models/user'; + +/** + * Change password + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = async (params, user) => new Promise(async (res, rej) => { + // Get 'current_password' parameter + const [currentPassword, currentPasswordErr] = $(params.current_password).string().$; + if (currentPasswordErr) return rej('invalid current_password param'); + + // Get 'new_password' parameter + const [newPassword, newPasswordErr] = $(params.new_password).string().$; + if (newPasswordErr) return rej('invalid new_password param'); + + // Compare password + const same = bcrypt.compareSync(currentPassword, user.password); + + if (!same) { + return rej('incorrect password'); + } + + // Generate hash of password + const salt = bcrypt.genSaltSync(8); + const hash = bcrypt.hashSync(newPassword, salt); + + await User.update(user._id, { + $set: { + password: hash + } + }); + + res(); +}); diff --git a/src/web/app/desktop/tags/settings.tag b/src/web/app/desktop/tags/settings.tag index 7fc6acb4a8..80a42d6652 100644 --- a/src/web/app/desktop/tags/settings.tag +++ b/src/web/app/desktop/tags/settings.tag @@ -7,7 +7,7 @@

アプリ

Twitter

ログイン履歴

-

パスワード

+

%i18n:desktop.tags.mk-settings.password%

API

@@ -58,6 +58,11 @@ +
+

%i18n:desktop.tags.mk-settings.password%

+ +
+

API

@@ -236,8 +241,37 @@ passwordDialog('%i18n:desktop.tags.mk-api-info.regenerate-token%', password => { this.api('i/regenerate_token', { password: password - }) + }); }); }; + + + + + + -- cgit v1.2.3-freya From 57e6571afdd00f9ce489dd66e06455b9b753ff0b Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Tue, 29 Aug 2017 02:56:07 +0900 Subject: MAKE EMOJIS GREAT AGAIN --- src/api/service/github.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/api') diff --git a/src/api/service/github.ts b/src/api/service/github.ts index a631808ba5..1c78267c0f 100644 --- a/src/api/service/github.ts +++ b/src/api/service/github.ts @@ -111,12 +111,12 @@ module.exports = async (app: express.Application) => { handler.on('watch', event => { const sender = event.sender; - post(`Starred by **${sender.login}**`); + post(`⭐️ Starred by **${sender.login}** ⭐️`); }); handler.on('fork', event => { const repo = event.forkee; - post(`Forked:\n${repo.html_url}`); + post(`🍴 Forked:\n${repo.html_url} 🍴`); }); handler.on('pull_request', event => { -- cgit v1.2.3-freya From e7415dd42bf656a24d70c49776ff7c84a1838f9e Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 30 Aug 2017 17:31:39 +0900 Subject: Implement #746 --- CHANGELOG.md | 4 + locales/en.yml | 4 + locales/ja.yml | 4 + src/api/endpoints.ts | 4 + src/api/endpoints/i/pin.ts | 44 ++++++++++ src/api/serializers/user.ts | 43 ++++++---- src/web/app/common/tags/index.js | 1 + src/web/app/common/tags/post-menu.tag | 134 +++++++++++++++++++++++++++++++ src/web/app/desktop/tags/post-detail.tag | 23 ++++-- src/web/app/desktop/tags/timeline.tag | 21 +++-- src/web/app/mobile/tags/page/post.tag | 14 +++- src/web/app/mobile/tags/post-detail.tag | 54 +++++++------ src/web/app/mobile/tags/timeline.tag | 23 ++++-- src/web/app/mobile/tags/user.tag | 4 + 14 files changed, 316 insertions(+), 61 deletions(-) create mode 100644 src/api/endpoints/i/pin.ts create mode 100644 src/web/app/common/tags/post-menu.tag (limited to 'src/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index c64a8ea04d..2669bfee74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +unreleased +---------- +* New: 投稿のピン留め (#746) + 2508 (2017/08/30) ----------------- * New: モバイル版のユーザーページのアクティビティチャートを変更 diff --git a/locales/en.yml b/locales/en.yml index 29a6a764d3..15d278c370 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -77,6 +77,10 @@ common: show-result: "Show result" voted: "Voted" + mk-post-menu: + pin: "Pin" + pinned: "Pinned" + mk-reaction-picker: choose-reaction: "Pick your reaction" diff --git a/locales/ja.yml b/locales/ja.yml index 21a07640d0..7ed4262f1c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -77,6 +77,10 @@ common: show-result: "結果を見る" voted: "投票済み" + mk-post-menu: + pin: "ピン留め" + pinned: "ピン留めしました" + mk-reaction-picker: choose-reaction: "リアクションを選択" diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index c6661533e8..e5be68c096 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -167,6 +167,10 @@ const endpoints: Endpoint[] = [ name: 'i/regenerate_token', withCredential: true }, + { + name: 'i/pin', + kind: 'account-write' + }, { name: 'i/appdata/get', withCredential: true diff --git a/src/api/endpoints/i/pin.ts b/src/api/endpoints/i/pin.ts new file mode 100644 index 0000000000..a94950d22b --- /dev/null +++ b/src/api/endpoints/i/pin.ts @@ -0,0 +1,44 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import User from '../../models/user'; +import Post from '../../models/post'; +import serialize from '../../serializers/user'; + +/** + * Pin post + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = async (params, user) => new Promise(async (res, rej) => { + // Get 'post_id' parameter + const [postId, postIdErr] = $(params.post_id).id().$; + if (postIdErr) return rej('invalid post_id param'); + + // Fetch pinee + const post = await Post.findOne({ + _id: postId, + user_id: user._id + }); + + if (post === null) { + return rej('post not found'); + } + + await User.update(user._id, { + $set: { + pinned_post_id: post._id + } + }); + + // Serialize + const iObj = await serialize(user, user, { + detail: true + }); + + // Send response + res(iObj); +}); diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index bdbc749589..c9189d9034 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -4,6 +4,7 @@ import * as mongo from 'mongodb'; import deepcopy = require('deepcopy'); import User from '../models/user'; +import serializePost from './post'; import Following from '../models/following'; import getFriends from '../common/get-friends'; import config from '../../conf'; @@ -116,24 +117,32 @@ export default ( _user.is_followed = follow2 !== null; } - if (me && !me.equals(_user.id) && opts.detail) { - const myFollowingIds = await getFriends(me); - - // Get following you know count - const followingYouKnowCount = await Following.count({ - followee_id: { $in: myFollowingIds }, - follower_id: _user.id, - deleted_at: { $exists: false } - }); - _user.following_you_know_count = followingYouKnowCount; + if (opts.detail) { + if (_user.pinned_post_id) { + _user.pinned_post = await serializePost(_user.pinned_post_id, me, { + detail: true + }); + } - // Get followers you know count - const followersYouKnowCount = await Following.count({ - followee_id: _user.id, - follower_id: { $in: myFollowingIds }, - deleted_at: { $exists: false } - }); - _user.followers_you_know_count = followersYouKnowCount; + if (me && !me.equals(_user.id)) { + const myFollowingIds = await getFriends(me); + + // Get following you know count + const followingYouKnowCount = await Following.count({ + followee_id: { $in: myFollowingIds }, + follower_id: _user.id, + deleted_at: { $exists: false } + }); + _user.following_you_know_count = followingYouKnowCount; + + // Get followers you know count + const followersYouKnowCount = await Following.count({ + followee_id: _user.id, + follower_id: { $in: myFollowingIds }, + deleted_at: { $exists: false } + }); + _user.followers_you_know_count = followersYouKnowCount; + } } resolve(_user); diff --git a/src/web/app/common/tags/index.js b/src/web/app/common/tags/index.js index dd6ba75d7a..6e6081da9b 100644 --- a/src/web/app/common/tags/index.js +++ b/src/web/app/common/tags/index.js @@ -28,3 +28,4 @@ require('./reaction-picker.tag'); require('./reactions-viewer.tag'); require('./reaction-icon.tag'); require('./weekly-activity-chart.tag'); +require('./post-menu.tag'); diff --git a/src/web/app/common/tags/post-menu.tag b/src/web/app/common/tags/post-menu.tag new file mode 100644 index 0000000000..33895212bc --- /dev/null +++ b/src/web/app/common/tags/post-menu.tag @@ -0,0 +1,134 @@ + +
+
+ +
+ + +
diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag index 7a90dccf39..58343482d0 100644 --- a/src/web/app/desktop/tags/post-detail.tag +++ b/src/web/app/desktop/tags/post-detail.tag @@ -43,16 +43,18 @@
- - - + -
@@ -315,6 +317,13 @@ }); }; + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p + }); + }; + this.loadContext = () => { this.contextFetching = true; diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag index bce27cd7f3..cd7ac7d207 100644 --- a/src/web/app/desktop/tags/timeline.tag +++ b/src/web/app/desktop/tags/timeline.tag @@ -128,16 +128,16 @@
- - - - - - + -
-
+
@@ -64,19 +66,14 @@ :scope display block overflow hidden - margin 8px auto + margin 0 auto padding 0 - max-width 500px - width calc(100% - 16px) + width 100% text-align left background #fff border-radius 8px box-shadow 0 0 0 1px rgba(0, 0, 0, 0.2) - @media (min-width 500px) - margin 16px auto - width calc(100% - 32px) - > .fetching padding 64px 0 @@ -269,6 +266,7 @@ this.mixin('api'); + this.compact = this.opts.compact; this.post = this.opts.post; this.isRepost = this.post.repost != null; this.p = this.isRepost ? this.post.repost : this.post; @@ -299,14 +297,16 @@ } // Get replies - this.api('posts/replies', { - post_id: this.p.id, - limit: 8 - }).then(replies => { - this.update({ - replies: replies + if (!this.compact) { + this.api('posts/replies', { + post_id: this.p.id, + limit: 8 + }).then(replies => { + this.update({ + replies: replies + }); }); - }); + } }); this.reply = () => { @@ -332,6 +332,14 @@ }); }; + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p, + compact: true + }); + }; + this.loadContext = () => { this.contextFetching = true; diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag index 43470d197e..d8df8b2663 100644 --- a/src/web/app/mobile/tags/timeline.tag +++ b/src/web/app/mobile/tags/timeline.tag @@ -181,14 +181,17 @@
- - - +
@@ -558,6 +561,14 @@ compact: true }); }; + + this.menu = () => { + riot.mount(document.body.appendChild(document.createElement('mk-post-menu')), { + source: this.refs.menuButton, + post: this.p, + compact: true + }); + }; diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag index d85e3b51fd..0fe4055cf0 100644 --- a/src/web/app/mobile/tags/user.tag +++ b/src/web/app/mobile/tags/user.tag @@ -215,6 +215,7 @@ +

%i18n:mobile.tags.mk-user-overview.recent-posts%

@@ -240,6 +241,9 @@ max-width 600px margin 0 auto + > mk-post-detail + margin 0 0 8px 0 + > section background #eee border-radius 8px -- cgit v1.2.3-freya From 7fe0abc5cec71759807a41f383606dd156f11d88 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 30 Aug 2017 17:45:23 +0900 Subject: Implement #745 --- CHANGELOG.md | 1 + src/api/stream/home.ts | 10 ++++++++++ src/web/app/common/scripts/home-stream.js | 5 +++++ 3 files changed, 16 insertions(+) (limited to 'src/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a362dbef..cc5a6bc8a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ChangeLog (Release Notes) unreleased ---------- * New: 投稿のピン留め (#746) +* New: ホームストリームにメッセージを流すことでlast_used_atを更新できるようにする (#745) * その他細かな修正 2508 (2017/08/30) diff --git a/src/api/stream/home.ts b/src/api/stream/home.ts index 2ab8d3025b..d5fe01c261 100644 --- a/src/api/stream/home.ts +++ b/src/api/stream/home.ts @@ -2,6 +2,7 @@ import * as websocket from 'websocket'; import * as redis from 'redis'; import * as debug from 'debug'; +import User from '../models/user'; import serializePost from '../serializers/post'; const log = debug('misskey'); @@ -35,6 +36,15 @@ export default function homeStream(request: websocket.request, connection: webso const msg = JSON.parse(data.utf8Data); switch (msg.type) { + case 'alive': + // Update lastUsedAt + User.update({ _id: user._id }, { + $set: { + last_used_at: new Date() + } + }); + break; + case 'capture': if (!msg.id) return; const postId = msg.id; diff --git a/src/web/app/common/scripts/home-stream.js b/src/web/app/common/scripts/home-stream.js index c54cbd7f19..de9ceb3b51 100644 --- a/src/web/app/common/scripts/home-stream.js +++ b/src/web/app/common/scripts/home-stream.js @@ -12,6 +12,11 @@ class Connection extends Stream { i: me.token }); + // 最終利用日時を更新するため定期的にaliveメッセージを送信 + setInterval(() => { + this.send({ type: 'alive' }); + }, 1000 * 60); + this.on('i_updated', me.update); this.on('my_token_regenerated', () => { -- cgit v1.2.3-freya From c6b0bf42a112f0d9afa8920d6497cc76205ecaf4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 6 Sep 2017 23:19:58 +0900 Subject: wip --- locales/en.yml | 12 ++++ locales/ja.yml | 12 ++++ src/api/endpoints.ts | 4 ++ src/api/endpoints/posts/categorize.ts | 52 +++++++++++++++++ src/tools/ai/categorizer.ts | 93 ------------------------------- src/tools/ai/predict-all-post-category.ts | 57 +++++++++++++++++++ src/tools/ai/predict-user-interst.ts | 45 +++++++++++++++ src/web/app/common/tags/post-menu.tag | 23 ++++++++ 8 files changed, 205 insertions(+), 93 deletions(-) create mode 100644 src/api/endpoints/posts/categorize.ts delete mode 100644 src/tools/ai/categorizer.ts create mode 100644 src/tools/ai/predict-all-post-category.ts create mode 100644 src/tools/ai/predict-user-interst.ts (limited to 'src/api') diff --git a/locales/en.yml b/locales/en.yml index d40896212b..3b87ea758d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -22,6 +22,14 @@ common: confused: "Confused" pudding: "Pudding" + post_categories: + music: "Music" + game: "Video Game" + anime: "Anime" + it: "IT" + gadgets: "Gadgets" + photography: "Photography" + input-message-here: "Enter message here" send: "Send" delete: "Delete" @@ -80,6 +88,9 @@ common: mk-post-menu: pin: "Pin" pinned: "Pinned" + select: "Select category" + categorize: "Accept" + categorized: "Category reported. Thank you!" mk-reaction-picker: choose-reaction: "Pick your reaction" @@ -375,6 +386,7 @@ mobile: twitter-integration: "Twitter integration" signin-history: "Sign in history" api: "API" + link: "MisskeyLink" settings: "Settings" signout: "Sign out" diff --git a/locales/ja.yml b/locales/ja.yml index b8e5cff412..13d451b6d8 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -22,6 +22,14 @@ common: confused: "こまこまのこまり" pudding: "Pudding" + post_categories: + music: "音楽" + game: "ゲーム" + anime: "アニメ" + it: "IT" + gadgets: "ガジェット" + photography: "写真" + input-message-here: "ここにメッセージを入力" send: "送信" delete: "削除" @@ -80,6 +88,9 @@ common: mk-post-menu: pin: "ピン留め" pinned: "ピン留めしました" + select: "カテゴリを選択" + categorize: "決定" + categorized: "カテゴリを報告しました。これによりMisskeyが賢くなり、投稿の自動カテゴライズに役立てられます。ご協力ありがとうございました。" mk-reaction-picker: choose-reaction: "リアクションを選択" @@ -375,6 +386,7 @@ mobile: twitter-integration: "Twitter連携" signin-history: "ログイン履歴" api: "API" + link: "Misskeyリンク" settings: "設定" signout: "サインアウト" diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index e5be68c096..97b98895b8 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -394,6 +394,10 @@ const endpoints: Endpoint[] = [ name: 'posts/trend', withCredential: true }, + { + name: 'posts/categorize', + withCredential: true + }, { name: 'posts/reactions', withCredential: true diff --git a/src/api/endpoints/posts/categorize.ts b/src/api/endpoints/posts/categorize.ts new file mode 100644 index 0000000000..3530ba6bc4 --- /dev/null +++ b/src/api/endpoints/posts/categorize.ts @@ -0,0 +1,52 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import Post from '../../models/post'; + +/** + * Categorize a post + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + if (!user.is_pro) { + return rej('This endpoint is available only from a Pro account'); + } + + // Get 'post_id' parameter + const [postId, postIdErr] = $(params.post_id).id().$; + if (postIdErr) return rej('invalid post_id param'); + + // Get categorizee + const post = await Post.findOne({ + _id: postId + }); + + if (post === null) { + return rej('post not found'); + } + + if (post.is_category_verified) { + return rej('This post already has the verified category'); + } + + // Get 'category' parameter + const [category, categoryErr] = $(params.category).string().or([ + 'music', 'game', 'anime', 'it', 'gadgets', 'photography' + ]).$; + if (categoryErr) return rej('invalid category param'); + + // Set category + Post.update({ _id: post._id }, { + $set: { + category: category, + is_category_verified: true + } + }); + + // Send response + res(); +}); diff --git a/src/tools/ai/categorizer.ts b/src/tools/ai/categorizer.ts deleted file mode 100644 index c13374161d..0000000000 --- a/src/tools/ai/categorizer.ts +++ /dev/null @@ -1,93 +0,0 @@ -import * as fs from 'fs'; - -const bayes = require('./naive-bayes.js'); -const MeCab = require('mecab-async'); -import * as msgpack from 'msgpack-lite'; - -import Post from '../../api/models/post'; -import config from '../../conf'; - -/** - * 投稿を学習したり与えられた投稿のカテゴリを予測します - */ -export default class Categorizer { - private classifier: any; - private categorizerDbFilePath: string; - private mecab: any; - - constructor() { - this.categorizerDbFilePath = `${__dirname}/../../../data/category`; - - this.mecab = new MeCab(); - if (config.categorizer.mecab_command) this.mecab.command = config.categorizer.mecab_command; - - // BIND ----------------------------------- - this.tokenizer = this.tokenizer.bind(this); - } - - private tokenizer(text: string) { - return this.mecab.wakachiSync(text); - } - - public async init() { - try { - const buffer = fs.readFileSync(this.categorizerDbFilePath); - const db = msgpack.decode(buffer); - - this.classifier = bayes.import(db); - this.classifier.tokenizer = this.tokenizer; - } catch (e) { - this.classifier = bayes({ - tokenizer: this.tokenizer - }); - - // 訓練データ - const verifiedPosts = await Post.find({ - is_category_verified: true - }); - - // 学習 - verifiedPosts.forEach(post => { - this.classifier.learn(post.text, post.category); - }); - - this.save(); - } - } - - public async learn(id, category) { - const post = await Post.findOne({ _id: id }); - - Post.update({ _id: id }, { - $set: { - category: category, - is_category_verified: true - } - }); - - this.classifier.learn(post.text, category); - - this.save(); - } - - public async categorize(id) { - const post = await Post.findOne({ _id: id }); - - const category = this.classifier.categorize(post.text); - - Post.update({ _id: id }, { - $set: { - category: category - } - }); - } - - public async test(text) { - return this.classifier.categorize(text); - } - - private save() { - const buffer = msgpack.encode(this.classifier.export()); - fs.writeFileSync(this.categorizerDbFilePath, buffer); - } -} diff --git a/src/tools/ai/predict-all-post-category.ts b/src/tools/ai/predict-all-post-category.ts new file mode 100644 index 0000000000..87e198b39b --- /dev/null +++ b/src/tools/ai/predict-all-post-category.ts @@ -0,0 +1,57 @@ +const bayes = require('./naive-bayes.js'); +const MeCab = require('mecab-async'); + +import Post from '../../api/models/post'; +import config from '../../conf'; + +const classifier = bayes({ + tokenizer: this.tokenizer +}); + +const mecab = new MeCab(); +if (config.categorizer.mecab_command) mecab.command = config.categorizer.mecab_command; + +// 訓練データ取得 +Post.find({ + is_category_verified: true +}, { + fields: { + _id: false, + text: true, + category: true + } +}).then(verifiedPosts => { + // 学習 + verifiedPosts.forEach(post => { + classifier.learn(post.text, post.category); + }); + + // 全ての(人間によって証明されていない)投稿を取得 + Post.find({ + text: { + $exists: true + }, + is_category_verified: { + $ne: true + } + }, { + sort: { + _id: -1 + }, + fields: { + _id: true, + text: true + } + }).then(posts => { + posts.forEach(post => { + console.log(`predicting... ${post._id}`); + const category = classifier.categorize(post.text); + + Post.update({ _id: post._id }, { + $set: { + category: category + } + }); + }); + }); +}); diff --git a/src/tools/ai/predict-user-interst.ts b/src/tools/ai/predict-user-interst.ts new file mode 100644 index 0000000000..99bdfa4206 --- /dev/null +++ b/src/tools/ai/predict-user-interst.ts @@ -0,0 +1,45 @@ +import Post from '../../api/models/post'; +import User from '../../api/models/user'; + +export async function predictOne(id) { + console.log(`predict interest of ${id} ...`); + + // TODO: repostなども含める + const recentPosts = await Post.find({ + user_id: id, + category: { + $exists: true + } + }, { + sort: { + _id: -1 + }, + limit: 1000, + fields: { + _id: false, + category: true + } + }); + + const categories = {}; + + recentPosts.forEach(post => { + if (categories[post.category]) { + categories[post.category]++; + } else { + categories[post.category] = 1; + } + }); +} + +export async function predictAll() { + const allUsers = await User.find({}, { + fields: { + _id: true + } + }); + + allUsers.forEach(user => { + predictOne(user._id); + }); +} diff --git a/src/web/app/common/tags/post-menu.tag b/src/web/app/common/tags/post-menu.tag index 33895212bc..be4468a214 100644 --- a/src/web/app/common/tags/post-menu.tag +++ b/src/web/app/common/tags/post-menu.tag @@ -2,6 +2,18 @@
+
+ + +
+ + diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag index a323559218..f29f0a0c86 100644 --- a/src/web/app/mobile/tags/user.tag +++ b/src/web/app/mobile/tags/user.tag @@ -246,6 +246,12 @@
+
+

%i18n:mobile.tags.mk-user-overview.frequently-replied-users%

+
+ +
+

%i18n:mobile.tags.mk-user-overview.followers-you-know%

@@ -619,6 +625,58 @@ + +

%i18n:mobile.tags.mk-user-overview-frequently-replied-users.loading%

+
0 }> + + + +
+

%i18n:mobile.tags.mk-user-overview-frequently-replied-users.no-users%

+ + +
+

%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%

0 }> -- cgit v1.2.3-freya From e22a6f2913a51f93ce4681f12407070de40f1797 Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Sat, 9 Sep 2017 04:39:23 +0900 Subject: Update user.ts --- src/api/serializers/user.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index 57599fe85c..23a176096a 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -37,7 +37,9 @@ export default ( data: false } : { data: false, - profile: false + profile: false, + keywords: false, + domains: false }; // Populate the user if 'user' is ID @@ -119,6 +121,7 @@ export default ( if (opts.detail) { if (_user.pinned_post_id) { + // Populate pinned post _user.pinned_post = await serializePost(_user.pinned_post_id, meId, { detail: true }); -- cgit v1.2.3-freya From bbfac657fb95536f2e942fbd02343bb1185fc68b Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Sat, 16 Sep 2017 14:30:44 +0900 Subject: Refactoring --- src/api/authenticate.ts | 18 +++++++++++++----- src/api/streaming.ts | 22 ++++++++++------------ 2 files changed, 23 insertions(+), 17 deletions(-) (limited to 'src/api') diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index d4cc3fc41f..e044d4ae8b 100644 --- a/src/api/authenticate.ts +++ b/src/api/authenticate.ts @@ -1,6 +1,6 @@ import * as express from 'express'; import App from './models/app'; -import User from './models/user'; +import{ default as User, IUser } from './models/user'; import AccessToken from './models/access-token'; import isNativeToken from './common/is-native-token'; @@ -13,7 +13,7 @@ export interface IAuthContext { /** * Authenticated user */ - user: any; + user: IUser; /** * Weather if the request is via the User-Native Token or not @@ -25,11 +25,15 @@ export default (req: express.Request) => new Promise(async (resolv const token = req.body['i'] as string; if (token == null) { - return resolve({ app: null, user: null, isSecure: false }); + return resolve({ + app: null, + user: null, + isSecure: false + }); } if (isNativeToken(token)) { - const user = await User + const user: IUser = await User .findOne({ token: token }); if (user === null) { @@ -56,6 +60,10 @@ export default (req: express.Request) => new Promise(async (resolv const user = await User .findOne({ _id: accessToken.user_id }); - return resolve({ app: app, user: user, isSecure: false }); + return resolve({ + app: app, + user: user, + isSecure: false + }); } }); diff --git a/src/api/streaming.ts b/src/api/streaming.ts index c71132100c..db600013b9 100644 --- a/src/api/streaming.ts +++ b/src/api/streaming.ts @@ -2,7 +2,7 @@ import * as http from 'http'; import * as websocket from 'websocket'; import * as redis from 'redis'; import config from '../conf'; -import User from './models/user'; +import { default as User, IUser } from './models/user'; import AccessToken from './models/access-token'; import isNativeToken from './common/is-native-token'; @@ -26,7 +26,7 @@ module.exports = (server: http.Server) => { return; } - const user = await authenticate(connection, request.resourceURL.query.i); + const user = await authenticate(request.resourceURL.query.i); if (user == null) { connection.send('authentication-failed'); @@ -56,7 +56,11 @@ module.exports = (server: http.Server) => { }); }; -function authenticate(connection: websocket.connection, token: string): Promise { +/** + * 接続してきたユーザーを取得します + * @param token 送信されてきたトークン + */ +function authenticate(token: string): Promise { if (token == null) { return Promise.resolve(null); } @@ -64,8 +68,7 @@ function authenticate(connection: websocket.connection, token: string): Promise< return new Promise(async (resolve, reject) => { if (isNativeToken(token)) { // Fetch user - // SELECT _id - const user = await User + const user: IUser = await User .findOne({ token: token }); @@ -81,13 +84,8 @@ function authenticate(connection: websocket.connection, token: string): Promise< } // Fetch user - // SELECT _id - const user = await User - .findOne({ _id: accessToken.user_id }, { - fields: { - _id: true - } - }); + const user: IUser = await User + .findOne({ _id: accessToken.user_id }); resolve(user); } -- cgit v1.2.3-freya From b2e28869cc9410070b689517993f6cdbfd73e0f8 Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Sat, 16 Sep 2017 14:31:24 +0900 Subject: oops --- src/api/authenticate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index e044d4ae8b..6de91c16e8 100644 --- a/src/api/authenticate.ts +++ b/src/api/authenticate.ts @@ -1,6 +1,6 @@ import * as express from 'express'; import App from './models/app'; -import{ default as User, IUser } from './models/user'; +import { default as User, IUser } from './models/user'; import AccessToken from './models/access-token'; import isNativeToken from './common/is-native-token'; -- cgit v1.2.3-freya From 0b3cee9057ec4c0e9e640c72103d9dd229f0bd82 Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Sat, 16 Sep 2017 14:38:33 +0900 Subject: Refactor: Better English --- src/api/authenticate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index 6de91c16e8..48de748e90 100644 --- a/src/api/authenticate.ts +++ b/src/api/authenticate.ts @@ -16,7 +16,7 @@ export interface IAuthContext { user: IUser; /** - * Weather if the request is via the User-Native Token or not + * Weather requested with a User-Native Token */ isSecure: boolean; } -- cgit v1.2.3-freya From 76c0f67def857cabfcc70c01be28318e3175de97 Mon Sep 17 00:00:00 2001 From: こぴなたみぽ Date: Sat, 16 Sep 2017 14:38:58 +0900 Subject: typo --- src/api/authenticate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/api') diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts index 48de748e90..b289959ac1 100644 --- a/src/api/authenticate.ts +++ b/src/api/authenticate.ts @@ -16,7 +16,7 @@ export interface IAuthContext { user: IUser; /** - * Weather requested with a User-Native Token + * Whether requested with a User-Native Token */ isSecure: boolean; } -- cgit v1.2.3-freya From a12cc1a1b067745695bd6f55defe5eb2d7400da0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 16 Sep 2017 17:31:37 +0900 Subject: Refactoring --- src/api/private/signin.ts | 4 ++-- src/api/private/signup.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src/api') diff --git a/src/api/private/signin.ts b/src/api/private/signin.ts index afa83e50c3..c7dc243980 100644 --- a/src/api/private/signin.ts +++ b/src/api/private/signin.ts @@ -1,6 +1,6 @@ import * as express from 'express'; import * as bcrypt from 'bcryptjs'; -import User from '../models/user'; +import { default as User, IUser } from '../models/user'; import Signin from '../models/signin'; import serialize from '../serializers/signin'; import event from '../event'; @@ -23,7 +23,7 @@ export default async (req: express.Request, res: express.Response) => { } // Fetch user - const user = await User.findOne({ + const user: IUser = await User.findOne({ username_lower: username.toLowerCase() }, { fields: { diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts index 899fa88472..bcc17a876d 100644 --- a/src/api/private/signup.ts +++ b/src/api/private/signup.ts @@ -1,7 +1,7 @@ import * as express from 'express'; import * as bcrypt from 'bcryptjs'; import recaptcha = require('recaptcha-promise'); -import User from '../models/user'; +import { default as User, IUser } from '../models/user'; import { validateUsername, validatePassword } from '../models/user'; import serialize from '../serializers/user'; import generateUserToken from '../common/generate-native-user-token'; @@ -61,7 +61,7 @@ export default async (req: express.Request, res: express.Response) => { const secret = generateUserToken(); // Create account - const account = await User.insert({ + const account: IUser = await User.insert({ token: secret, avatar_id: null, banner_id: null, -- cgit v1.2.3-freya From 6a5c6280ffd3ffe820beb23294f1c2c1f5deb9cf Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 7 Oct 2017 03:36:46 +0900 Subject: :v: --- src/api/bot/core.ts | 88 ++++++++++++++++++++++++ src/api/bot/interfaces/line.ts | 37 ++++++++++ src/api/endpoints/i/appdata/set.ts | 2 +- src/api/models/user.ts | 3 + src/api/serializers/user.ts | 1 + src/api/server.ts | 2 + src/common/get-post-summary.js | 39 +++++++++++ src/config.ts | 3 + src/web/app/common/scripts/get-post-summary.js | 35 ---------- src/web/app/desktop/script.js | 2 +- src/web/app/desktop/tags/notifications.tag | 2 +- src/web/app/desktop/tags/pages/home.tag | 2 +- src/web/app/mobile/tags/notification-preview.tag | 2 +- src/web/app/mobile/tags/notification.tag | 2 +- src/web/app/mobile/tags/notifications.tag | 2 +- src/web/app/mobile/tags/page/home.tag | 2 +- src/web/app/mobile/tags/post-detail.tag | 2 +- src/web/app/mobile/tags/timeline.tag | 2 +- src/web/app/mobile/tags/user.tag | 2 +- tslint.json | 1 + 20 files changed, 185 insertions(+), 46 deletions(-) create mode 100644 src/api/bot/core.ts create mode 100644 src/api/bot/interfaces/line.ts create mode 100644 src/common/get-post-summary.js delete mode 100644 src/web/app/common/scripts/get-post-summary.js (limited to 'src/api') diff --git a/src/api/bot/core.ts b/src/api/bot/core.ts new file mode 100644 index 0000000000..002ac1b06e --- /dev/null +++ b/src/api/bot/core.ts @@ -0,0 +1,88 @@ +import * as EventEmitter from 'events'; +import * as bcrypt from 'bcryptjs'; + +import User, { IUser } from '../models/user'; + +export default class BotCore extends EventEmitter { + public user: IUser; + + private context: Context = null; + + constructor(user: IUser) { + super(); + + this.user = user; + } + + public async q(query: string): Promise { + if (this.context != null) { + return await this.context.q(query); + } + + switch (query) { + case 'ping': + return 'PONG'; + case 'ログイン': + case 'サインイン': + this.context = new SigninContext(this); + return await this.context.greet(); + default: + return '?'; + } + } + + public setUser(user: IUser) { + this.user = user; + this.emit('set-user', user); + } +} + +abstract class Context { + protected core: BotCore; + + public abstract async greet(): Promise; + public abstract async q(query: string): Promise; + + constructor(core: BotCore) { + this.core = core; + } +} + +class SigninContext extends Context { + private temporaryUser: IUser; + + public async greet(): Promise { + return 'まずユーザー名を教えてください:'; + } + + public async q(query: string): Promise { + if (this.temporaryUser == null) { + // Fetch user + const user: IUser = await User.findOne({ + username_lower: query.toLowerCase() + }, { + fields: { + data: false, + profile: false + } + }); + + if (user === null) { + return `${query}というユーザーは存在しませんでした... もう一度教えてください:`; + } else { + this.temporaryUser = user; + return `パスワードを教えてください:`; + } + } else { + // Compare password + const same = bcrypt.compareSync(query, this.temporaryUser.password); + + if (same) { + this.core.setUser(this.temporaryUser); + return `${this.temporaryUser.name}さん、おかえりなさい!`; + } else { + return `パスワードが違います... もう一度教えてください:`; + } + } + } +} diff --git a/src/api/bot/interfaces/line.ts b/src/api/bot/interfaces/line.ts new file mode 100644 index 0000000000..4bee844c12 --- /dev/null +++ b/src/api/bot/interfaces/line.ts @@ -0,0 +1,37 @@ +import * as EventEmitter from 'events'; +import * as express from 'express'; +import * as crypto from 'crypto'; +//import User from '../../models/user'; +import config from '../../../conf'; +/*import BotCore from '../core'; + +const sessions: { + userId: string; + session: BotCore; +}[] = []; +*/ +module.exports = async (app: express.Application) => { + if (config.line_bot == null) return; + + const handler = new EventEmitter(); + + app.post('/hooks/line', (req, res, next) => { + // req.headers['X-Line-Signature'] は常に string ですが、型定義の都合上 + // string | string[] になっているので string を明示しています + const sig1 = req.headers['X-Line-Signature'] as string; + + const hash = crypto.createHmac('sha256', config.line_bot.channel_secret) + .update(JSON.stringify(req.body)); + + const sig2 = hash.digest('base64'); + + // シグネチャ比較 + if (sig1 === sig2) { + console.log(req.body); + handler.emit(req.body.type); + res.sendStatus(200); + } else { + res.sendStatus(400); + } + }); +}; diff --git a/src/api/endpoints/i/appdata/set.ts b/src/api/endpoints/i/appdata/set.ts index 24f192de6b..9c3dbe185b 100644 --- a/src/api/endpoints/i/appdata/set.ts +++ b/src/api/endpoints/i/appdata/set.ts @@ -21,7 +21,7 @@ module.exports = (params, user, app, isSecure) => new Promise(async (res, rej) = const [data, dataError] = $(params.data).optional.object() .pipe(obj => { const hasInvalidData = Object.entries(obj).some(([k, v]) => - $(k).string().match(/^[a-z_]+$/).isNg() && $(v).string().isNg()); + $(k).string().match(/^[a-z_]+$/).nok() && $(v).string().nok()); return !hasInvalidData; }).$; if (dataError) return rej('invalid data param'); diff --git a/src/api/models/user.ts b/src/api/models/user.ts index 1591b339bc..4f8086d42b 100644 --- a/src/api/models/user.ts +++ b/src/api/models/user.ts @@ -57,6 +57,9 @@ export type IUser = { user_id: string; screen_name: string; }; + line: { + user_id: string; + }; description: string; profile: { location: string; diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts index 23a176096a..3deff2d003 100644 --- a/src/api/serializers/user.ts +++ b/src/api/serializers/user.ts @@ -79,6 +79,7 @@ export default ( delete _user.twitter.access_token; delete _user.twitter.access_token_secret; } + delete _user.line; // Visible via only the official client if (!opts.includeSecrets) { diff --git a/src/api/server.ts b/src/api/server.ts index c98167eb3e..fdff0c7546 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -54,4 +54,6 @@ app.use((req, res, next) => { require('./service/github')(app); require('./service/twitter')(app); +require('./bot/interfaces/line')(app); + module.exports = app; diff --git a/src/common/get-post-summary.js b/src/common/get-post-summary.js new file mode 100644 index 0000000000..f7a481a164 --- /dev/null +++ b/src/common/get-post-summary.js @@ -0,0 +1,39 @@ +/** + * 投稿を表す文字列を取得します。 + * @param {*} post 投稿 + */ +const summarize = post => { + let summary = post.text ? post.text : ''; + + // メディアが添付されているとき + if (post.media) { + summary += ` (${post.media.length}つのメディア)`; + } + + // 投票が添付されているとき + if (post.poll) { + summary += ' (投票)'; + } + + // 返信のとき + if (post.reply_to_id) { + if (post.reply_to) { + summary += ` RE: ${summarize(post.reply_to)}`; + } else { + summary += ' RE: ...'; + } + } + + // Repostのとき + if (post.repost_id) { + if (post.repost) { + summary += ` RP: ${summarize(post.repost)}`; + } else { + summary += ' RP: ...'; + } + } + + return summary.trim(); +}; + +export default summarize; diff --git a/src/config.ts b/src/config.ts index f8facdee2e..0ea332f67d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -68,6 +68,9 @@ type Source = { hook_secret: string; username: string; }; + line_bot?: { + channel_secret: string; + }; analysis?: { mecab_command?: string; }; diff --git a/src/web/app/common/scripts/get-post-summary.js b/src/web/app/common/scripts/get-post-summary.js deleted file mode 100644 index 83eda8f6b4..0000000000 --- a/src/web/app/common/scripts/get-post-summary.js +++ /dev/null @@ -1,35 +0,0 @@ -const summarize = post => { - let summary = post.text ? post.text : ''; - - // メディアが添付されているとき - if (post.media) { - summary += ` (${post.media.length}つのメディア)`; - } - - // 投票が添付されているとき - if (post.poll) { - summary += ' (投票)'; - } - - // 返信のとき - if (post.reply_to_id) { - if (post.reply_to) { - summary += ` RE: ${summarize(post.reply_to)}`; - } else { - summary += ' RE: ...'; - } - } - - // Repostのとき - if (post.repost_id) { - if (post.repost) { - summary += ` RP: ${summarize(post.repost)}`; - } else { - summary += ' RP: ...'; - } - } - - return summary.trim(); -}; - -export default summarize; diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.js index 2e81147943..e3dc8b7d96 100644 --- a/src/web/app/desktop/script.js +++ b/src/web/app/desktop/script.js @@ -11,7 +11,7 @@ import * as riot from 'riot'; import init from '../init'; import route from './router'; import fuckAdBlock from './scripts/fuck-ad-block'; -import getPostSummary from '../common/scripts/get-post-summary'; +import getPostSummary from '../../../common/get-post-summary'; /** * init diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag index 21e4fe7fa5..4747d1c0f4 100644 --- a/src/web/app/desktop/tags/notifications.tag +++ b/src/web/app/desktop/tags/notifications.tag @@ -207,7 +207,7 @@ diff --git a/src/web/app/mobile/tags/notification.tag b/src/web/app/mobile/tags/notification.tag index 3663709525..416493ee23 100644 --- a/src/web/app/mobile/tags/notification.tag +++ b/src/web/app/mobile/tags/notification.tag @@ -163,7 +163,7 @@ diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag index 2f314769db..9985b3351c 100644 --- a/src/web/app/mobile/tags/notifications.tag +++ b/src/web/app/mobile/tags/notifications.tag @@ -78,7 +78,7 @@ diff --git a/src/web/app/mobile/tags/notification.tag b/src/web/app/mobile/tags/notification.tag index 416493ee23..53222b9dbe 100644 --- a/src/web/app/mobile/tags/notification.tag +++ b/src/web/app/mobile/tags/notification.tag @@ -163,7 +163,7 @@ diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag index 9985b3351c..7370aa84d3 100644 --- a/src/web/app/mobile/tags/notifications.tag +++ b/src/web/app/mobile/tags/notifications.tag @@ -78,7 +78,7 @@ + diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag index e0d7393b08..452a72c00a 100644 --- a/src/web/app/desktop/tags/ui.tag +++ b/src/web/app/desktop/tags/ui.tag @@ -319,18 +319,26 @@ -
    -
  • - - -

    %i18n:desktop.tags.mk-ui-header-nav.home%

    -
    -
  • -
  • - - -

    %i18n:desktop.tags.mk-ui-header-nav.messaging%

    - +
      + +
    • + + +

      %i18n:desktop.tags.mk-ui-header-nav.home%

      +
      +
    • +
    • + + +

      %i18n:desktop.tags.mk-ui-header-nav.messaging%

      + +
      +
    • + +
    • + + +

      %i18n:desktop.tags.mk-ui-header-nav.bbs%

    • -- cgit v1.2.3-freya From caa47cb38cfc3950539c78ca2e70f2c50e815d2c Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 30 Oct 2017 22:12:10 +0900 Subject: 未読の通知がある場合アイコンを表示するように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 + locales/en.yml | 1 + locales/ja.yml | 1 + src/api/common/read-notification.ts | 52 +++ src/api/endpoints.ts | 8 +- src/api/endpoints/i/notifications.ts | 14 +- .../endpoints/notifications/get_unread_count.ts | 23 ++ src/api/endpoints/notifications/mark_as_read.ts | 47 --- .../endpoints/notifications/mark_as_read_all.ts | 32 ++ src/api/models/notification.ts | 5 + src/api/stream/home.ts | 6 + src/web/app/desktop/tags/notifications.tag | 6 + src/web/app/mobile/tags/index.js | 2 - src/web/app/mobile/tags/notifications.tag | 6 + src/web/app/mobile/tags/page/notifications.tag | 14 + src/web/app/mobile/tags/ui-header.tag | 156 --------- src/web/app/mobile/tags/ui-nav.tag | 170 ---------- src/web/app/mobile/tags/ui.tag | 368 +++++++++++++++++++++ 18 files changed, 524 insertions(+), 391 deletions(-) create mode 100644 src/api/common/read-notification.ts create mode 100644 src/api/endpoints/notifications/get_unread_count.ts delete mode 100644 src/api/endpoints/notifications/mark_as_read.ts create mode 100644 src/api/endpoints/notifications/mark_as_read_all.ts delete mode 100644 src/web/app/mobile/tags/ui-header.tag delete mode 100644 src/web/app/mobile/tags/ui-nav.tag (limited to 'src/api') diff --git a/CHANGELOG.md b/CHANGELOG.md index ca41d016c1..bf5c1fcb2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ChangeLog (Release Notes) ========================= 主に notable な changes を書いていきます +unreleased +---------- +* New: 未読の通知がある場合アイコンを表示するように + 2747 (2017/10/25) ----------------- * Fix: 非ログイン状態ですべてのページが致命的な問題を発生させる (#89) diff --git a/locales/en.yml b/locales/en.yml index 03d5306d3e..020813ddbb 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -389,6 +389,7 @@ mobile: mk-notifications-page: notifications: "Notifications" + read-all: "Are you sure you want to mark as read all your notifications?" mk-post-page: title: "Post" diff --git a/locales/ja.yml b/locales/ja.yml index b640f0f248..1b3058fe02 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -389,6 +389,7 @@ mobile: mk-notifications-page: notifications: "通知" + read-all: "すべての通知を既読にしますか?" mk-post-page: title: "投稿" diff --git a/src/api/common/read-notification.ts b/src/api/common/read-notification.ts new file mode 100644 index 0000000000..3009cc5d08 --- /dev/null +++ b/src/api/common/read-notification.ts @@ -0,0 +1,52 @@ +import * as mongo from 'mongodb'; +import { default as Notification, INotification } from '../models/notification'; +import publishUserStream from '../event'; + +/** + * Mark as read notification(s) + */ +export default ( + user: string | mongo.ObjectID, + message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] +) => new Promise(async (resolve, reject) => { + + const userId = mongo.ObjectID.prototype.isPrototypeOf(user) + ? user + : new mongo.ObjectID(user); + + 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 INotification[]).map(m => m._id) + : mongo.ObjectID.prototype.isPrototypeOf(message) + ? [(message as mongo.ObjectID)] + : typeof message === 'string' + ? [new mongo.ObjectID(message)] + : [(message as INotification)._id]; + + // Update documents + await Notification.update({ + _id: { $in: ids }, + is_read: false + }, { + $set: { + is_read: true + } + }, { + multi: true + }); + + // Calc count of my unread notifications + const count = await Notification + .count({ + notifiee_id: userId, + is_read: false + }); + + if (count == 0) { + // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 + publishUserStream(userId, 'read_all_notifications'); + } +}); diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index f05762340c..29a97bcb8a 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -196,17 +196,17 @@ const endpoints: Endpoint[] = [ kind: 'notification-read' }, { - name: 'notifications/delete', + name: 'notifications/get_unread_count', withCredential: true, - kind: 'notification-write' + kind: 'notification-read' }, { - name: 'notifications/delete_all', + name: 'notifications/delete', withCredential: true, kind: 'notification-write' }, { - name: 'notifications/mark_as_read', + name: 'notifications/delete_all', withCredential: true, kind: 'notification-write' }, diff --git a/src/api/endpoints/i/notifications.ts b/src/api/endpoints/i/notifications.ts index 5575fb7412..607e0768a4 100644 --- a/src/api/endpoints/i/notifications.ts +++ b/src/api/endpoints/i/notifications.ts @@ -5,6 +5,7 @@ import $ from 'cafy'; import Notification from '../../models/notification'; import serialize from '../../serializers/notification'; import getFriends from '../../common/get-friends'; +import read from '../../common/read-notification'; /** * Get notifications @@ -91,17 +92,6 @@ module.exports = (params, user) => new Promise(async (res, rej) => { // Mark as read all if (notifications.length > 0 && markAsRead) { - const ids = notifications - .filter(x => x.is_read == false) - .map(x => x._id); - - // Update documents - await Notification.update({ - _id: { $in: ids } - }, { - $set: { is_read: true } - }, { - multi: true - }); + read(user._id, notifications); } }); diff --git a/src/api/endpoints/notifications/get_unread_count.ts b/src/api/endpoints/notifications/get_unread_count.ts new file mode 100644 index 0000000000..9514e78713 --- /dev/null +++ b/src/api/endpoints/notifications/get_unread_count.ts @@ -0,0 +1,23 @@ +/** + * Module dependencies + */ +import Notification from '../../models/notification'; + +/** + * Get count of unread notifications + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const count = await Notification + .count({ + notifiee_id: user._id, + is_read: false + }); + + res({ + count: count + }); +}); diff --git a/src/api/endpoints/notifications/mark_as_read.ts b/src/api/endpoints/notifications/mark_as_read.ts deleted file mode 100644 index 5cce33e850..0000000000 --- a/src/api/endpoints/notifications/mark_as_read.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Notification from '../../models/notification'; -import serialize from '../../serializers/notification'; -import event from '../../event'; - -/** - * Mark as read a notification - * - * @param {any} params - * @param {any} user - * @return {Promise} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const [notificationId, notificationIdErr] = $(params.notification_id).id().$; - if (notificationIdErr) return rej('invalid notification_id param'); - - // Get notification - const notification = await Notification - .findOne({ - _id: notificationId, - i: user._id - }); - - if (notification === null) { - return rej('notification-not-found'); - } - - // Update - notification.is_read = true; - Notification.update({ _id: notification._id }, { - $set: { - is_read: true - } - }); - - // Response - res(); - - // Serialize - const notificationObj = await serialize(notification); - - // Publish read_notification event - event(user._id, 'read_notification', notificationObj); -}); diff --git a/src/api/endpoints/notifications/mark_as_read_all.ts b/src/api/endpoints/notifications/mark_as_read_all.ts new file mode 100644 index 0000000000..3550e344c4 --- /dev/null +++ b/src/api/endpoints/notifications/mark_as_read_all.ts @@ -0,0 +1,32 @@ +/** + * Module dependencies + */ +import Notification from '../../models/notification'; +import event from '../../event'; + +/** + * Mark as read all notifications + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + // Update documents + await Notification.update({ + notifiee_id: user._id, + is_read: false + }, { + $set: { + is_read: true + } + }, { + multi: true + }); + + // Response + res(); + + // 全ての通知を読みましたよというイベントを発行 + event(user._id, 'read_all_notifications'); +}); diff --git a/src/api/models/notification.ts b/src/api/models/notification.ts index 1c1f429a0d..1065e8baaa 100644 --- a/src/api/models/notification.ts +++ b/src/api/models/notification.ts @@ -1,3 +1,8 @@ +import * as mongo from 'mongodb'; import db from '../../db/mongodb'; export default db.get('notifications') as any; // fuck type definition + +export interface INotification { + _id: mongo.ObjectID; +} diff --git a/src/api/stream/home.ts b/src/api/stream/home.ts index d5fe01c261..7c8f3bfec8 100644 --- a/src/api/stream/home.ts +++ b/src/api/stream/home.ts @@ -4,6 +4,7 @@ import * as debug from 'debug'; import User from '../models/user'; import serializePost from '../serializers/post'; +import readNotification from '../common/read-notification'; const log = debug('misskey'); @@ -45,6 +46,11 @@ export default function homeStream(request: websocket.request, connection: webso }); break; + case 'read_notification': + if (!msg.id) return; + readNotification(user._id, msg.id); + break; + case 'capture': if (!msg.id) return; const postId = msg.id; diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag index 1046358ce9..a4f66105a8 100644 --- a/src/web/app/desktop/tags/notifications.tag +++ b/src/web/app/desktop/tags/notifications.tag @@ -252,6 +252,12 @@ }); this.onNotification = notification => { + // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない + this.stream.send({ + type: 'read_notification', + id: notification.id + }); + this.notifications.unshift(notification); this.update(); }; diff --git a/src/web/app/mobile/tags/index.js b/src/web/app/mobile/tags/index.js index c5aafd20ba..a79f4f7e7e 100644 --- a/src/web/app/mobile/tags/index.js +++ b/src/web/app/mobile/tags/index.js @@ -1,6 +1,4 @@ require('./ui.tag'); -require('./ui-header.tag'); -require('./ui-nav.tag'); require('./page/entrance.tag'); require('./page/entrance/signin.tag'); require('./page/entrance/signup.tag'); diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag index 7370aa84d3..2e95990314 100644 --- a/src/web/app/mobile/tags/notifications.tag +++ b/src/web/app/mobile/tags/notifications.tag @@ -123,6 +123,12 @@ }); this.onNotification = notification => { + // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない + this.stream.send({ + type: 'read_notification', + id: notification.id + }); + this.notifications.unshift(notification); this.update(); }; diff --git a/src/web/app/mobile/tags/page/notifications.tag b/src/web/app/mobile/tags/page/notifications.tag index 06a5be039f..743de04393 100644 --- a/src/web/app/mobile/tags/page/notifications.tag +++ b/src/web/app/mobile/tags/page/notifications.tag @@ -10,16 +10,30 @@ import ui from '../../scripts/ui-event'; import Progress from '../../../common/scripts/loading'; + this.mixin('api'); + this.on('mount', () => { document.title = 'Misskey | %i18n:mobile.tags.mk-notifications-page.notifications%'; ui.trigger('title', '%i18n:mobile.tags.mk-notifications-page.notifications%'); document.documentElement.style.background = '#313a42'; + ui.trigger('func', () => { + this.readAll(); + }, 'check'); + Progress.start(); this.refs.ui.refs.notifications.on('fetched', () => { Progress.done(); }); }); + + this.readAll = () => { + const ok = window.confirm('%i18n:mobile.tags.mk-notifications-page.read-all%'); + + if (!ok) return; + + this.api('notifications/mark_as_read_all'); + }; diff --git a/src/web/app/mobile/tags/ui-header.tag b/src/web/app/mobile/tags/ui-header.tag deleted file mode 100644 index 10b44b2153..0000000000 --- a/src/web/app/mobile/tags/ui-header.tag +++ /dev/null @@ -1,156 +0,0 @@ - - -
      -
      -
      - - -

      Misskey

      - -
      -
      - - -
      diff --git a/src/web/app/mobile/tags/ui-nav.tag b/src/web/app/mobile/tags/ui-nav.tag deleted file mode 100644 index 34235ba4f1..0000000000 --- a/src/web/app/mobile/tags/ui-nav.tag +++ /dev/null @@ -1,170 +0,0 @@ - -
      - - - -
      diff --git a/src/web/app/mobile/tags/ui.tag b/src/web/app/mobile/tags/ui.tag index 9d9cd4d74a..fb8cbcdbd2 100644 --- a/src/web/app/mobile/tags/ui.tag +++ b/src/web/app/mobile/tags/ui.tag @@ -30,9 +30,377 @@ }; this.onStreamNotification = notification => { + // TODO: ユーザーが画面を見てないと思われるとき(ブラウザやタブがアクティブじゃないなど)は送信しない + this.stream.send({ + type: 'read_notification', + id: notification.id + }); + riot.mount(document.body.appendChild(document.createElement('mk-notify')), { notification: notification }); }; + + + +
      +
      +
      + + +

      Misskey

      + +
      +
      + + +
      + + +
      + + + +
      -- cgit v1.2.3-freya From dc9fddf839df7959a83819eb7064f402db05f200 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 31 Oct 2017 21:42:11 +0900 Subject: RENAME: bbs -> channel --- locales/en.yml | 7 ++--- locales/ja.yml | 7 ++--- src/api/endpoints/bbs/threads/create.ts | 12 ++++---- src/api/models/bbs-thread.ts | 13 --------- src/api/models/channel.ts | 13 +++++++++ src/api/serializers/bbs-thread.ts | 44 ----------------------------- src/api/serializers/channel.ts | 44 +++++++++++++++++++++++++++++ src/web/app/desktop/tags/index.js | 2 +- src/web/app/desktop/tags/pages/bbs.tag | 30 -------------------- src/web/app/desktop/tags/pages/channels.tag | 30 ++++++++++++++++++++ 10 files changed, 100 insertions(+), 102 deletions(-) delete mode 100644 src/api/models/bbs-thread.ts create mode 100644 src/api/models/channel.ts delete mode 100644 src/api/serializers/bbs-thread.ts create mode 100644 src/api/serializers/channel.ts delete mode 100644 src/web/app/desktop/tags/pages/bbs.tag create mode 100644 src/web/app/desktop/tags/pages/channels.tag (limited to 'src/api') diff --git a/locales/en.yml b/locales/en.yml index f0204b52cb..da532fc78a 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -352,10 +352,9 @@ desktop: mk-repost-form-window: title: "Are you sure you want to repost this post?" - mk-bbs-page: - title: "Misskey BBS" - new: "Create new thread" - thread-title: "Thread title" + mk-channels-page: + new: "Create new channel" + channel-title: "Channel title" mobile: tags: diff --git a/locales/ja.yml b/locales/ja.yml index 65d92782f2..1ae94652b5 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -352,10 +352,9 @@ desktop: mk-repost-form-window: title: "この投稿をRepostしますか?" - mk-bbs-page: - title: "Misskey掲示板" - new: "スレッドを作成" - thread-title: "スレッドのタイトル" + mk-channels-page: + new: "チャンネルを作成" + channel-title: "チャンネルのタイトル" mobile: tags: diff --git a/src/api/endpoints/bbs/threads/create.ts b/src/api/endpoints/bbs/threads/create.ts index 71d61d8711..d9b4d34a0c 100644 --- a/src/api/endpoints/bbs/threads/create.ts +++ b/src/api/endpoints/bbs/threads/create.ts @@ -2,11 +2,11 @@ * Module dependencies */ import $ from 'cafy'; -import Thread from '../../../models/bbs-thread'; -import serialize from '../../../serializers/bbs-thread'; +import Channel from '../../../models/channel'; +import serialize from '../../../serializers/channel'; /** - * Create a thread + * Create a channel * * @param {any} params * @param {any} user @@ -17,13 +17,13 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { const [title, titleErr] = $(params.title).string().range(1, 100).$; if (titleErr) return rej('invalid title param'); - // Create a thread - const thread = await Thread.insert({ + // Create a channel + const channel = await Channel.insert({ created_at: new Date(), user_id: user._id, title: title }); // Response - res(await serialize(thread)); + res(await serialize(channel)); }); diff --git a/src/api/models/bbs-thread.ts b/src/api/models/bbs-thread.ts deleted file mode 100644 index a92157c6f4..0000000000 --- a/src/api/models/bbs-thread.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../../db/mongodb'; - -const collection = db.get('bbs_threads'); - -export default collection as any; // fuck type definition - -export type IBbsThread = { - _id: mongo.ObjectID; - created_at: Date; - title: string; - user_id: mongo.ObjectID; -}; diff --git a/src/api/models/channel.ts b/src/api/models/channel.ts new file mode 100644 index 0000000000..79edb71367 --- /dev/null +++ b/src/api/models/channel.ts @@ -0,0 +1,13 @@ +import * as mongo from 'mongodb'; +import db from '../../db/mongodb'; + +const collection = db.get('channels'); + +export default collection as any; // fuck type definition + +export type IChannel = { + _id: mongo.ObjectID; + created_at: Date; + title: string; + user_id: mongo.ObjectID; +}; diff --git a/src/api/serializers/bbs-thread.ts b/src/api/serializers/bbs-thread.ts deleted file mode 100644 index d9e41a8468..0000000000 --- a/src/api/serializers/bbs-thread.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import { IUser } from '../models/user'; -import { default as Thread, IBbsThread } from '../models/bbs-thread'; - -/** - * Serialize a thread - * - * @param thread target - * @param me? serializee - * @return response - */ -export default ( - thread: string | mongo.ObjectID | IBbsThread, - me?: string | mongo.ObjectID | IUser -) => new Promise(async (resolve, reject) => { - - let _thread: any; - - // Populate the thread if 'thread' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(thread)) { - _thread = await Thread.findOne({ - _id: thread - }); - } else if (typeof thread === 'string') { - _thread = await Thread.findOne({ - _id: new mongo.ObjectID(thread) - }); - } else { - _thread = deepcopy(thread); - } - - // Rename _id to id - _thread.id = _thread._id; - delete _thread._id; - - // Remove needless properties - delete _thread.user_id; - - resolve(_thread); -}); diff --git a/src/api/serializers/channel.ts b/src/api/serializers/channel.ts new file mode 100644 index 0000000000..d4e16d6be3 --- /dev/null +++ b/src/api/serializers/channel.ts @@ -0,0 +1,44 @@ +/** + * Module dependencies + */ +import * as mongo from 'mongodb'; +import deepcopy = require('deepcopy'); +import { IUser } from '../models/user'; +import { default as Channel, IChannel } from '../models/channel'; + +/** + * Serialize a channel + * + * @param channel target + * @param me? serializee + * @return response + */ +export default ( + channel: string | mongo.ObjectID | IChannel, + me?: string | mongo.ObjectID | IUser +) => new Promise(async (resolve, reject) => { + + let _channel: any; + + // Populate the channel if 'channel' is ID + if (mongo.ObjectID.prototype.isPrototypeOf(channel)) { + _channel = await Channel.findOne({ + _id: channel + }); + } else if (typeof channel === 'string') { + _channel = await Channel.findOne({ + _id: new mongo.ObjectID(channel) + }); + } else { + _channel = deepcopy(channel); + } + + // Rename _id to id + _channel.id = _channel._id; + delete _channel._id; + + // Remove needless properties + delete _channel.user_id; + + resolve(_channel); +}); diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js index fa7161ddfa..6d49006526 100644 --- a/src/web/app/desktop/tags/index.js +++ b/src/web/app/desktop/tags/index.js @@ -61,7 +61,7 @@ require('./pages/user.tag'); require('./pages/post.tag'); require('./pages/search.tag'); require('./pages/not-found.tag'); -require('./pages/bbs.tag'); +require('./pages/channels.tag'); require('./autocomplete-suggestion.tag'); require('./progress-dialog.tag'); require('./user-preview.tag'); diff --git a/src/web/app/desktop/tags/pages/bbs.tag b/src/web/app/desktop/tags/pages/bbs.tag deleted file mode 100644 index cb58af1934..0000000000 --- a/src/web/app/desktop/tags/pages/bbs.tag +++ /dev/null @@ -1,30 +0,0 @@ - - -
      -

      %i18n:desktop.tags.mk-bbs-page.title%

      - -
      -
      - - -
      diff --git a/src/web/app/desktop/tags/pages/channels.tag b/src/web/app/desktop/tags/pages/channels.tag new file mode 100644 index 0000000000..9e47e52d25 --- /dev/null +++ b/src/web/app/desktop/tags/pages/channels.tag @@ -0,0 +1,30 @@ + + +
      +

      %i18n:desktop.tags.mk-bbs-page.title%

      + +
      +
      + + +
      -- cgit v1.2.3-freya From b4340b1d91a6fc1679c3cb891ea800e1b491109c Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 31 Oct 2017 22:09:09 +0900 Subject: wip --- src/api/endpoints/posts/create.ts | 47 ++++++++++++++++++++++++++++++++++----- src/api/models/post.ts | 1 + 2 files changed, 42 insertions(+), 6 deletions(-) (limited to 'src/api') diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index 805dba7f83..42a55f850e 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -4,9 +4,9 @@ import $ from 'cafy'; import deepEqual = require('deep-equal'); import parse from '../../common/text'; -import Post from '../../models/post'; -import { isValidText } from '../../models/post'; +import { default as Post, IPost, isValidText } from '../../models/post'; import { default as User, IUser } from '../../models/user'; +import { default as Channel, IChannel } from '../../models/channel'; import Following from '../../models/following'; import DriveFile from '../../models/drive-file'; import Watching from '../../models/post-watching'; @@ -62,7 +62,8 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { const [repostId, repostIdErr] = $(params.repost_id).optional.id().$; if (repostIdErr) return rej('invalid repost_id'); - let repost = null; + let repost: IPost = null; + let isQuote = false; if (repostId !== undefined) { // Fetch repost to post repost = await Post.findOne({ @@ -84,18 +85,20 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { } }); + isQuote = text != null || files != null; + // 直近と同じRepost対象かつ引用じゃなかったらエラー if (latestPost && latestPost.repost_id && latestPost.repost_id.equals(repost._id) && - text === undefined && files === null) { + !isQuote) { return rej('cannot repost same post that already reposted in your latest post'); } // 直近がRepost対象かつ引用じゃなかったらエラー if (latestPost && latestPost._id.equals(repost._id) && - text === undefined && files === null) { + !isQuote) { return rej('cannot repost your latest post'); } } @@ -104,7 +107,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { const [inReplyToPostId, inReplyToPostIdErr] = $(params.reply_to_id).optional.id().$; if (inReplyToPostIdErr) return rej('invalid in_reply_to_post_id'); - let inReplyToPost = null; + let inReplyToPost: IPost = null; if (inReplyToPostId !== undefined) { // Fetch reply inReplyToPost = await Post.findOne({ @@ -121,6 +124,37 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { } } + // Get 'channel_id' parameter + const [channelId, channelIdErr] = $(params.channel_id).optional.id().$; + if (channelIdErr) return rej('invalid channel_id'); + + let channel: IChannel = null; + if (channelId !== undefined) { + // Fetch channel + channel = await Channel.findOne({ + _id: channelId + }); + + if (channel === null) { + return rej('channel not found'); + } + + // 返信対象の投稿がこのチャンネルじゃなかったらダメ + if (inReplyToPost && !channelId.equals(inReplyToPost.channel_id)) { + return rej('チャンネル内部からチャンネル外部の投稿に返信することはできません'); + } + + // Repost対象の投稿がこのチャンネルじゃなかったらダメ + if (repost && !channelId.equals(repost.channel_id)) { + return rej('チャンネル内部からチャンネル外部の投稿をRepostすることはできません'); + } + + // 引用ではないRepostはダメ + if (repost && !isQuote) { + return rej('チャンネル内部では引用ではないRepostをすることはできません'); + } + } + // Get 'poll' parameter const [poll, pollErr] = $(params.poll).optional.strict.object() .have('choices', $().array('string') @@ -164,6 +198,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { // 投稿を作成 const post = await Post.insert({ created_at: new Date(), + channel_id: channel ? channel._id : undefined, media_ids: files ? files.map(file => file._id) : undefined, reply_to_id: inReplyToPost ? inReplyToPost._id : undefined, repost_id: repost ? repost._id : undefined, diff --git a/src/api/models/post.ts b/src/api/models/post.ts index 8b9f7f5ef6..fe07dcb0b1 100644 --- a/src/api/models/post.ts +++ b/src/api/models/post.ts @@ -10,6 +10,7 @@ export function isValidText(text: string): boolean { export type IPost = { _id: mongo.ObjectID; + channel_id: mongo.ObjectID; created_at: Date; media_ids: mongo.ObjectID[]; reply_to_id: mongo.ObjectID; -- cgit v1.2.3-freya From 30a4e839a687bed7ed839e3c17f6781bb4b76499 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 31 Oct 2017 22:14:12 +0900 Subject: Fix indent --- src/api/endpoints/posts/create.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/api') diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index 42a55f850e..e0a02fa4a0 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -186,11 +186,11 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { repost: user.latest_post.repost_id ? user.latest_post.repost_id.toString() : null, media_ids: (user.latest_post.media_ids || []).map(id => id.toString()) }, { - text: text, - reply: inReplyToPost ? inReplyToPost._id.toString() : null, - repost: repost ? repost._id.toString() : null, - media_ids: (files || []).map(file => file._id.toString()) - })) { + text: text, + reply: inReplyToPost ? inReplyToPost._id.toString() : null, + repost: repost ? repost._id.toString() : null, + media_ids: (files || []).map(file => file._id.toString()) + })) { return rej('duplicate'); } } -- cgit v1.2.3-freya From 5efb52b9f563ae7d6b5383d054a6c21fee676b68 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 31 Oct 2017 22:35:31 +0900 Subject: wip --- locales/en.yml | 2 +- locales/ja.yml | 2 +- src/api/endpoints.ts | 12 ++++++++++-- src/api/endpoints/bbs/threads/create.ts | 29 ----------------------------- src/api/endpoints/channels/create.ts | 29 +++++++++++++++++++++++++++++ src/web/app/desktop/router.js | 5 +++++ src/web/app/desktop/tags/pages/channels.tag | 8 +++----- src/web/app/desktop/tags/ui.tag | 8 ++++---- 8 files changed, 53 insertions(+), 42 deletions(-) delete mode 100644 src/api/endpoints/bbs/threads/create.ts create mode 100644 src/api/endpoints/channels/create.ts (limited to 'src/api') diff --git a/locales/en.yml b/locales/en.yml index da532fc78a..5c7a1165ba 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -241,7 +241,7 @@ desktop: mk-ui-header-nav: home: "Home" messaging: "Messages" - bbs: "BBS" + channels: "Channels" info: "News" mk-ui-header-search: diff --git a/locales/ja.yml b/locales/ja.yml index 1ae94652b5..dd76a2b900 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -241,7 +241,7 @@ desktop: mk-ui-header-nav: home: "ホーム" messaging: "メッセージ" - bbs: "掲示板" + channels: "チャンネル" info: "お知らせ" mk-ui-header-search: diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 29a97bcb8a..26177b8775 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -474,8 +474,16 @@ const endpoints: Endpoint[] = [ name: 'messaging/messages/create', withCredential: true, kind: 'messaging-write' - } - + }, + { + name: 'channels/create', + withCredential: true, + limit: { + duration: ms('1hour'), + max: 3, + minInterval: ms('10seconds') + } + }, ]; export default endpoints; diff --git a/src/api/endpoints/bbs/threads/create.ts b/src/api/endpoints/bbs/threads/create.ts deleted file mode 100644 index d9b4d34a0c..0000000000 --- a/src/api/endpoints/bbs/threads/create.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Channel from '../../../models/channel'; -import serialize from '../../../serializers/channel'; - -/** - * Create a channel - * - * @param {any} params - * @param {any} user - * @return {Promise} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'title' parameter - const [title, titleErr] = $(params.title).string().range(1, 100).$; - if (titleErr) return rej('invalid title param'); - - // Create a channel - const channel = await Channel.insert({ - created_at: new Date(), - user_id: user._id, - title: title - }); - - // Response - res(await serialize(channel)); -}); diff --git a/src/api/endpoints/channels/create.ts b/src/api/endpoints/channels/create.ts new file mode 100644 index 0000000000..74b089dfc3 --- /dev/null +++ b/src/api/endpoints/channels/create.ts @@ -0,0 +1,29 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import Channel from '../../models/channel'; +import serialize from '../../serializers/channel'; + +/** + * Create a channel + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = async (params, user) => new Promise(async (res, rej) => { + // Get 'title' parameter + const [title, titleErr] = $(params.title).string().range(1, 100).$; + if (titleErr) return rej('invalid title param'); + + // Create a channel + const channel = await Channel.insert({ + created_at: new Date(), + user_id: user._id, + title: title + }); + + // Response + res(await serialize(channel)); +}); diff --git a/src/web/app/desktop/router.js b/src/web/app/desktop/router.js index afa8a2dce3..51738f3afa 100644 --- a/src/web/app/desktop/router.js +++ b/src/web/app/desktop/router.js @@ -9,6 +9,7 @@ let page = null; export default me => { route('/', index); route('/i>mentions', mentions); + route('/channel', channels); route('/post::post', post); route('/search::query', search); route('/:user', user.bind(null, 'home')); @@ -54,6 +55,10 @@ export default me => { mount(el); } + function channels() { + mount(document.createElement('mk-channels-page')); + } + function notFound() { mount(document.createElement('mk-not-found')); } diff --git a/src/web/app/desktop/tags/pages/channels.tag b/src/web/app/desktop/tags/pages/channels.tag index 9e47e52d25..03fae3c8d1 100644 --- a/src/web/app/desktop/tags/pages/channels.tag +++ b/src/web/app/desktop/tags/pages/channels.tag @@ -1,8 +1,7 @@
      -

      %i18n:desktop.tags.mk-bbs-page.title%

      - +
      + + diff --git a/src/web/app/desktop/tags/pages/channels.tag b/src/web/app/desktop/tags/pages/channels.tag index 03fae3c8d1..220f1ca50e 100644 --- a/src/web/app/desktop/tags/pages/channels.tag +++ b/src/web/app/desktop/tags/pages/channels.tag @@ -18,7 +18,7 @@ this.new = () => { const title = window.prompt('%i18n:desktop.tags.mk-channels-page.channel-title%'); - this.api('bbs/channels/create', { + this.api('channels/create', { title: title }).then(channel => { location.href = '/channel/' + channel.id; diff --git a/src/web/app/desktop/tags/pages/user.tag b/src/web/app/desktop/tags/pages/user.tag index 864fe22735..811ca5c0fd 100644 --- a/src/web/app/desktop/tags/pages/user.tag +++ b/src/web/app/desktop/tags/pages/user.tag @@ -16,7 +16,7 @@ this.refs.ui.refs.user.on('user-fetched', user => { Progress.set(0.5); - document.title = user.name + ' | Misskey' + document.title = user.name + ' | Misskey'; }); this.refs.ui.refs.user.on('loaded', () => { -- cgit v1.2.3-freya From 346c2959e058fa445ebb82e71eb37ef023ba6bd4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 1 Nov 2017 00:10:30 +0900 Subject: wip --- src/api/endpoints.ts | 3 + src/api/endpoints/channels/posts.ts | 79 +++++++++++++++++++++ src/web/app/common/scripts/channel-stream.js | 14 ++++ src/web/app/desktop/tags/pages/channel.tag | 87 ++++++++++++++++++++++++ src/web/app/desktop/tags/pages/drive-chooser.tag | 44 ++++++++++++ 5 files changed, 227 insertions(+) create mode 100644 src/api/endpoints/channels/posts.ts create mode 100644 src/web/app/common/scripts/channel-stream.js create mode 100644 src/web/app/desktop/tags/pages/drive-chooser.tag (limited to 'src/api') diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 45b83fc9e5..88c01d4e7f 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -487,6 +487,9 @@ const endpoints: Endpoint[] = [ { name: 'channels/show' }, + { + name: 'channels/posts' + }, ]; export default endpoints; diff --git a/src/api/endpoints/channels/posts.ts b/src/api/endpoints/channels/posts.ts new file mode 100644 index 0000000000..fa91fb93ee --- /dev/null +++ b/src/api/endpoints/channels/posts.ts @@ -0,0 +1,79 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import { default as Channel, IChannel } from '../../models/channel'; +import { default as Post, IPost } from '../../models/post'; +import serialize from '../../serializers/post'; + +/** + * Show a posts of a channel + * + * @param {any} params + * @param {any} user + * @return {Promise} + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + // Get 'limit' parameter + const [limit = 1000, limitErr] = $(params.limit).optional.number().range(1, 1000).$; + if (limitErr) return rej('invalid limit param'); + + // Get 'since_id' parameter + const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; + if (sinceIdErr) return rej('invalid since_id param'); + + // Get 'max_id' parameter + const [maxId, maxIdErr] = $(params.max_id).optional.id().$; + if (maxIdErr) return rej('invalid max_id param'); + + // Check if both of since_id and max_id is specified + if (sinceId && maxId) { + return rej('cannot set since_id and max_id'); + } + + // Get 'channel_id' parameter + const [channelId, channelIdErr] = $(params.channel_id).id().$; + if (channelIdErr) return rej('invalid channel_id param'); + + // Fetch channel + const channel: IChannel = await Channel.findOne({ + _id: channelId + }); + + if (channel === null) { + return rej('channel not found'); + } + + //#region Construct query + const sort = { + _id: -1 + }; + + const query = { + channel_id: channel._id + } as any; + + if (sinceId) { + sort._id = 1; + query._id = { + $gt: sinceId + }; + } else if (maxId) { + query._id = { + $lt: maxId + }; + } + //#endregion Construct query + + // Issue query + const posts = await Post + .find(query, { + limit: limit, + sort: sort + }); + + // Serialize + res(await Promise.all(posts.map(async (post) => + await serialize(post, user) + ))); +}); diff --git a/src/web/app/common/scripts/channel-stream.js b/src/web/app/common/scripts/channel-stream.js new file mode 100644 index 0000000000..38e7d91132 --- /dev/null +++ b/src/web/app/common/scripts/channel-stream.js @@ -0,0 +1,14 @@ +'use strict'; + +import Stream from './stream'; + +/** + * Channel stream connection + */ +class Connection extends Stream { + constructor() { + super('channel'); + } +} + +export default Connection; diff --git a/src/web/app/desktop/tags/pages/channel.tag b/src/web/app/desktop/tags/pages/channel.tag index 4fa172f99d..8a3034f40c 100644 --- a/src/web/app/desktop/tags/pages/channel.tag +++ b/src/web/app/desktop/tags/pages/channel.tag @@ -2,6 +2,8 @@

      { parent.channel.title }

      + +
      + + +
      + { post.user.name } +
      +
      + { post.text } +
      + + +
      + + +

      { reply.user.name }への返信: (or キャンセル)

      + + + + + +
      diff --git a/src/web/app/desktop/tags/pages/drive-chooser.tag b/src/web/app/desktop/tags/pages/drive-chooser.tag new file mode 100644 index 0000000000..49741ad40c --- /dev/null +++ b/src/web/app/desktop/tags/pages/drive-chooser.tag @@ -0,0 +1,44 @@ + + +
      + + + +
      + + + +
      -- cgit v1.2.3-freya From 71c3e11708dad327924bdcb95193d44c2b11a907 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 1 Nov 2017 01:38:19 +0900 Subject: wip --- src/api/endpoints/channels/create.ts | 3 ++- src/api/endpoints/posts/create.ts | 17 +++++++++++++++ src/api/models/channel.ts | 1 + src/api/serializers/post.ts | 8 ++++++- src/web/app/desktop/tags/pages/channel.tag | 35 +++++++++++++++++++++++++----- src/web/app/desktop/tags/timeline.tag | 4 ++++ src/web/app/mobile/tags/timeline.tag | 4 ++++ 7 files changed, 65 insertions(+), 7 deletions(-) (limited to 'src/api') diff --git a/src/api/endpoints/channels/create.ts b/src/api/endpoints/channels/create.ts index 74b089dfc3..e0c0e0192a 100644 --- a/src/api/endpoints/channels/create.ts +++ b/src/api/endpoints/channels/create.ts @@ -21,7 +21,8 @@ module.exports = async (params, user) => new Promise(async (res, rej) => { const channel = await Channel.insert({ created_at: new Date(), user_id: user._id, - title: title + title: title, + index: 0 }); // Response diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts index e0a02fa4a0..183cabf135 100644 --- a/src/api/endpoints/posts/create.ts +++ b/src/api/endpoints/posts/create.ts @@ -153,6 +153,16 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { if (repost && !isQuote) { return rej('チャンネル内部では引用ではないRepostをすることはできません'); } + } else { + // 返信対象の投稿がチャンネルへの投稿だったらダメ + if (inReplyToPost && inReplyToPost.channel_id != null) { + return rej('チャンネル外部からチャンネル内部の投稿に返信することはできません'); + } + + // Repost対象の投稿がチャンネルへの投稿だったらダメ + if (repost && repost.channel_id != null) { + return rej('チャンネル外部からチャンネル内部の投稿をRepostすることはできません'); + } } // Get 'poll' parameter @@ -199,6 +209,7 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { const post = await Post.insert({ created_at: new Date(), channel_id: channel ? channel._id : undefined, + index: channel ? channel.index + 1 : undefined, media_ids: files ? files.map(file => file._id) : undefined, reply_to_id: inReplyToPost ? inReplyToPost._id : undefined, repost_id: repost ? repost._id : undefined, @@ -217,6 +228,12 @@ module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { // ----------------------------------------------------------- // Post processes + Channel.update({ _id: channel._id }, { + $inc: { + index: 1 + } + }); + User.update({ _id: user._id }, { $set: { latest_post: post diff --git a/src/api/models/channel.ts b/src/api/models/channel.ts index 79edb71367..c80e84dbc8 100644 --- a/src/api/models/channel.ts +++ b/src/api/models/channel.ts @@ -10,4 +10,5 @@ export type IChannel = { created_at: Date; title: string; user_id: mongo.ObjectID; + index: number; }; diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts index df917a8595..7d40df2d6a 100644 --- a/src/api/serializers/post.ts +++ b/src/api/serializers/post.ts @@ -8,6 +8,7 @@ import Reaction from '../models/post-reaction'; import { IUser } from '../models/user'; import Vote from '../models/poll-vote'; import serializeApp from './app'; +import serializeChannel from './channel'; import serializeUser from './user'; import serializeDriveFile from './drive-file'; import parse from '../common/text'; @@ -76,8 +77,13 @@ const self = ( _post.app = await serializeApp(_post.app_id); } + // Populate channel + if (_post.channel_id) { + _post.channel = await serializeChannel(_post.channel_id); + } + + // Populate media if (_post.media_ids) { - // Populate media _post.media = await Promise.all(_post.media_ids.map(async fileId => await serializeDriveFile(fileId) )); diff --git a/src/web/app/desktop/tags/pages/channel.tag b/src/web/app/desktop/tags/pages/channel.tag index 8a3034f40c..ebd26f07b8 100644 --- a/src/web/app/desktop/tags/pages/channel.tag +++ b/src/web/app/desktop/tags/pages/channel.tag @@ -2,8 +2,9 @@

      { parent.channel.title }

      - - + +
      +
      @@ -57,9 +60,13 @@
      - { post.user.name } + { post.index }: + { post.user.name } + + ID:{ post.user.username }
      + >>{ post.reply_to.index } { post.text }
      -

      { reply.user.name }への返信: (or キャンセル)

      +

      >>{ reply.index } ({ reply.user.name }): [x]

      +
      + +
        +
      1. { name }
      2. +
      + + +
      diff --git a/src/web/app/ch/tags/index.js b/src/web/app/ch/tags/index.js new file mode 100644 index 0000000000..1e99ccd43e --- /dev/null +++ b/src/web/app/ch/tags/index.js @@ -0,0 +1,2 @@ +require('./index.tag'); +require('./channel.tag'); diff --git a/src/web/app/ch/tags/index.tag b/src/web/app/ch/tags/index.tag new file mode 100644 index 0000000000..1c0a037c2d --- /dev/null +++ b/src/web/app/ch/tags/index.tag @@ -0,0 +1,24 @@ + + + + + diff --git a/src/web/app/common/scripts/channel-stream.js b/src/web/app/common/scripts/channel-stream.js index 38e7d91132..17944dbe45 100644 --- a/src/web/app/common/scripts/channel-stream.js +++ b/src/web/app/common/scripts/channel-stream.js @@ -6,8 +6,10 @@ import Stream from './stream'; * Channel stream connection */ class Connection extends Stream { - constructor() { - super('channel'); + constructor(channelId) { + super('channel', { + channel: channelId + }); } } diff --git a/src/web/app/common/scripts/config.js b/src/web/app/common/scripts/config.js index 75a7abba29..c5015622f0 100644 --- a/src/web/app/common/scripts/config.js +++ b/src/web/app/common/scripts/config.js @@ -6,6 +6,7 @@ const host = isRoot ? Url.host : Url.host.substring(Url.host.indexOf('.') + 1, U const scheme = Url.protocol; const url = `${scheme}//${host}`; const apiUrl = `${scheme}//api.${host}`; +const chUrl = `${scheme}//ch.${host}`; const devUrl = `${scheme}//dev.${host}`; const aboutUrl = `${scheme}//about.${host}`; const statsUrl = `${scheme}//stats.${host}`; @@ -16,6 +17,7 @@ export default { scheme, url, apiUrl, + chUrl, devUrl, aboutUrl, statsUrl, diff --git a/src/web/app/desktop/router.js b/src/web/app/desktop/router.js index df67bb7b7c..977e3fa9a6 100644 --- a/src/web/app/desktop/router.js +++ b/src/web/app/desktop/router.js @@ -10,8 +10,6 @@ export default me => { route('/', index); route('/selectdrive', selectDrive); route('/i>mentions', mentions); - route('/channel', channels); - route('/channel/:channel', channel); route('/post::post', post); route('/search::query', search); route('/:user', user.bind(null, 'home')); @@ -57,16 +55,6 @@ export default me => { mount(el); } - function channel(ctx) { - const el = document.createElement('mk-channel-page'); - el.setAttribute('id', ctx.params.channel); - mount(el); - } - - function channels() { - mount(document.createElement('mk-channels-page')); - } - function selectDrive() { mount(document.createElement('mk-selectdrive-page')); } diff --git a/src/web/app/desktop/tags/index.js b/src/web/app/desktop/tags/index.js index 0b92d8c236..37fdfe37e4 100644 --- a/src/web/app/desktop/tags/index.js +++ b/src/web/app/desktop/tags/index.js @@ -61,8 +61,6 @@ require('./pages/user.tag'); require('./pages/post.tag'); require('./pages/search.tag'); require('./pages/not-found.tag'); -require('./pages/channel.tag'); -require('./pages/channels.tag'); require('./pages/selectdrive.tag'); require('./autocomplete-suggestion.tag'); require('./progress-dialog.tag'); diff --git a/src/web/app/desktop/tags/pages/channel.tag b/src/web/app/desktop/tags/pages/channel.tag deleted file mode 100644 index a14c0648c4..0000000000 --- a/src/web/app/desktop/tags/pages/channel.tag +++ /dev/null @@ -1,184 +0,0 @@ - - -
      -

      { parent.channel.title }

      - - - -
      - -
      -
      - - -
      - - -
      - { post.index }: - { post.user.name } - - ID:{ post.user.username } -
      -
      - >>{ post.reply_to.index } - { post.text } -
      - - { - -
      -
      - - -
      - - -

      >>{ reply.index } ({ reply.user.name }): [x]

      - - -
      - -
        -
      1. { name }
      2. -
      - - -
      diff --git a/src/web/app/desktop/tags/pages/channels.tag b/src/web/app/desktop/tags/pages/channels.tag deleted file mode 100644 index 220f1ca50e..0000000000 --- a/src/web/app/desktop/tags/pages/channels.tag +++ /dev/null @@ -1,28 +0,0 @@ - - -
      - -
      -
      - - -
      diff --git a/src/web/app/desktop/tags/timeline.tag b/src/web/app/desktop/tags/timeline.tag index 17b2c66dc8..64b64f902f 100644 --- a/src/web/app/desktop/tags/timeline.tag +++ b/src/web/app/desktop/tags/timeline.tag @@ -112,7 +112,7 @@
      -

      { p.channel.title }:

      +

      { p.channel.title }:

      diff --git a/src/web/app/desktop/tags/ui.tag b/src/web/app/desktop/tags/ui.tag index 7527358dce..3123c34f4f 100644 --- a/src/web/app/desktop/tags/ui.tag +++ b/src/web/app/desktop/tags/ui.tag @@ -335,10 +335,10 @@
    • -
    • - +
    • + -

      %i18n:desktop.tags.mk-ui-header-nav.channels%

      +

      %i18n:desktop.tags.mk-ui-header-nav.ch%

    • diff --git a/src/web/app/mobile/tags/timeline.tag b/src/web/app/mobile/tags/timeline.tag index b26a5cb108..ad18521df6 100644 --- a/src/web/app/mobile/tags/timeline.tag +++ b/src/web/app/mobile/tags/timeline.tag @@ -164,7 +164,7 @@
      -

      { p.channel.title }:

      +

      { p.channel.title }:

      diff --git a/src/web/app/mobile/tags/ui.tag b/src/web/app/mobile/tags/ui.tag index fb8cbcdbd2..b2d96f6b8b 100644 --- a/src/web/app/mobile/tags/ui.tag +++ b/src/web/app/mobile/tags/ui.tag @@ -231,10 +231,11 @@
    • %i18n:mobile.tags.mk-ui-nav.messaging%
    • %i18n:mobile.tags.mk-ui-nav.settings%
    • diff --git a/webpack/webpack.config.ts b/webpack/webpack.config.ts index 5199285d55..066df18157 100644 --- a/webpack/webpack.config.ts +++ b/webpack/webpack.config.ts @@ -16,6 +16,7 @@ module.exports = langs.map(([lang, locale]) => { const entry = { desktop: './src/web/app/desktop/script.js', mobile: './src/web/app/mobile/script.js', + ch: './src/web/app/ch/script.js', stats: './src/web/app/stats/script.js', status: './src/web/app/status/script.js', dev: './src/web/app/dev/script.js', -- cgit v1.2.3-freya From 42be937fcb6f02037ff4024a2fb1cf463c50ce0c Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 1 Nov 2017 04:11:56 +0900 Subject: wip --- src/api/endpoints.ts | 3 +++ src/api/endpoints/channels.ts | 59 +++++++++++++++++++++++++++++++++++++++++ src/web/app/ch/tags/channel.tag | 7 ++--- src/web/app/ch/tags/index.tag | 13 +++++++-- 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/api/endpoints/channels.ts (limited to 'src/api') diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts index 88c01d4e7f..c4dacad857 100644 --- a/src/api/endpoints.ts +++ b/src/api/endpoints.ts @@ -490,6 +490,9 @@ const endpoints: Endpoint[] = [ { name: 'channels/posts' }, + { + name: 'channels' + }, ]; export default endpoints; diff --git a/src/api/endpoints/channels.ts b/src/api/endpoints/channels.ts new file mode 100644 index 0000000000..e10c943896 --- /dev/null +++ b/src/api/endpoints/channels.ts @@ -0,0 +1,59 @@ +/** + * Module dependencies + */ +import $ from 'cafy'; +import Channel from '../models/channel'; +import serialize from '../serializers/channel'; + +/** + * Get all channels + * + * @param {any} params + * @param {any} me + * @return {Promise} + */ +module.exports = (params, me) => new Promise(async (res, rej) => { + // Get 'limit' parameter + const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; + if (limitErr) return rej('invalid limit param'); + + // Get 'since_id' parameter + const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; + if (sinceIdErr) return rej('invalid since_id param'); + + // Get 'max_id' parameter + const [maxId, maxIdErr] = $(params.max_id).optional.id().$; + if (maxIdErr) return rej('invalid max_id param'); + + // Check if both of since_id and max_id is specified + if (sinceId && maxId) { + return rej('cannot set since_id and max_id'); + } + + // Construct query + const sort = { + _id: -1 + }; + const query = {} as any; + if (sinceId) { + sort._id = 1; + query._id = { + $gt: sinceId + }; + } else if (maxId) { + query._id = { + $lt: maxId + }; + } + + // Issue query + const channels = await Channel + .find(query, { + limit: limit, + sort: sort + }); + + // Serialize + res(await Promise.all(channels.map(async channel => + await serialize(channel, me)))); +}); diff --git a/src/web/app/ch/tags/channel.tag b/src/web/app/ch/tags/channel.tag index 43a1f851f8..12a6b5a3b9 100644 --- a/src/web/app/ch/tags/channel.tag +++ b/src/web/app/ch/tags/channel.tag @@ -1,4 +1,6 @@ +
      Misskey Channels
      +

      { channel.title }

      読み込み中

      @@ -21,10 +23,9 @@ diff --git a/src/web/app/ch/tags/index.tag b/src/web/app/ch/tags/index.tag index 1c0a037c2d..a64ddb6ccd 100644 --- a/src/web/app/ch/tags/index.tag +++ b/src/web/app/ch/tags/index.tag @@ -1,5 +1,9 @@ - + +
      +