Merge branch 'develop'
This commit is contained in:
		
						commit
						7495206db2
					
				
					 19 changed files with 262 additions and 180 deletions
				
			
		
							
								
								
									
										13
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										13
									
								
								CHANGELOG.md
									
										
									
									
									
								
							| 
						 | 
					@ -35,6 +35,19 @@ mongodb:
 | 
				
			||||||
8. master ブランチに戻す
 | 
					8. master ブランチに戻す
 | 
				
			||||||
9. enjoy
 | 
					9. enjoy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					11.4.0 (2019/04/25)
 | 
				
			||||||
 | 
					-------------------
 | 
				
			||||||
 | 
					### Improvements
 | 
				
			||||||
 | 
					* 検索でローカルの投稿のみに絞れるように
 | 
				
			||||||
 | 
					* 検索で特定のインスタンスの投稿のみに絞れるように
 | 
				
			||||||
 | 
					* 検索で特定のユーザーの投稿のみに絞れるように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixes
 | 
				
			||||||
 | 
					* 投稿が増殖する問題を修正
 | 
				
			||||||
 | 
					* ストリームで過去の投稿が流れてくる問題を修正
 | 
				
			||||||
 | 
					* モバイル版のユーザーページで遷移してもユーザー名が変わらない問題を修正
 | 
				
			||||||
 | 
					* お知らせを切り替えても内容が変わらない問題を修正
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.3.1 (2019/04/24)
 | 
					11.3.1 (2019/04/24)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
### Fixes
 | 
					### Fixes
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"name": "misskey",
 | 
						"name": "misskey",
 | 
				
			||||||
	"author": "syuilo <i@syuilo.com>",
 | 
						"author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
	"version": "11.3.1",
 | 
						"version": "11.4.0",
 | 
				
			||||||
	"codename": "daybreak",
 | 
						"codename": "daybreak",
 | 
				
			||||||
	"repository": {
 | 
						"repository": {
 | 
				
			||||||
		"type": "git",
 | 
							"type": "git",
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@
 | 
				
			||||||
		"format": "gulp format"
 | 
							"format": "gulp format"
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	"dependencies": {
 | 
						"dependencies": {
 | 
				
			||||||
 | 
							"@elastic/elasticsearch": "7.0.0-rc.2",
 | 
				
			||||||
		"@fortawesome/fontawesome-svg-core": "1.2.15",
 | 
							"@fortawesome/fontawesome-svg-core": "1.2.15",
 | 
				
			||||||
		"@fortawesome/free-brands-svg-icons": "5.7.2",
 | 
							"@fortawesome/free-brands-svg-icons": "5.7.2",
 | 
				
			||||||
		"@fortawesome/free-regular-svg-icons": "5.7.2",
 | 
							"@fortawesome/free-regular-svg-icons": "5.7.2",
 | 
				
			||||||
| 
						 | 
					@ -35,7 +36,6 @@
 | 
				
			||||||
		"@types/dateformat": "3.0.0",
 | 
							"@types/dateformat": "3.0.0",
 | 
				
			||||||
		"@types/deep-equal": "1.0.1",
 | 
							"@types/deep-equal": "1.0.1",
 | 
				
			||||||
		"@types/double-ended-queue": "2.1.0",
 | 
							"@types/double-ended-queue": "2.1.0",
 | 
				
			||||||
		"@types/elasticsearch": "5.0.32",
 | 
					 | 
				
			||||||
		"@types/file-type": "10.9.1",
 | 
							"@types/file-type": "10.9.1",
 | 
				
			||||||
		"@types/gulp": "4.0.6",
 | 
							"@types/gulp": "4.0.6",
 | 
				
			||||||
		"@types/gulp-mocha": "0.0.32",
 | 
							"@types/gulp-mocha": "0.0.32",
 | 
				
			||||||
| 
						 | 
					@ -113,7 +113,6 @@
 | 
				
			||||||
		"deep-equal": "1.0.1",
 | 
							"deep-equal": "1.0.1",
 | 
				
			||||||
		"diskusage": "1.1.0",
 | 
							"diskusage": "1.1.0",
 | 
				
			||||||
		"double-ended-queue": "2.1.0-0",
 | 
							"double-ended-queue": "2.1.0-0",
 | 
				
			||||||
		"elasticsearch": "15.4.1",
 | 
					 | 
				
			||||||
		"emojilib": "2.4.0",
 | 
							"emojilib": "2.4.0",
 | 
				
			||||||
		"eslint": "5.16.0",
 | 
							"eslint": "5.16.0",
 | 
				
			||||||
		"eslint-plugin-vue": "5.2.2",
 | 
							"eslint-plugin-vue": "5.2.2",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										31
									
								
								src/client/app/common/scripts/gen-search-query.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/client/app/common/scripts/gen-search-query.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,31 @@
 | 
				
			||||||
 | 
					import parseAcct from '../../../../misc/acct/parse';
 | 
				
			||||||
 | 
					import { host as localHost } from '../../config';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function genSearchQuery(v: any, q: string) {
 | 
				
			||||||
 | 
						let host: string;
 | 
				
			||||||
 | 
						let userId: string;
 | 
				
			||||||
 | 
						if (q.split(' ').some(x => x.startsWith('@'))) {
 | 
				
			||||||
 | 
							for (const at of q.split(' ').filter(x => x.startsWith('@')).map(x => x.substr(1))) {
 | 
				
			||||||
 | 
								if (at.includes('.')) {
 | 
				
			||||||
 | 
									if (at === localHost || at === '.') {
 | 
				
			||||||
 | 
										host = null;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										host = at;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									const user = await v.$root.api('users/show', parseAcct(at)).catch(x => null);
 | 
				
			||||||
 | 
									if (user) {
 | 
				
			||||||
 | 
										userId = user.id;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										// todo: show error
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return {
 | 
				
			||||||
 | 
							query: q.split(' ').filter(x => !x.startsWith('/') && !x.startsWith('@')).join(' '),
 | 
				
			||||||
 | 
							host: host,
 | 
				
			||||||
 | 
							userId: userId
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import { faHistory } from '@fortawesome/free-solid-svg-icons';
 | 
				
			||||||
export async function search(v: any, q: string) {
 | 
					export async function search(v: any, q: string) {
 | 
				
			||||||
	q = q.trim();
 | 
						q = q.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (q.startsWith('@')) {
 | 
						if (q.startsWith('@') && !q.includes(' ')) {
 | 
				
			||||||
		v.$router.push(`/${q}`);
 | 
							v.$router.push(`/${q}`);
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,9 +60,9 @@ export default Vue.extend({
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	methods: {
 | 
						methods: {
 | 
				
			||||||
		init() {
 | 
							async init() {
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
			this.makePromise().then(x => {
 | 
								await (this.makePromise()).then(x => {
 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
									if (Array.isArray(x)) {
 | 
				
			||||||
					this.us = x;
 | 
										this.us = x;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
| 
						 | 
					@ -76,9 +76,9 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMoreUsers() {
 | 
							async fetchMoreUsers() {
 | 
				
			||||||
			this.fetchingMoreUsers = true;
 | 
								this.fetchingMoreUsers = true;
 | 
				
			||||||
			this.makePromise(this.cursor).then(x => {
 | 
								await (this.makePromise(this.cursor)).then(x => {
 | 
				
			||||||
				this.us = this.us.concat(x.users);
 | 
									this.us = this.us.concat(x.users);
 | 
				
			||||||
				this.cursor = x.cursor;
 | 
									this.cursor = x.cursor;
 | 
				
			||||||
				this.fetchingMoreUsers = false;
 | 
									this.fetchingMoreUsers = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,11 +110,11 @@ export default Vue.extend({
 | 
				
			||||||
			this.init();
 | 
								this.init();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		init() {
 | 
							async init() {
 | 
				
			||||||
			this.queue = [];
 | 
								this.queue = [];
 | 
				
			||||||
			this.notes = [];
 | 
								this.notes = [];
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
			this.makePromise().then(x => {
 | 
								await (this.makePromise()).then(x => {
 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
									if (Array.isArray(x)) {
 | 
				
			||||||
					this.notes = x;
 | 
										this.notes = x;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
| 
						 | 
					@ -129,10 +129,10 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMore() {
 | 
							async fetchMore() {
 | 
				
			||||||
			if (!this.more || this.moreFetching) return;
 | 
								if (!this.more || this.moreFetching) return;
 | 
				
			||||||
			this.moreFetching = true;
 | 
								this.moreFetching = true;
 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
								await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
									this.notes = this.notes.concat(x.notes);
 | 
				
			||||||
				this.more = x.more;
 | 
									this.more = x.more;
 | 
				
			||||||
				this.moreFetching = false;
 | 
									this.moreFetching = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import XColumn from './deck.column.vue';
 | 
					import XColumn from './deck.column.vue';
 | 
				
			||||||
import XNotes from './deck.notes.vue';
 | 
					import XNotes from './deck.notes.vue';
 | 
				
			||||||
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					const limit = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,10 +26,10 @@ export default Vue.extend({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search', {
 | 
								makePromise: async cursor => this.$root.api('notes/search', {
 | 
				
			||||||
				limit: limit + 1,
 | 
									limit: limit + 1,
 | 
				
			||||||
				offset: cursor ? cursor : undefined,
 | 
									offset: cursor ? cursor : undefined,
 | 
				
			||||||
				query: this.q
 | 
									...(await genSearchQuery(this, this.q))
 | 
				
			||||||
			}).then(notes => {
 | 
								}).then(notes => {
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
									if (notes.length == limit + 1) {
 | 
				
			||||||
					notes.pop();
 | 
										notes.pop();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@
 | 
				
			||||||
			<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
 | 
								<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
 | 
				
			||||||
			<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
 | 
								<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
 | 
				
			||||||
			<p v-if="!fetching">
 | 
								<p v-if="!fetching">
 | 
				
			||||||
				<mfm v-if="announcements.length != 0" :text="announcements[i].text"/>
 | 
									<mfm v-if="announcements.length != 0" :text="announcements[i].text" :key="i"/>
 | 
				
			||||||
				<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
 | 
									<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
 | 
				
			||||||
				<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
 | 
									<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
 | 
				
			||||||
			</p>
 | 
								</p>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,9 +105,9 @@ export default Vue.extend({
 | 
				
			||||||
			this.init();
 | 
								this.init();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		init() {
 | 
							async init() {
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
			this.makePromise().then(x => {
 | 
								await (this.makePromise()).then(x => {
 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
									if (Array.isArray(x)) {
 | 
				
			||||||
					this.notes = x;
 | 
										this.notes = x;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
| 
						 | 
					@ -122,7 +122,7 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMore() {
 | 
							async fetchMore() {
 | 
				
			||||||
			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
								if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
				
			||||||
			this.moreFetching = true;
 | 
								this.moreFetching = true;
 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
								this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					const limit = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,10 +22,10 @@ export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('desktop/views/pages/search.vue'),
 | 
						i18n: i18n('desktop/views/pages/search.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search', {
 | 
								makePromise: async cursor => this.$root.api('notes/search', {
 | 
				
			||||||
				limit: limit + 1,
 | 
									limit: limit + 1,
 | 
				
			||||||
				offset: cursor ? cursor : undefined,
 | 
									offset: cursor ? cursor : undefined,
 | 
				
			||||||
				query: this.q
 | 
									...(await genSearchQuery(this, this.q))
 | 
				
			||||||
			}).then(notes => {
 | 
								}).then(notes => {
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
									if (notes.length == limit + 1) {
 | 
				
			||||||
					notes.pop();
 | 
										notes.pop();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,9 +106,9 @@ export default Vue.extend({
 | 
				
			||||||
			this.init();
 | 
								this.init();
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		init() {
 | 
							async init() {
 | 
				
			||||||
			this.fetching = true;
 | 
								this.fetching = true;
 | 
				
			||||||
			this.makePromise().then(x => {
 | 
								await (this.makePromise()).then(x => {
 | 
				
			||||||
				if (Array.isArray(x)) {
 | 
									if (Array.isArray(x)) {
 | 
				
			||||||
					this.notes = x;
 | 
										this.notes = x;
 | 
				
			||||||
				} else {
 | 
									} else {
 | 
				
			||||||
| 
						 | 
					@ -123,10 +123,10 @@ export default Vue.extend({
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fetchMore() {
 | 
							async fetchMore() {
 | 
				
			||||||
			if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
								if (!this.more || this.moreFetching || this.notes.length === 0) return;
 | 
				
			||||||
			this.moreFetching = true;
 | 
								this.moreFetching = true;
 | 
				
			||||||
			this.makePromise(this.notes[this.notes.length - 1].id).then(x => {
 | 
								await (this.makePromise(this.notes[this.notes.length - 1].id)).then(x => {
 | 
				
			||||||
				this.notes = this.notes.concat(x.notes);
 | 
									this.notes = this.notes.concat(x.notes);
 | 
				
			||||||
				this.more = x.more;
 | 
									this.more = x.more;
 | 
				
			||||||
				this.moreFetching = false;
 | 
									this.moreFetching = false;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@
 | 
				
			||||||
				<div class="announcements" v-if="announcements && announcements.length > 0">
 | 
									<div class="announcements" v-if="announcements && announcements.length > 0">
 | 
				
			||||||
					<article v-for="announcement in announcements">
 | 
										<article v-for="announcement in announcements">
 | 
				
			||||||
						<span v-html="announcement.title" class="title"></span>
 | 
											<span v-html="announcement.title" class="title"></span>
 | 
				
			||||||
						<mfm :text="announcement.text"/>
 | 
											<div><mfm :text="announcement.text"/></div>
 | 
				
			||||||
						<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
 | 
											<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
 | 
				
			||||||
					</article>
 | 
										</article>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,6 +12,7 @@
 | 
				
			||||||
import Vue from 'vue';
 | 
					import Vue from 'vue';
 | 
				
			||||||
import i18n from '../../../i18n';
 | 
					import i18n from '../../../i18n';
 | 
				
			||||||
import Progress from '../../../common/scripts/loading';
 | 
					import Progress from '../../../common/scripts/loading';
 | 
				
			||||||
 | 
					import { genSearchQuery } from '../../../common/scripts/gen-search-query';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const limit = 20;
 | 
					const limit = 20;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,10 +20,10 @@ export default Vue.extend({
 | 
				
			||||||
	i18n: i18n('mobile/views/pages/search.vue'),
 | 
						i18n: i18n('mobile/views/pages/search.vue'),
 | 
				
			||||||
	data() {
 | 
						data() {
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			makePromise: cursor => this.$root.api('notes/search', {
 | 
								makePromise: async cursor => this.$root.api('notes/search', {
 | 
				
			||||||
				limit: limit + 1,
 | 
									limit: limit + 1,
 | 
				
			||||||
				untilId: cursor ? cursor : undefined,
 | 
									untilId: cursor ? cursor : undefined,
 | 
				
			||||||
				query: this.q
 | 
									...(await genSearchQuery(this, this.q))
 | 
				
			||||||
			}).then(notes => {
 | 
								}).then(notes => {
 | 
				
			||||||
				if (notes.length == limit + 1) {
 | 
									if (notes.length == limit + 1) {
 | 
				
			||||||
					notes.pop();
 | 
										notes.pop();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<div class="title">
 | 
									<div class="title">
 | 
				
			||||||
					<h1><mk-user-name :user="user" :key="user.id"/></h1>
 | 
										<h1><mk-user-name :user="user" :key="user.id"/></h1>
 | 
				
			||||||
					<span class="username"><mk-acct :user="user" :detail="true" /></span>
 | 
										<span class="username"><mk-acct :user="user" :detail="true" :key="user.id"/></span>
 | 
				
			||||||
					<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
 | 
										<span class="followed" v-if="user.isFollowed">{{ $t('follows-you') }}</span>
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
				<div class="description">
 | 
									<div class="description">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,41 +1,30 @@
 | 
				
			||||||
import * as elasticsearch from 'elasticsearch';
 | 
					import * as elasticsearch from '@elastic/elasticsearch';
 | 
				
			||||||
import config from '../config';
 | 
					import config from '../config';
 | 
				
			||||||
import Logger from '../services/logger';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const esLogger = new Logger('es');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const index = {
 | 
					const index = {
 | 
				
			||||||
	settings: {
 | 
						settings: {
 | 
				
			||||||
		analysis: {
 | 
							analysis: {
 | 
				
			||||||
			normalizer: {
 | 
					 | 
				
			||||||
				lowercase_normalizer: {
 | 
					 | 
				
			||||||
					type: 'custom',
 | 
					 | 
				
			||||||
					filter: ['lowercase']
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			analyzer: {
 | 
								analyzer: {
 | 
				
			||||||
				bigram: {
 | 
									ngram: {
 | 
				
			||||||
					tokenizer: 'bigram_tokenizer'
 | 
										tokenizer: 'ngram'
 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
			tokenizer: {
 | 
					 | 
				
			||||||
				bigram_tokenizer: {
 | 
					 | 
				
			||||||
					type: 'nGram',
 | 
					 | 
				
			||||||
					min_gram: 2,
 | 
					 | 
				
			||||||
					max_gram: 2
 | 
					 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	mappings: {
 | 
						mappings: {
 | 
				
			||||||
		note: {
 | 
					 | 
				
			||||||
		properties: {
 | 
							properties: {
 | 
				
			||||||
			text: {
 | 
								text: {
 | 
				
			||||||
				type: 'text',
 | 
									type: 'text',
 | 
				
			||||||
				index: true,
 | 
									index: true,
 | 
				
			||||||
					analyzer: 'bigram',
 | 
									analyzer: 'ngram',
 | 
				
			||||||
					normalizer: 'lowercase_normalizer'
 | 
								},
 | 
				
			||||||
				}
 | 
								userId: {
 | 
				
			||||||
 | 
									type: 'keyword',
 | 
				
			||||||
 | 
									index: true,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								userHost: {
 | 
				
			||||||
 | 
									type: 'keyword',
 | 
				
			||||||
 | 
									index: true,
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -43,31 +32,20 @@ const index = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Init ElasticSearch connection
 | 
					// Init ElasticSearch connection
 | 
				
			||||||
const client = config.elasticsearch ? new elasticsearch.Client({
 | 
					const client = config.elasticsearch ? new elasticsearch.Client({
 | 
				
			||||||
	host: `${config.elasticsearch.host}:${config.elasticsearch.port}`
 | 
						node: `http://${config.elasticsearch.host}:${config.elasticsearch.port}`,
 | 
				
			||||||
 | 
						pingTimeout: 30000
 | 
				
			||||||
}) : null;
 | 
					}) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if (client) {
 | 
					if (client) {
 | 
				
			||||||
	// Send a HEAD request
 | 
					 | 
				
			||||||
	client.ping({
 | 
					 | 
				
			||||||
		// Ping usually has a 3000ms timeout
 | 
					 | 
				
			||||||
		requestTimeout: 30000
 | 
					 | 
				
			||||||
	}, error => {
 | 
					 | 
				
			||||||
		if (error) {
 | 
					 | 
				
			||||||
			esLogger.error('elasticsearch is down!');
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			esLogger.succ('elasticsearch is available!');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	client.indices.exists({
 | 
						client.indices.exists({
 | 
				
			||||||
		index: 'misskey'
 | 
							index: 'misskey_note'
 | 
				
			||||||
	}).then(exist => {
 | 
						}).then(exist => {
 | 
				
			||||||
		if (exist) return;
 | 
							if (!exist.body) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			client.indices.create({
 | 
								client.indices.create({
 | 
				
			||||||
			index: 'misskey',
 | 
									index: 'misskey_note',
 | 
				
			||||||
				body: index
 | 
									body: index
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -247,7 +247,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
 | 
				
			||||||
	// リモートサーバーからフェッチしてきて登録
 | 
						// リモートサーバーからフェッチしてきて登録
 | 
				
			||||||
	// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
 | 
						// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが
 | 
				
			||||||
	// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
 | 
						// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
 | 
				
			||||||
	return await createNote(uri, resolver).catch(e => {
 | 
						return await createNote(uri, resolver, true).catch(e => {
 | 
				
			||||||
		if (e.name === 'duplicated') {
 | 
							if (e.name === 'duplicated') {
 | 
				
			||||||
			return fetchNote(uri).then(note => {
 | 
								return fetchNote(uri).then(note => {
 | 
				
			||||||
				if (note == null) {
 | 
									if (note == null) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,6 +101,32 @@ async function fetchAny(uri: string) {
 | 
				
			||||||
	// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
 | 
						// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
 | 
				
			||||||
	// これはDBに存在する可能性があるため再度DB検索
 | 
						// これはDBに存在する可能性があるため再度DB検索
 | 
				
			||||||
	if (uri !== object.id) {
 | 
						if (uri !== object.id) {
 | 
				
			||||||
 | 
							if (object.id.startsWith(config.url + '/')) {
 | 
				
			||||||
 | 
								const parts = object.id.split('/');
 | 
				
			||||||
 | 
								const id = parts.pop();
 | 
				
			||||||
 | 
								const type = parts.pop();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (type === 'notes') {
 | 
				
			||||||
 | 
									const note = await Notes.findOne(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (note) {
 | 
				
			||||||
 | 
										return {
 | 
				
			||||||
 | 
											type: 'Note',
 | 
				
			||||||
 | 
											object: await Notes.pack(note, null, { detail: true })
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else if (type === 'users') {
 | 
				
			||||||
 | 
									const user = await Users.findOne(id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (user) {
 | 
				
			||||||
 | 
										return {
 | 
				
			||||||
 | 
											type: 'User',
 | 
				
			||||||
 | 
											object: await Users.pack(user, null, { detail: true })
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const [user, note] = await Promise.all([
 | 
							const [user, note] = await Promise.all([
 | 
				
			||||||
			Users.findOne({ uri: object.id }),
 | 
								Users.findOne({ uri: object.id }),
 | 
				
			||||||
			Notes.findOne({ uri: object.id })
 | 
								Notes.findOne({ uri: object.id })
 | 
				
			||||||
| 
						 | 
					@ -120,7 +146,7 @@ async function fetchAny(uri: string) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (['Note', 'Question', 'Article'].includes(object.type)) {
 | 
						if (['Note', 'Question', 'Article'].includes(object.type)) {
 | 
				
			||||||
		const note = await createNote(object.id);
 | 
							const note = await createNote(object.id, undefined, true);
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			type: 'Note',
 | 
								type: 'Note',
 | 
				
			||||||
			object: await Notes.pack(note!, null, { detail: true })
 | 
								object: await Notes.pack(note!, null, { detail: true })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,6 +5,7 @@ import { ApiError } from '../../error';
 | 
				
			||||||
import { Notes } from '../../../../models';
 | 
					import { Notes } from '../../../../models';
 | 
				
			||||||
import { In } from 'typeorm';
 | 
					import { In } from 'typeorm';
 | 
				
			||||||
import { types, bool } from '../../../../misc/schema';
 | 
					import { types, bool } from '../../../../misc/schema';
 | 
				
			||||||
 | 
					import { ID } from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const meta = {
 | 
					export const meta = {
 | 
				
			||||||
	desc: {
 | 
						desc: {
 | 
				
			||||||
| 
						 | 
					@ -29,7 +30,17 @@ export const meta = {
 | 
				
			||||||
		offset: {
 | 
							offset: {
 | 
				
			||||||
			validator: $.optional.num.min(0),
 | 
								validator: $.optional.num.min(0),
 | 
				
			||||||
			default: 0
 | 
								default: 0
 | 
				
			||||||
		}
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							host: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.str,
 | 
				
			||||||
 | 
								default: undefined
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							userId: {
 | 
				
			||||||
 | 
								validator: $.optional.nullable.type(ID),
 | 
				
			||||||
 | 
								default: null
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	res: {
 | 
						res: {
 | 
				
			||||||
| 
						 | 
					@ -54,30 +65,51 @@ export const meta = {
 | 
				
			||||||
export default define(meta, async (ps, me) => {
 | 
					export default define(meta, async (ps, me) => {
 | 
				
			||||||
	if (es == null) throw new ApiError(meta.errors.searchingNotAvailable);
 | 
						if (es == null) throw new ApiError(meta.errors.searchingNotAvailable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const response = await es.search({
 | 
						const userQuery = ps.userId != null ? [{
 | 
				
			||||||
		index: 'misskey',
 | 
							term: {
 | 
				
			||||||
		type: 'note',
 | 
								userId: ps.userId
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}] : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const hostQuery = ps.userId == null ?
 | 
				
			||||||
 | 
							ps.host === null ? [{
 | 
				
			||||||
 | 
								bool: {
 | 
				
			||||||
 | 
									must_not: {
 | 
				
			||||||
 | 
										exists: {
 | 
				
			||||||
 | 
											field: 'userHost'
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}] : ps.host !== undefined ? [{
 | 
				
			||||||
 | 
								term: {
 | 
				
			||||||
 | 
									userHost: ps.host
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}] : []
 | 
				
			||||||
 | 
						: [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const result = await es.search({
 | 
				
			||||||
 | 
							index: 'misskey_note',
 | 
				
			||||||
		body: {
 | 
							body: {
 | 
				
			||||||
			size: ps.limit!,
 | 
								size: ps.limit!,
 | 
				
			||||||
			from: ps.offset,
 | 
								from: ps.offset,
 | 
				
			||||||
			query: {
 | 
								query: {
 | 
				
			||||||
 | 
									bool: {
 | 
				
			||||||
 | 
										must: [{
 | 
				
			||||||
						simple_query_string: {
 | 
											simple_query_string: {
 | 
				
			||||||
							fields: ['text'],
 | 
												fields: ['text'],
 | 
				
			||||||
					query: ps.query,
 | 
												query: ps.query.toLowerCase(),
 | 
				
			||||||
							default_operator: 'and'
 | 
												default_operator: 'and'
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										}, ...hostQuery, ...userQuery]
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			sort: [
 | 
								sort: [{
 | 
				
			||||||
				{ _doc: 'desc' }
 | 
									_doc: 'desc'
 | 
				
			||||||
			]
 | 
								}]
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (response.hits.total === 0) {
 | 
						const hits = result.body.hits.hits.map((hit: any) => hit._id);
 | 
				
			||||||
		return [];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const hits = response.hits.hits.map((hit: any) => hit.id);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (hits.length === 0) return [];
 | 
						if (hits.length === 0) return [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,8 +106,6 @@ type Option = {
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default async (user: User, data: Option, silent = false) => new Promise<Note>(async (res, rej) => {
 | 
					export default async (user: User, data: Option, silent = false) => new Promise<Note>(async (res, rej) => {
 | 
				
			||||||
	const isFirstNote = user.notesCount === 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (data.createdAt == null) data.createdAt = new Date();
 | 
						if (data.createdAt == null) data.createdAt = new Date();
 | 
				
			||||||
	if (data.visibility == null) data.visibility = 'public';
 | 
						if (data.visibility == null) data.visibility = 'public';
 | 
				
			||||||
	if (data.viaMobile == null) data.viaMobile = false;
 | 
						if (data.viaMobile == null) data.viaMobile = false;
 | 
				
			||||||
| 
						 | 
					@ -195,8 +193,6 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
	// 統計を更新
 | 
						// 統計を更新
 | 
				
			||||||
	notesChart.update(note, true);
 | 
						notesChart.update(note, true);
 | 
				
			||||||
	perUserNotesChart.update(user, note, true);
 | 
						perUserNotesChart.update(user, note, true);
 | 
				
			||||||
	// ローカルユーザーのチャートはタイムライン取得時に更新しているのでリモートユーザーの場合だけでよい
 | 
					 | 
				
			||||||
	if (Users.isRemoteUser(user)) activeUsersChart.update(user);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register host
 | 
						// Register host
 | 
				
			||||||
	if (Users.isRemoteUser(user)) {
 | 
						if (Users.isRemoteUser(user)) {
 | 
				
			||||||
| 
						 | 
					@ -212,6 +208,18 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
	// Increment notes count (user)
 | 
						// Increment notes count (user)
 | 
				
			||||||
	incNotesCountOfUser(user);
 | 
						incNotesCountOfUser(user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (data.reply) {
 | 
				
			||||||
 | 
							saveReply(data.reply, note);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (data.renote) {
 | 
				
			||||||
 | 
							incRenoteCount(data.renote);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!silent) {
 | 
				
			||||||
 | 
							// ローカルユーザーのチャートはタイムライン取得時に更新しているのでリモートユーザーの場合だけでよい
 | 
				
			||||||
 | 
							if (Users.isRemoteUser(user)) activeUsersChart.update(user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 未読通知を作成
 | 
							// 未読通知を作成
 | 
				
			||||||
		if (data.visibility == 'specified') {
 | 
							if (data.visibility == 'specified') {
 | 
				
			||||||
			if (data.visibleUsers == null) throw new Error('invalid param');
 | 
								if (data.visibleUsers == null) throw new Error('invalid param');
 | 
				
			||||||
| 
						 | 
					@ -225,18 +233,10 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (data.reply) {
 | 
					 | 
				
			||||||
		saveReply(data.reply, note);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (data.renote) {
 | 
					 | 
				
			||||||
		incRenoteCount(data.renote);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Pack the note
 | 
							// Pack the note
 | 
				
			||||||
		const noteObj = await Notes.pack(note);
 | 
							const noteObj = await Notes.pack(note);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (isFirstNote) {
 | 
							if (user.notesCount === 0) {
 | 
				
			||||||
			(noteObj as any).isFirstNote = true;
 | 
								(noteObj as any).isFirstNote = true;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -295,13 +295,12 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!silent) {
 | 
					 | 
				
			||||||
		publish(user, note, data.reply, data.renote, noteActivity);
 | 
							publish(user, note, data.reply, data.renote, noteActivity);
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Promise.all(nmRelatedPromises).then(() => {
 | 
							Promise.all(nmRelatedPromises).then(() => {
 | 
				
			||||||
			nm.deliver();
 | 
								nm.deliver();
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Register to search database
 | 
						// Register to search database
 | 
				
			||||||
	index(note);
 | 
						index(note);
 | 
				
			||||||
| 
						 | 
					@ -436,11 +435,12 @@ function index(note: Note) {
 | 
				
			||||||
	if (note.text == null || config.elasticsearch == null) return;
 | 
						if (note.text == null || config.elasticsearch == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	es!.index({
 | 
						es!.index({
 | 
				
			||||||
		index: 'misskey',
 | 
							index: 'misskey_note',
 | 
				
			||||||
		type: 'note',
 | 
					 | 
				
			||||||
		id: note.id.toString(),
 | 
							id: note.id.toString(),
 | 
				
			||||||
		body: {
 | 
							body: {
 | 
				
			||||||
			text: note.text
 | 
								text: note.text.toLowerCase(),
 | 
				
			||||||
 | 
								userId: note.userId,
 | 
				
			||||||
 | 
								userHost: note.userHost
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue