diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2018-09-18 02:14:12 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2018-09-18 02:14:12 +0900 |
| commit | ab83e08bc7deb244d35e2315abead473d536d2c3 (patch) | |
| tree | 1a6b441f69460b0bf620694d7b3f2019dfe4c830 /src/client/app/desktop | |
| parent | Refactor (diff) | |
| download | misskey-ab83e08bc7deb244d35e2315abead473d536d2c3.tar.gz misskey-ab83e08bc7deb244d35e2315abead473d536d2c3.tar.bz2 misskey-ab83e08bc7deb244d35e2315abead473d536d2c3.zip | |
メッセージタイムラインを追加
Diffstat (limited to 'src/client/app/desktop')
6 files changed, 248 insertions, 67 deletions
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 @@ <span :data-active="src == 'local'" @click="src = 'local'" v-if="enableLocalTimeline">%fa:R comments% %i18n:@local%</span> <span :data-active="src == 'hybrid'" @click="src = 'hybrid'" v-if="enableLocalTimeline">%fa:share-alt% %i18n:@hybrid%</span> <span :data-active="src == 'global'" @click="src = 'global'">%fa:globe% %i18n:@global%</span> - <span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span> <span :data-active="src == 'tag'" @click="src = 'tag'" v-if="tagTl">%fa:hashtag% {{ tagTl.title }}</span> <span :data-active="src == 'list'" @click="src = 'list'" v-if="list">%fa:list% {{ list.title }}</span> <div class="buttons"> + <button :data-active="src == 'mentions'" @click="src = 'mentions'" title="%i18n:@mentions%">%fa:at%</button> + <button :data-active="src == 'messages'" @click="src = 'messages'" title="%i18n:@messages%">%fa:envelope R%</button> <button @click="chooseTag" title="%i18n:@hashtag%" ref="tagButton">%fa:hashtag%</button> <button @click="chooseList" title="%i18n:@list%" ref="listButton">%fa:list%</button> </div> @@ -18,6 +19,7 @@ <x-core v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/> <x-core v-if="src == 'global'" ref="tl" key="global" src="global"/> <x-core v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/> + <x-core v-if="src == 'messages'" ref="tl" key="messages" src="messages"/> <x-core v-if="src == 'tag'" ref="tl" key="tag" src="tag" :tag-tl="tagTl"/> <mk-user-list-timeline v-if="src == 'list'" ref="tl" :key="list.id" :list="list"/> </div> @@ -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 @@ <x-tl-column v-else-if="column.type == 'list'" :column="column" :is-stacked="isStacked"/> <x-tl-column v-else-if="column.type == 'hashtag'" :column="column" :is-stacked="isStacked"/> <x-mentions-column v-else-if="column.type == 'mentions'" :column="column" :is-stacked="isStacked"/> +<x-direct-column v-else-if="column.type == 'direct'" :column="column" :is-stacked="isStacked"/> </template> <script lang="ts"> @@ -16,13 +17,15 @@ import XTlColumn from './deck.tl-column.vue'; import XNotificationsColumn from './deck.notifications-column.vue'; import XWidgetsColumn from './deck.widgets-column.vue'; import XMentionsColumn from './deck.mentions-column.vue'; +import XDirectColumn from './deck.direct-column.vue'; export default Vue.extend({ components: { XTlColumn, XNotificationsColumn, XWidgetsColumn, - XMentionsColumn + XMentionsColumn, + XDirectColumn }, props: { diff --git a/src/client/app/desktop/views/pages/deck/deck.direct-column.vue b/src/client/app/desktop/views/pages/deck/deck.direct-column.vue new file mode 100644 index 0000000000..d5093761f4 --- /dev/null +++ b/src/client/app/desktop/views/pages/deck/deck.direct-column.vue @@ -0,0 +1,38 @@ +<template> +<x-column :name="name" :column="column" :is-stacked="isStacked"> + <span slot="header">%fa:envelope R%{{ name }}</span> + + <x-direct/> +</x-column> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import XColumn from './deck.column.vue'; +import XDirect from './deck.direct.vue'; + +export default Vue.extend({ + components: { + XColumn, + XDirect + }, + + props: { + column: { + type: Object, + required: true + }, + isStacked: { + type: Boolean, + required: true + } + }, + + computed: { + name(): string { + if (this.column.name) return this.column.name; + return '%i18n:common.deck.direct%'; + } + }, +}); +</script> 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 @@ +<template> + <x-notes ref="timeline" :more="existMore ? more : null"/> +</template> + +<script lang="ts"> +import Vue from 'vue'; +import XNotes from './deck.notes.vue'; + +const fetchLimit = 10; + +export default Vue.extend({ + components: { + XNotes + }, + + props: { + }, + + data() { + return { + fetching: true, + moreFetching: false, + existMore: false, + connection: null, + connectionId: null + }; + }, + + mounted() { + this.connection = (this as any).os.stream.getConnection(); + this.connectionId = (this as any).os.stream.use(); + + this.connection.on('mention', this.onNote); + + this.fetch(); + }, + + beforeDestroy() { + this.connection.off('mention', this.onNote); + (this as any).os.stream.dispose(this.connectionId); + }, + + methods: { + fetch() { + this.fetching = true; + + (this.$refs.timeline as any).init(() => new Promise((res, rej) => { + (this as any).api('notes/mentions', { + limit: fetchLimit + 1, + includeMyRenotes: this.$store.state.settings.showMyRenotes, + includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, + includeLocalRenotes: this.$store.state.settings.showLocalRenotes, + visibility: 'specified' + }).then(notes => { + if (notes.length == fetchLimit + 1) { + notes.pop(); + this.existMore = true; + } + res(notes); + this.fetching = false; + this.$emit('loaded'); + }, rej); + })); + }, + more() { + this.moreFetching = true; + + const promise = (this as any).api('notes/mentions', { + 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, + visibility: 'specified' + }); + + promise.then(notes => { + if (notes.length == fetchLimit + 1) { + notes.pop(); + } else { + this.existMore = false; + } + notes.forEach(n => (this.$refs.timeline as any).append(n)); + this.moreFetching = false; + }); + + return promise; + }, + onNote(note) { + // Prepend a note + if (note.visibility == 'specified') { + (this.$refs.timeline as any).prepend(note); + } + } + } +}); +</script> 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 @@ -148,6 +148,15 @@ export default Vue.extend({ }); } }, { + 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%', action: () => { |