メッセージタイムラインを追加
This commit is contained in:
		
							parent
							
								
									2fad6e6d5f
								
							
						
					
					
						commit
						ab83e08bc7
					
				
					 11 changed files with 393 additions and 150 deletions
				
			
		| 
						 | 
				
			
			@ -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: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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', this.onNote);
 | 
			
		||||
			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', 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();
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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: {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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>
 | 
			
		||||
							
								
								
									
										97
									
								
								src/client/app/desktop/views/pages/deck/deck.direct.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/client/app/desktop/views/pages/deck/deck.direct.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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%',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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', this.onNote);
 | 
			
		||||
			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', 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();
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
			<span v-if="src == 'hybrid'">%fa:share-alt%%i18n:@hybrid%</span>
 | 
			
		||||
			<span v-if="src == 'global'">%fa:globe%%i18n:@global%</span>
 | 
			
		||||
			<span v-if="src == 'mentions'">%fa:at%%i18n:@mentions%</span>
 | 
			
		||||
			<span v-if="src == 'messages'">%fa:envelope R%%i18n:@messages%</span>
 | 
			
		||||
			<span v-if="src == 'list'">%fa:list%{{ list.title }}</span>
 | 
			
		||||
			<span v-if="src == 'tag'">%fa:hashtag%{{ tagTl.title }}</span>
 | 
			
		||||
		</span>
 | 
			
		||||
| 
						 | 
				
			
			@ -23,16 +24,21 @@
 | 
			
		|||
	<main :data-darkmode="$store.state.device.darkmode">
 | 
			
		||||
		<div class="nav" v-if="showNav">
 | 
			
		||||
			<div class="bg" @click="showNav = false"></div>
 | 
			
		||||
			<div class="pointer"></div>
 | 
			
		||||
			<div class="body">
 | 
			
		||||
				<div>
 | 
			
		||||
					<span :data-active="src == 'home'" @click="src = 'home'">%fa:home% %i18n:@home%</span>
 | 
			
		||||
					<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>
 | 
			
		||||
					<div class="hr"></div>
 | 
			
		||||
					<span :data-active="src == 'mentions'" @click="src = 'mentions'">%fa:at% %i18n:@mentions%</span>
 | 
			
		||||
					<span :data-active="src == 'messages'" @click="src = 'messages'">%fa:envelope R% %i18n:@messages%</span>
 | 
			
		||||
					<template v-if="lists">
 | 
			
		||||
						<div class="hr"></div>
 | 
			
		||||
						<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id">%fa:list% {{ l.title }}</span>
 | 
			
		||||
					</template>
 | 
			
		||||
					<div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
 | 
			
		||||
					<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id">%fa:hashtag% {{ tl.title }}</span>
 | 
			
		||||
				</div>
 | 
			
		||||
			</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -44,6 +50,7 @@
 | 
			
		|||
			<x-tl v-if="src == 'hybrid'" ref="tl" key="hybrid" src="hybrid"/>
 | 
			
		||||
			<x-tl v-if="src == 'global'" ref="tl" key="global" src="global"/>
 | 
			
		||||
			<x-tl v-if="src == 'mentions'" ref="tl" key="mentions" src="mentions"/>
 | 
			
		||||
			<x-tl v-if="src == 'messages'" ref="tl" key="messages" src="messages"/>
 | 
			
		||||
			<x-tl 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>
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue