Resolve #4735
This commit is contained in:
parent
cce768aaac
commit
7827aeb695
8 changed files with 128 additions and 71 deletions
|
@ -35,6 +35,7 @@ common:
|
||||||
signup: "新規登録"
|
signup: "新規登録"
|
||||||
signout: "ログアウト"
|
signout: "ログアウト"
|
||||||
reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
|
reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?"
|
||||||
|
fetching-as-ap-object: "連合に照会中"
|
||||||
|
|
||||||
got-it: "わかった"
|
got-it: "わかった"
|
||||||
customization-tips:
|
customization-tips:
|
||||||
|
|
64
src/client/app/common/scripts/search.ts
Normal file
64
src/client/app/common/scripts/search.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import { faHistory } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
|
export async function search(v: any, q: string) {
|
||||||
|
q = q.trim();
|
||||||
|
|
||||||
|
if (q.startsWith('@')) {
|
||||||
|
v.$router.push(`/${q}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith('#')) {
|
||||||
|
v.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// like 2018/03/12
|
||||||
|
if (/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}/.test(q.replace(/-/g, '/'))) {
|
||||||
|
const date = new Date(q.replace(/-/g, '/'));
|
||||||
|
|
||||||
|
// 日付しか指定されてない場合、例えば 2018/03/12 ならユーザーは
|
||||||
|
// 2018/03/12 のコンテンツを「含む」結果になることを期待するはずなので
|
||||||
|
// 23時間59分進める(そのままだと 2018/03/12 00:00:00 「まで」の
|
||||||
|
// 結果になってしまい、2018/03/12 のコンテンツは含まれない)
|
||||||
|
if (q.replace(/-/g, '/').match(/^[0-9]{4}\/[0-9]{2}\/[0-9]{2}$/)) {
|
||||||
|
date.setHours(23, 59, 59, 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
v.$root.$emit('warp', date);
|
||||||
|
v.$root.dialog({
|
||||||
|
icon: faHistory,
|
||||||
|
splash: true,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.startsWith('https://')) {
|
||||||
|
const dialog = v.$root.dialog({
|
||||||
|
type: 'waiting',
|
||||||
|
text: v.$t('@.fetching-as-ap-object'),
|
||||||
|
showOkButton: false,
|
||||||
|
showCancelButton: false,
|
||||||
|
cancelableByBgClick: false
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await v.$root.api('ap/show', {
|
||||||
|
uri: q
|
||||||
|
});
|
||||||
|
dialog.close();
|
||||||
|
if (res.type == 'User') {
|
||||||
|
v.$router.push(`/@${res.object.username}@${res.object.host}`);
|
||||||
|
} else if (res.type == 'Note') {
|
||||||
|
v.$router.push(`/notes/${res.object.id}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
dialog.close();
|
||||||
|
// TODO: Show error
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
v.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
||||||
|
}
|
|
@ -6,7 +6,17 @@
|
||||||
<mk-signin/>
|
<mk-signin/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="icon" v-if="!input && !select && !user" :class="type"><fa :icon="icon"/></div>
|
<div class="icon" v-if="icon">
|
||||||
|
<fa :icon="icon"/>
|
||||||
|
</div>
|
||||||
|
<div class="icon" v-else-if="!input && !select && !user" :class="type">
|
||||||
|
<fa icon="check" v-if="type === 'success'"/>
|
||||||
|
<fa :icon="faTimesCircle" v-if="type === 'error'"/>
|
||||||
|
<fa icon="exclamation-triangle" v-if="type === 'warning'"/>
|
||||||
|
<fa icon="info-circle" v-if="type === 'info'"/>
|
||||||
|
<fa :icon="faQuestionCircle" v-if="type === 'question'"/>
|
||||||
|
<fa icon="spinner" pulse v-if="type === 'waiting'"/>
|
||||||
|
</div>
|
||||||
<header v-if="title" v-html="title"></header>
|
<header v-if="title" v-html="title"></header>
|
||||||
<div class="body" v-if="text" v-html="text"></div>
|
<div class="body" v-if="text" v-html="text"></div>
|
||||||
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
|
<ui-input v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder" @keydown="onInputKeydown"></ui-input>
|
||||||
|
@ -14,8 +24,8 @@
|
||||||
<ui-select v-if="select" v-model="selectedValue" autofocus>
|
<ui-select v-if="select" v-model="selectedValue" autofocus>
|
||||||
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
||||||
</ui-select>
|
</ui-select>
|
||||||
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash">
|
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
|
||||||
<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
|
<ui-button @click="ok" v-if="showOkButton" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
|
||||||
<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
|
<ui-button @click="cancel" v-if="showCancelButton || input || select || user">{{ $t('@.cancel') }}</ui-button>
|
||||||
</ui-horizon-group>
|
</ui-horizon-group>
|
||||||
</template>
|
</template>
|
||||||
|
@ -55,10 +65,21 @@ export default Vue.extend({
|
||||||
user: {
|
user: {
|
||||||
required: false
|
required: false
|
||||||
},
|
},
|
||||||
|
icon: {
|
||||||
|
required: false
|
||||||
|
},
|
||||||
|
showOkButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
showCancelButton: {
|
showCancelButton: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
cancelableByBgClick: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
splash: {
|
splash: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
@ -69,22 +90,11 @@ export default Vue.extend({
|
||||||
return {
|
return {
|
||||||
inputValue: this.input && this.input.default ? this.input.default : null,
|
inputValue: this.input && this.input.default ? this.input.default : null,
|
||||||
userInputValue: null,
|
userInputValue: null,
|
||||||
selectedValue: null
|
selectedValue: null,
|
||||||
|
faTimesCircle, faQuestionCircle
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
|
||||||
icon(): any {
|
|
||||||
switch (this.type) {
|
|
||||||
case 'success': return 'check';
|
|
||||||
case 'error': return faTimesCircle;
|
|
||||||
case 'warning': return 'exclamation-triangle';
|
|
||||||
case 'info': return 'info-circle';
|
|
||||||
case 'question': return faQuestionCircle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
||||||
|
@ -113,6 +123,8 @@ export default Vue.extend({
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async ok() {
|
async ok() {
|
||||||
|
if (!this.showOkButton) return;
|
||||||
|
|
||||||
if (this.user) {
|
if (this.user) {
|
||||||
const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
|
const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -156,7 +168,9 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
onBgClick() {
|
onBgClick() {
|
||||||
this.cancel();
|
if (this.cancelableByBgClick) {
|
||||||
|
this.cancel();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputKeydown(e) {
|
onInputKeydown(e) {
|
||||||
|
@ -240,7 +254,7 @@ export default Vue.extend({
|
||||||
margin-top 8px
|
margin-top 8px
|
||||||
|
|
||||||
> .body
|
> .body
|
||||||
margin 16px 0
|
margin 16px 0 0 0
|
||||||
|
|
||||||
> .buttons
|
> .buttons
|
||||||
margin-top 16px
|
margin-top 16px
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import { search } from '../../../common/scripts/search';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/ui.header.search.vue'),
|
i18n: i18n('desktop/views/components/ui.header.search.vue'),
|
||||||
|
@ -22,29 +23,11 @@ export default Vue.extend({
|
||||||
async onSubmit() {
|
async onSubmit() {
|
||||||
if (this.wait) return;
|
if (this.wait) return;
|
||||||
|
|
||||||
const q = this.q.trim();
|
this.wait = true;
|
||||||
if (q.startsWith('@')) {
|
search(this, this.q).finally(() => {
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.wait = true;
|
|
||||||
try {
|
|
||||||
const res = await this.$root.api('ap/show', {
|
|
||||||
uri: q
|
|
||||||
});
|
|
||||||
if (res.type == 'User') {
|
|
||||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
|
||||||
} else if (res.type == 'Note') {
|
|
||||||
this.$router.push(`/notes/${res.object.id}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
this.wait = false;
|
this.wait = false;
|
||||||
} else {
|
this.q = '';
|
||||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -53,6 +53,12 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
this.connection.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
const prepend = note => {
|
const prepend = note => {
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
};
|
};
|
||||||
|
@ -124,13 +130,14 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
(this.$refs.timeline as any).focus();
|
(this.$refs.timeline as any).focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS)
|
||||||
},
|
},
|
||||||
dialog(opts) {
|
dialog(opts) {
|
||||||
const vm = this.new(Dialog, opts);
|
const vm = this.new(Dialog, opts);
|
||||||
return new Promise((res) => {
|
const p: any = new Promise((res) => {
|
||||||
vm.$once('ok', result => res({ canceled: false, result }));
|
vm.$once('ok', result => res({ canceled: false, result }));
|
||||||
vm.$once('cancel', () => res({ canceled: true }));
|
vm.$once('cancel', () => res({ canceled: true }));
|
||||||
});
|
});
|
||||||
|
p.close = () => {
|
||||||
|
vm.close();
|
||||||
|
};
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
router,
|
router,
|
||||||
|
|
|
@ -66,6 +66,7 @@ import i18n from '../../../i18n';
|
||||||
import { lang } from '../../../config';
|
import { lang } from '../../../config';
|
||||||
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
|
import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { search } from '../../../common/scripts/search';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
i18n: i18n('mobile/views/components/ui.nav.vue'),
|
||||||
|
@ -133,29 +134,10 @@ export default Vue.extend({
|
||||||
}).then(async ({ canceled, result: query }) => {
|
}).then(async ({ canceled, result: query }) => {
|
||||||
if (canceled) return;
|
if (canceled) return;
|
||||||
|
|
||||||
const q = query.trim();
|
this.searching = true;
|
||||||
if (q.startsWith('@')) {
|
search(this, query).finally(() => {
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.searching = true;
|
|
||||||
try {
|
|
||||||
const res = await this.$root.api('ap/show', {
|
|
||||||
uri: q
|
|
||||||
});
|
|
||||||
if (res.type == 'User') {
|
|
||||||
this.$router.push(`/@${res.object.username}@${res.object.host}`);
|
|
||||||
} else if (res.type == 'Note') {
|
|
||||||
this.$router.push(`/notes/${res.object.id}`);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
this.searching = false;
|
this.searching = false;
|
||||||
} else {
|
});
|
||||||
this.$router.push(`/search?q=${encodeURIComponent(q)}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,12 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
this.connection.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
const prepend = note => {
|
const prepend = note => {
|
||||||
(this.$refs.timeline as any).prepend(note);
|
(this.$refs.timeline as any).prepend(note);
|
||||||
};
|
};
|
||||||
|
@ -125,10 +131,6 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
this.connection.dispose();
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
focus() {
|
focus() {
|
||||||
(this.$refs.timeline as any).focus();
|
(this.$refs.timeline as any).focus();
|
||||||
|
|
Loading…
Reference in a new issue