From 32afe77a269f414965373e3c53044c4a94cfeded Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 16 Sep 2018 22:48:57 +0900 Subject: 自分宛ての投稿をタイムラインで見れるように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/endpoints/notes/mentions.ts | 59 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 27 deletions(-) (limited to 'src/server/api/endpoints/notes/mentions.ts') diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index a7fb14d8a9..3b2e262e4f 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -3,6 +3,7 @@ import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { desc: { @@ -10,42 +11,48 @@ export const meta = { 'en-US': 'Get mentions of myself.' }, - requireCredential: true -}; + requireCredential: true, -export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'following' parameter - const [following = false, followingError] = - $.bool.optional.get(params.following); - if (followingError) return rej('invalid following param'); + params: { + following: $.bool.optional.note({ + default: false + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10 + }), - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + sinceId: $.type(ID).optional.note({ + }), - // Get 'sinceId' parameter - const [sinceId, sinceIdErr] = $.type(ID).optional.get(params.sinceId); - if (sinceIdErr) return rej('invalid sinceId param'); + untilId: $.type(ID).optional.note({ + }), + } +}; - // Get 'untilId' parameter - const [untilId, untilIdErr] = $.type(ID).optional.get(params.untilId); - if (untilIdErr) return rej('invalid untilId param'); +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; // Check if both of sinceId and untilId is specified - if (sinceId && untilId) { + if (ps.sinceId && ps.untilId) { return rej('cannot set sinceId and untilId'); } // Construct query const query = { - mentions: user._id + $or: [{ + mentions: user._id + }, { + visibleUserIds: user._id + }] } as any; const sort = { _id: -1 }; - if (following) { + if (ps.following) { const followingIds = await getFriendIds(user._id); query.userId = { @@ -53,26 +60,24 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }; } - if (sinceId) { + if (ps.sinceId) { sort._id = 1; query._id = { - $gt: sinceId + $gt: ps.sinceId }; - } else if (untilId) { + } else if (ps.untilId) { query._id = { - $lt: untilId + $lt: ps.untilId }; } // Issue query const mentions = await Note .find(query, { - limit: limit, + limit: ps.limit, sort: sort }); // Serialize - res(await Promise.all(mentions.map(async mention => - await pack(mention, user) - ))); + res(await Promise.all(mentions.map(mention => pack(mention, user)))); }); -- cgit v1.2.3-freya From ab83e08bc7deb244d35e2315abead473d536d2c3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 18 Sep 2018 02:14:12 +0900 Subject: メッセージタイムラインを追加 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 3 + .../app/desktop/views/components/timeline.core.vue | 148 ++++++++++++--------- .../app/desktop/views/components/timeline.vue | 18 ++- .../desktop/views/pages/deck/deck.column-core.vue | 5 +- .../views/pages/deck/deck.direct-column.vue | 38 ++++++ .../app/desktop/views/pages/deck/deck.direct.vue | 97 ++++++++++++++ src/client/app/desktop/views/pages/deck/deck.vue | 9 ++ .../app/mobile/views/pages/home.timeline.vue | 147 +++++++++++--------- src/client/app/mobile/views/pages/home.vue | 49 +++++-- src/server/api/endpoints/notes/mentions.ts | 7 + src/services/note/create.ts | 22 ++- 11 files changed, 393 insertions(+), 150 deletions(-) create mode 100644 src/client/app/desktop/views/pages/deck/deck.direct-column.vue create mode 100644 src/client/app/desktop/views/pages/deck/deck.direct.vue (limited to 'src/server/api/endpoints/notes/mentions.ts') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index a3b2bd88e7..11dd76d0e6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -169,6 +169,7 @@ common: hashtag: "ハッシュタグ" global: "グローバル" mentions: "あなた宛て" + direct: "ダイレクト投稿" notifications: "通知" list: "リスト" swap-left: "左に移動" @@ -916,6 +917,7 @@ desktop/views/components/timeline.vue: hybrid: "ソーシャル" global: "グローバル" mentions: "あなた宛て" + messages: "メッセージ" list: "リスト" hashtag: "ハッシュタグ" add-tag-timeline: "ハッシュタグを追加" @@ -1322,6 +1324,7 @@ mobile/views/pages/home.vue: hybrid: "ソーシャル" global: "グローバル" mentions: "あなた宛て" + messages: "メッセージ" mobile/views/pages/tag.vue: no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。" diff --git a/src/client/app/desktop/views/components/timeline.core.vue b/src/client/app/desktop/views/components/timeline.core.vue index d2176dee87..c8aa36f171 100644 --- a/src/client/app/desktop/views/components/timeline.core.vue +++ b/src/client/app/desktop/views/components/timeline.core.vue @@ -38,7 +38,14 @@ export default Vue.extend({ streamManager: null, connection: null, connectionId: null, - date: null + date: null, + baseQuery: { + includeMyRenotes: this.$store.state.settings.showMyRenotes, + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes + }, + query: {}, + endpoint: null }; }, @@ -47,53 +54,102 @@ export default Vue.extend({ return this.$store.state.i.followingCount == 0; }, - endpoint(): string { - switch (this.src) { - case 'home': return 'notes/timeline'; - case 'local': return 'notes/local-timeline'; - case 'hybrid': return 'notes/hybrid-timeline'; - case 'global': return 'notes/global-timeline'; - case 'mentions': return 'notes/mentions'; - case 'tag': return 'notes/search_by_tag'; - } - }, - canFetchMore(): boolean { return !this.moreFetching && !this.fetching && this.existMore; } }, mounted() { + const prepend = note => { + (this.$refs.timeline as any).prepend(note); + }; + if (this.src == 'tag') { + this.endpoint = 'notes/search_by_tag'; + this.query = { + query: this.tagTl.query + }; this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.connection.close(); + }); } else if (this.src == 'home') { + this.endpoint = 'notes/timeline'; + const onChangeFollowing = () => { + this.fetch(); + }; this.streamManager = (this as any).os.stream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); - this.connection.on('follow', this.onChangeFollowing); - this.connection.on('unfollow', this.onChangeFollowing); + this.connection.on('note', prepend); + this.connection.on('follow', onChangeFollowing); + this.connection.on('unfollow', onChangeFollowing); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.connection.off('follow', onChangeFollowing); + this.connection.off('unfollow', onChangeFollowing); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'local') { + this.endpoint = 'notes/local-timeline'; this.streamManager = (this as any).os.streams.localTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'hybrid') { + this.endpoint = 'notes/hybrid-timeline'; this.streamManager = (this as any).os.streams.hybridTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'global') { + this.endpoint = 'notes/global-timeline'; this.streamManager = (this as any).os.streams.globalTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'mentions') { + this.endpoint = 'notes/mentions'; + this.streamManager = (this as any).os.stream; + this.connection = this.streamManager.getConnection(); + this.connectionId = this.streamManager.use(); + this.connection.on('mention', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('mention', prepend); + this.streamManager.dispose(this.connectionId); + }); + } else if (this.src == 'messages') { + this.endpoint = 'notes/mentions'; + this.query = { + visibility: 'specified' + }; + const onNote = note => { + if (note.visibility == 'specified') { + prepend(note); + } + }; this.streamManager = (this as any).os.stream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('mention', this.onNote); + this.connection.on('mention', onNote); + this.$once('beforeDestroy', () => { + this.connection.off('mention', onNote); + this.streamManager.dispose(this.connectionId); + }); } document.addEventListener('keydown', this.onKeydown); @@ -102,28 +158,7 @@ export default Vue.extend({ }, beforeDestroy() { - if (this.src == 'tag') { - this.connection.off('note', this.onNote); - this.connection.close(); - } else if (this.src == 'home') { - this.connection.off('note', this.onNote); - this.connection.off('follow', this.onChangeFollowing); - this.connection.off('unfollow', this.onChangeFollowing); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'local') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'hybrid') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'global') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'mentions') { - this.connection.off('mention', this.onNote); - this.streamManager.dispose(this.connectionId); - } - + this.$emit('beforeDestroy'); document.removeEventListener('keydown', this.onKeydown); }, @@ -132,14 +167,10 @@ export default Vue.extend({ this.fetching = true; (this.$refs.timeline as any).init(() => new Promise((res, rej) => { - (this as any).api(this.endpoint, { + (this as any).api(this.endpoint, Object.assign({ limit: fetchLimit + 1, - untilDate: this.date ? this.date.getTime() : undefined, - includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.settings.showLocalRenotes, - query: this.tagTl ? this.tagTl.query : undefined - }).then(notes => { + untilDate: this.date ? this.date.getTime() : undefined + }, this.baseQuery, this.query)).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); this.existMore = true; @@ -156,14 +187,10 @@ export default Vue.extend({ this.moreFetching = true; - const promise = (this as any).api(this.endpoint, { + const promise = (this as any).api(this.endpoint, Object.assign({ limit: fetchLimit + 1, - untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.settings.showLocalRenotes, - query: this.tagTl ? this.tagTl.query : undefined - }); + untilId: (this.$refs.timeline as any).tail().id + }, this.baseQuery, this.query)); promise.then(notes => { if (notes.length == fetchLimit + 1) { @@ -178,15 +205,6 @@ export default Vue.extend({ return promise; }, - onNote(note) { - // Prepend a note - (this.$refs.timeline as any).prepend(note); - }, - - onChangeFollowing() { - this.fetch(); - }, - focus() { (this.$refs.timeline as any).focus(); }, diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue index 2dc84004df..ccc35f95ff 100644 --- a/src/client/app/desktop/views/components/timeline.vue +++ b/src/client/app/desktop/views/components/timeline.vue @@ -5,10 +5,11 @@ %fa:R comments% %i18n:@local% %fa:share-alt% %i18n:@hybrid% %fa:globe% %i18n:@global% - %fa:at% %i18n:@mentions% %fa:hashtag% {{ tagTl.title }} %fa:list% {{ list.title }}
+ +
@@ -18,6 +19,7 @@ + @@ -202,6 +204,20 @@ root(isDark) &:active color isDark ? #b2c1d5 : #999 + &[data-active] + color $theme-color + cursor default + + &:before + content "" + display block + position absolute + bottom 0 + left 0 + width 100% + height 2px + background $theme-color + > span display inline-block padding 0 10px diff --git a/src/client/app/desktop/views/pages/deck/deck.column-core.vue b/src/client/app/desktop/views/pages/deck/deck.column-core.vue index a320f697b3..e1490cb0e4 100644 --- a/src/client/app/desktop/views/pages/deck/deck.column-core.vue +++ b/src/client/app/desktop/views/pages/deck/deck.column-core.vue @@ -8,6 +8,7 @@ + diff --git a/src/client/app/desktop/views/pages/deck/deck.direct.vue b/src/client/app/desktop/views/pages/deck/deck.direct.vue new file mode 100644 index 0000000000..ec9e6b9c3d --- /dev/null +++ b/src/client/app/desktop/views/pages/deck/deck.direct.vue @@ -0,0 +1,97 @@ + + + diff --git a/src/client/app/desktop/views/pages/deck/deck.vue b/src/client/app/desktop/views/pages/deck/deck.vue index aafe9a45d3..e5aeba251a 100644 --- a/src/client/app/desktop/views/pages/deck/deck.vue +++ b/src/client/app/desktop/views/pages/deck/deck.vue @@ -147,6 +147,15 @@ export default Vue.extend({ type: 'mentions' }); } + }, { + icon: '%fa:envelope R%', + text: '%i18n:common.deck.direct%', + action: () => { + this.$store.dispatch('settings/addDeckColumn', { + id: uuid(), + type: 'direct' + }); + } }, { icon: '%fa:list%', text: '%i18n:common.deck.list%', diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue index fecb2384ba..225abcff6b 100644 --- a/src/client/app/mobile/views/pages/home.timeline.vue +++ b/src/client/app/mobile/views/pages/home.timeline.vue @@ -37,7 +37,14 @@ export default Vue.extend({ connection: null, connectionId: null, unreadCount: 0, - date: null + date: null, + baseQuery: { + includeMyRenotes: this.$store.state.settings.showMyRenotes, + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes + }, + query: {}, + endpoint: null }; }, @@ -46,80 +53,109 @@ export default Vue.extend({ return this.$store.state.i.followingCount == 0; }, - endpoint(): string { - switch (this.src) { - case 'home': return 'notes/timeline'; - case 'local': return 'notes/local-timeline'; - case 'hybrid': return 'notes/hybrid-timeline'; - case 'global': return 'notes/global-timeline'; - case 'mentions': return 'notes/mentions'; - case 'tag': return 'notes/search_by_tag'; - } - }, - canFetchMore(): boolean { return !this.moreFetching && !this.fetching && this.existMore; } }, mounted() { + const prepend = note => { + (this.$refs.timeline as any).prepend(note); + }; + if (this.src == 'tag') { + this.endpoint = 'notes/search_by_tag'; + this.query = { + query: this.tagTl.query + }; this.connection = new HashtagStream((this as any).os, this.$store.state.i, this.tagTl.query); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.connection.close(); + }); } else if (this.src == 'home') { + this.endpoint = 'notes/timeline'; + const onChangeFollowing = () => { + this.fetch(); + }; this.streamManager = (this as any).os.stream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); - this.connection.on('follow', this.onChangeFollowing); - this.connection.on('unfollow', this.onChangeFollowing); + this.connection.on('note', prepend); + this.connection.on('follow', onChangeFollowing); + this.connection.on('unfollow', onChangeFollowing); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.connection.off('follow', onChangeFollowing); + this.connection.off('unfollow', onChangeFollowing); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'local') { + this.endpoint = 'notes/local-timeline'; this.streamManager = (this as any).os.streams.localTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'hybrid') { + this.endpoint = 'notes/hybrid-timeline'; this.streamManager = (this as any).os.streams.hybridTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'global') { + this.endpoint = 'notes/global-timeline'; this.streamManager = (this as any).os.streams.globalTimelineStream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('note', this.onNote); + this.connection.on('note', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('note', prepend); + this.streamManager.dispose(this.connectionId); + }); } else if (this.src == 'mentions') { + this.endpoint = 'notes/mentions'; + this.streamManager = (this as any).os.stream; + this.connection = this.streamManager.getConnection(); + this.connectionId = this.streamManager.use(); + this.connection.on('mention', prepend); + this.$once('beforeDestroy', () => { + this.connection.off('mention', prepend); + this.streamManager.dispose(this.connectionId); + }); + } else if (this.src == 'messages') { + this.endpoint = 'notes/mentions'; + this.query = { + visibility: 'specified' + }; + const onNote = note => { + if (note.visibility == 'specified') { + prepend(note); + } + }; this.streamManager = (this as any).os.stream; this.connection = this.streamManager.getConnection(); this.connectionId = this.streamManager.use(); - this.connection.on('mention', this.onNote); + this.connection.on('mention', onNote); + this.$once('beforeDestroy', () => { + this.connection.off('mention', onNote); + this.streamManager.dispose(this.connectionId); + }); } this.fetch(); }, beforeDestroy() { - if (this.src == 'tag') { - this.connection.off('note', this.onNote); - this.connection.close(); - } else if (this.src == 'home') { - this.connection.off('note', this.onNote); - this.connection.off('follow', this.onChangeFollowing); - this.connection.off('unfollow', this.onChangeFollowing); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'local') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'hybrid') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'global') { - this.connection.off('note', this.onNote); - this.streamManager.dispose(this.connectionId); - } else if (this.src == 'mentions') { - this.connection.off('mention', this.onNote); - this.streamManager.dispose(this.connectionId); - } + this.$emit('beforeDestroy'); }, methods: { @@ -127,14 +163,10 @@ export default Vue.extend({ this.fetching = true; (this.$refs.timeline as any).init(() => new Promise((res, rej) => { - (this as any).api(this.endpoint, { + (this as any).api(this.endpoint, Object.assign({ limit: fetchLimit + 1, - untilDate: this.date ? this.date.getTime() : undefined, - includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.settings.showLocalRenotes, - query: this.tagTl ? this.tagTl.query : undefined - }).then(notes => { + untilDate: this.date ? this.date.getTime() : undefined + }, this.baseQuery, this.query)).then(notes => { if (notes.length == fetchLimit + 1) { notes.pop(); this.existMore = true; @@ -151,14 +183,10 @@ export default Vue.extend({ this.moreFetching = true; - const promise = (this as any).api(this.endpoint, { + const promise = (this as any).api(this.endpoint, Object.assign({ limit: fetchLimit + 1, - untilId: (this.$refs.timeline as any).tail().id, - includeMyRenotes: this.$store.state.settings.showMyRenotes, - includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, - includeLocalRenotes: this.$store.state.settings.showLocalRenotes, - query: this.tagTl ? this.tagTl.query : undefined - }); + untilId: (this.$refs.timeline as any).tail().id + }, this.baseQuery, this.query)); promise.then(notes => { if (notes.length == fetchLimit + 1) { @@ -173,15 +201,6 @@ export default Vue.extend({ return promise; }, - onNote(note) { - // Prepend a note - (this.$refs.timeline as any).prepend(note); - }, - - onChangeFollowing() { - this.fetch(); - }, - focus() { (this.$refs.timeline as any).focus(); }, diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue index 3ec2f16b75..e61916fe18 100644 --- a/src/client/app/mobile/views/pages/home.vue +++ b/src/client/app/mobile/views/pages/home.vue @@ -7,6 +7,7 @@ %fa:share-alt%%i18n:@hybrid% %fa:globe%%i18n:@global% %fa:at%%i18n:@mentions% + %fa:envelope R%%i18n:@messages% %fa:list%{{ list.title }} %fa:hashtag%{{ tagTl.title }} @@ -23,16 +24,21 @@
@@ -150,6 +157,26 @@ export default Vue.extend({ root(isDark) > .nav + > .pointer + position fixed + z-index 10002 + top 56px + left 0 + right 0 + + $size = 16px + + &:after + content "" + display block + position absolute + top -($size * 2) + left s('calc(50% - %s)', $size) + border-top solid $size transparent + border-left solid $size transparent + border-right solid $size transparent + border-bottom solid $size isDark ? #272f3a : #fff + > .bg position fixed z-index 10000 @@ -166,28 +193,22 @@ root(isDark) left 0 right 0 width 300px + max-height calc(100% - 70px) margin 0 auto + overflow auto + -webkit-overflow-scrolling touch background isDark ? #272f3a : #fff border-radius 8px box-shadow 0 0 16px rgba(#000, 0.1) - $balloon-size = 16px - - &:after - content "" - display block - position absolute - top -($balloon-size * 2) + 1.5px - left s('calc(50% - %s)', $balloon-size) - border-top solid $balloon-size transparent - border-left solid $balloon-size transparent - border-right solid $balloon-size transparent - border-bottom solid $balloon-size isDark ? #272f3a : #fff - > div padding 8px 0 - > * + > .hr + margin 8px 0 + border-top solid 1px isDark ? rgba(#000, 0.3) : rgba(#000, 0.1) + + > *:not(.hr) display block padding 8px 16px color isDark ? #cdd0d8 : #666 diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 3b2e262e4f..8675a9f562 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -27,6 +27,9 @@ export const meta = { untilId: $.type(ID).optional.note({ }), + + visibility: $.str.optional.note({ + }), } }; @@ -52,6 +55,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = _id: -1 }; + if (ps.visibility) { + query.visibility = ps.visibility; + } + if (ps.following) { const followingIds = await getFriendIds(user._id); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 7daf83b294..7c1e71dcb3 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -142,6 +142,14 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< mentionedUsers.push(await User.findOne({ _id: data.reply.userId })); } + if (data.visibility == 'specified') { + data.visibleUsers.forEach(u => { + if (!mentionedUsers.some(x => x._id.equals(u._id))) { + mentionedUsers.push(u); + } + }); + } + const note = await insertNote(user, data, tags, mentionedUsers); res(note); @@ -188,7 +196,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise< const nm = new NotificationManager(user, note); const nmRelatedPromises = []; - createMentionedEvents(mentionedUsers, noteObj, nm); + createMentionedEvents(mentionedUsers, note, nm); const noteActivity = await renderActivity(data, note); @@ -318,7 +326,7 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren if (['public', 'home', 'followers'].includes(note.visibility)) { // フォロワーに配信 - publishToFollowers(note, noteObj, user, noteActivity); + publishToFollowers(note, user, noteActivity); } // リストに配信 @@ -456,7 +464,7 @@ async function publishToUserLists(note: INote, noteObj: any) { }); } -async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteActivity: any) { +async function publishToFollowers(note: INote, user: IUser, noteActivity: any) { const detailPackedNote = await pack(note, null, { detail: true, skipHide: true @@ -505,9 +513,13 @@ function deliverNoteToMentionedRemoteUsers(mentionedUsers: IUser[], user: ILocal }); } -function createMentionedEvents(mentionedUsers: IUser[], noteObj: any, nm: NotificationManager) { +function createMentionedEvents(mentionedUsers: IUser[], note: INote, nm: NotificationManager) { mentionedUsers.filter(u => isLocalUser(u)).forEach(async (u) => { - publishUserStream(u._id, 'mention', noteObj); + const detailPackedNote = await pack(note, u, { + detail: true + }); + + publishUserStream(u._id, 'mention', detailPackedNote); // Create notification nm.push(u._id, 'mention'); -- cgit v1.2.3-freya From 49e82adc6c70c19de0897fc7768fd5e22a8a89f3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 23 Sep 2018 16:05:46 +0900 Subject: mentionsを読み込むときも既読にするように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/endpoints/notes/mentions.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/server/api/endpoints/notes/mentions.ts') diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 8675a9f562..d8d05b78ec 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -4,6 +4,7 @@ import { getFriendIds } from '../../common/get-friends'; import { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; +import read from '../../../../services/note/read'; export const meta = { desc: { @@ -85,6 +86,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = sort: sort }); + mentions.forEach(note => read(user._id, note._id)); + // Serialize res(await Promise.all(mentions.map(mention => pack(mention, user)))); }); -- cgit v1.2.3-freya From baad11288af8ae1b950ecb5a62e23a70bee7d51d Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 4 Oct 2018 00:39:11 +0900 Subject: ドキュメントが見つからなくてもエラーにせずnullを返すように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/note.ts | 17 ++++++++++++++++- src/models/user.ts | 14 ++++++++------ src/server/api/endpoints/notes.ts | 4 ++-- src/server/api/endpoints/notes/conversation.ts | 4 ++-- src/server/api/endpoints/notes/global-timeline.ts | 4 ++-- src/server/api/endpoints/notes/hybrid-timeline.ts | 4 ++-- src/server/api/endpoints/notes/local-timeline.ts | 4 ++-- src/server/api/endpoints/notes/mentions.ts | 4 ++-- src/server/api/endpoints/notes/replies.ts | 4 ++-- src/server/api/endpoints/notes/reposts.ts | 5 ++--- src/server/api/endpoints/notes/search.ts | 4 ++-- src/server/api/endpoints/notes/search_by_tag.ts | 4 ++-- src/server/api/endpoints/notes/timeline.ts | 4 ++-- src/server/api/endpoints/notes/user-list-timeline.ts | 4 ++-- src/server/api/endpoints/users/notes.ts | 4 ++-- 15 files changed, 50 insertions(+), 34 deletions(-) (limited to 'src/server/api/endpoints/notes/mentions.ts') diff --git a/src/models/note.ts b/src/models/note.ts index 67ee525c31..75518d709f 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -226,6 +226,17 @@ export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { } }; +export const packMany = async ( + notes: (string | mongo.ObjectID | INote)[], + me?: string | mongo.ObjectID | IUser, + options?: { + detail?: boolean; + skipHide?: boolean; + } +) => { + return (await Promise.all(notes.map(n => pack(n, me, options)))).filter(x => x != null); +}; + /** * Pack a note for API response * @@ -271,7 +282,11 @@ export const pack = async ( _note = deepcopy(note); } - if (!_note) throw `invalid note arg ${note}`; + // 投稿がデータベース上に見つからなかったとき + if (_note == null) { + console.warn(`note not found on database: ${note}`); + return null; + } const id = _note._id; diff --git a/src/models/user.ts b/src/models/user.ts index d2124bda74..3e8aefc4b1 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -3,7 +3,7 @@ const deepcopy = require('deepcopy'); const sequential = require('promise-sequential'); import rap from '@prezzemolo/rap'; import db from '../db/mongodb'; -import Note, { pack as packNote, deleteNote } from './note'; +import Note, { packMany as packNoteMany, deleteNote } from './note'; import Following, { deleteFollowing } from './following'; import Mute, { deleteMute } from './mute'; import { getFriendIds } from '../server/api/common/get-friends'; @@ -361,9 +361,11 @@ export const pack = ( _user = deepcopy(user); } - // TODO: ここでエラーにするのではなくダミーのユーザーデータを返す - // SEE: https://github.com/syuilo/misskey/issues/1432 - if (!_user) return reject('invalid user arg.'); + // ユーザーがデータベース上に見つからなかったとき + if (_user == null) { + console.warn(`user not found on database: ${user}`); + return null; + } // Me const meId: mongo.ObjectID = me @@ -468,9 +470,9 @@ export const pack = ( if (opts.detail) { if (_user.pinnedNoteIds) { // Populate pinned notes - _user.pinnedNotes = Promise.all(_user.pinnedNoteIds.map((id: mongo.ObjectId) => packNote(id, meId, { + _user.pinnedNotes = packNoteMany(_user.pinnedNoteIds, meId, { detail: true - }))); + }); } if (meId && !meId.equals(_user.id)) { diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 5fa58d19de..d65710d33f 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../misc/cafy-id'; -import Note, { pack } from '../../../models/note'; +import Note, { packMany } from '../../../models/note'; import getParams from '../get-params'; export const meta = { @@ -116,5 +116,5 @@ export default (params: any) => new Promise(async (res, rej) => { }); // Serialize - res(await Promise.all(notes.map(note => pack(note)))); + res(await packMany(notes)); }); diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 2782d14155..0c23f9e5fc 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack, INote } from '../../../../models/note'; +import Note, { packMany, INote } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -52,5 +52,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = } // Serialize - res(await Promise.all(conversation.map(note => pack(note, user)))); + res(await packMany(conversation, user)); }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 5d93cd78ec..8362143bb2 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import { countIf } from '../../../../prelude/array'; @@ -113,5 +113,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 5e39d8c78a..14b4432b33 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import { countIf } from '../../../../prelude/array'; @@ -240,5 +240,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 505454a82f..8ab07d8ea7 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import { countIf } from '../../../../prelude/array'; @@ -141,5 +141,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index d8d05b78ec..592a94263d 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import { getFriendIds } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import read from '../../../../services/note/read'; @@ -89,5 +89,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = mentions.forEach(note => read(user._id, note._id)); // Serialize - res(await Promise.all(mentions.map(mention => pack(mention, user)))); + res(await packMany(mentions, user)); }); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 44c80afc4a..b2f8f94f69 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack } from '../../../../models/note'; +import Note, { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -30,5 +30,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = const ids = (note._replyIds || []).slice(offset, offset + limit); // Serialize - res(await Promise.all(ids.map(id => pack(id, user)))); + res(await packMany(ids, user)); }); diff --git a/src/server/api/endpoints/notes/reposts.ts b/src/server/api/endpoints/notes/reposts.ts index 05e68302ba..2c6e1a499f 100644 --- a/src/server/api/endpoints/notes/reposts.ts +++ b/src/server/api/endpoints/notes/reposts.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; -import Note, { pack } from '../../../../models/note'; +import Note, { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; /** @@ -62,6 +62,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }); // Serialize - res(await Promise.all(renotes.map(async note => - await pack(note, user)))); + res(await packMany(renotes, user)); }); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index 9124899ad8..2755a70483 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import * as mongo from 'mongodb'; import Note from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import es from '../../../../db/elasticsearch'; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { @@ -60,6 +60,6 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => } }); - res(await Promise.all(notes.map(note => pack(note, me)))); + res(await packMany(notes, me)); }); }); diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index 0703210017..d380f27f9c 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -3,7 +3,7 @@ import Note from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; import Mute from '../../../../models/mute'; import { getFriendIds } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import getParams from '../../get-params'; import { erase } from '../../../../prelude/array'; @@ -363,5 +363,5 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => }); // Serialize - res(await Promise.all(notes.map(note => pack(note, me)))); + res(await packMany(notes, me)); }); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 5f3844987c..44a504eb18 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; import { getFriends } from '../../common/get-friends'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import { countIf } from '../../../../prelude/array'; @@ -237,5 +237,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 61192d7d3e..6758b4eb73 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; +import { packMany } from '../../../../models/note'; import UserList from '../../../../models/user-list'; import { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; @@ -242,5 +242,5 @@ export default async (params: any, user: ILocalUser) => { }); // Serialize - return await Promise.all(timeline.map(note => pack(note, user))); + return await packMany(timeline, user); }; diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 1ab7786a18..1bfe832c51 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import getHostLower from '../../common/get-host-lower'; -import Note, { pack } from '../../../../models/note'; +import Note, { packMany } from '../../../../models/note'; import User, { ILocalUser } from '../../../../models/user'; import getParams from '../../get-params'; import { countIf } from '../../../../prelude/array'; @@ -181,5 +181,5 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => }); // Serialize - res(await Promise.all(notes.map(note => pack(note, me)))); + res(await packMany(notes, me)); }); -- cgit v1.2.3-freya