Merge branch 'develop'
This commit is contained in:
commit
7b44727b23
39 changed files with 556 additions and 260 deletions
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/>
|
||||
</template>
|
||||
<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>
|
||||
<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>
|
||||
|
|
@ -14,8 +24,8 @@
|
|||
<ui-select v-if="select" v-model="selectedValue" autofocus>
|
||||
<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
|
||||
</ui-select>
|
||||
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash">
|
||||
<ui-button @click="ok" primary :autofocus="!input && !select && !user">{{ (showCancelButton || input || select || user) ? $t('@.ok') : $t('@.got-it') }}</ui-button>
|
||||
<ui-horizon-group no-grow class="buttons fit-bottom" v-if="!splash && (showOkButton || showCancelButton)">
|
||||
<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-horizon-group>
|
||||
</template>
|
||||
|
|
@ -55,10 +65,21 @@ export default Vue.extend({
|
|||
user: {
|
||||
required: false
|
||||
},
|
||||
icon: {
|
||||
required: false
|
||||
},
|
||||
showOkButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
cancelableByBgClick: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
splash: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
|
|
@ -69,22 +90,11 @@ export default Vue.extend({
|
|||
return {
|
||||
inputValue: this.input && this.input.default ? this.input.default : 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() {
|
||||
this.$nextTick(() => {
|
||||
(this.$refs.bg as any).style.pointerEvents = 'auto';
|
||||
|
|
@ -113,6 +123,8 @@ export default Vue.extend({
|
|||
|
||||
methods: {
|
||||
async ok() {
|
||||
if (!this.showOkButton) return;
|
||||
|
||||
if (this.user) {
|
||||
const user = await this.$root.api('users/show', parseAcct(this.userInputValue));
|
||||
if (user) {
|
||||
|
|
@ -156,7 +168,9 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
onBgClick() {
|
||||
this.cancel();
|
||||
if (this.cancelableByBgClick) {
|
||||
this.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
onInputKeydown(e) {
|
||||
|
|
@ -183,9 +197,6 @@ export default Vue.extend({
|
|||
height 100%
|
||||
|
||||
&.splash
|
||||
&, *
|
||||
pointer-events none !important
|
||||
|
||||
> .main
|
||||
min-width 0
|
||||
width initial
|
||||
|
|
@ -243,7 +254,7 @@ export default Vue.extend({
|
|||
margin-top 8px
|
||||
|
||||
> .body
|
||||
margin 16px 0
|
||||
margin 16px 0 0 0
|
||||
|
||||
> .buttons
|
||||
margin-top 16px
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<div class="body">
|
||||
<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
|
||||
<p class="empty" v-if="!init && messages.length == 0"><fa icon="info-circle"/>{{ $t('empty') }}</p>
|
||||
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa icon="flag"/>{{ $t('no-history') }}</p>
|
||||
<p class="no-history" v-if="!init && messages.length > 0 && !existMoreMessages"><fa :icon="faFlag"/>{{ $t('no-history') }}</p>
|
||||
<button class="more" :class="{ fetching: fetchingMoreMessages }" v-if="existMoreMessages" @click="fetchMoreMessages" :disabled="fetchingMoreMessages">
|
||||
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
||||
</button>
|
||||
|
|
@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
|
|||
import XForm from './messaging-room.form.vue';
|
||||
import { url } from '../../../config';
|
||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faFlag } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/messaging-room.vue'),
|
||||
|
|
@ -54,7 +55,7 @@ export default Vue.extend({
|
|||
connection: null,
|
||||
showIndicator: false,
|
||||
timer: null,
|
||||
faArrowCircleDown
|
||||
faArrowCircleDown, faFlag
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<section>
|
||||
<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
|
||||
<ui-input v-model="endpoint">
|
||||
<ui-input v-model="endpoint" :datalist="endpoints">
|
||||
<span>{{ $t('console.endpoint') }}</span>
|
||||
</ui-input>
|
||||
<ui-textarea v-model="body">
|
||||
|
|
@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
|
|||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/api-settings.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
endpoint: '',
|
||||
body: '{}',
|
||||
res: null,
|
||||
sending: false
|
||||
sending: false,
|
||||
endpoints: []
|
||||
};
|
||||
},
|
||||
|
||||
created() {
|
||||
this.$root.api('endpoints').then(endpoints => {
|
||||
this.endpoints = endpoints;
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
regenerateToken() {
|
||||
this.$root.dialog({
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="$emit('keydown', $event)"
|
||||
:list="id"
|
||||
>
|
||||
<input v-else ref="input"
|
||||
:type="type"
|
||||
|
|
@ -37,7 +38,11 @@
|
|||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
@keydown="$emit('keydown', $event)"
|
||||
:list="id"
|
||||
>
|
||||
<datalist :id="id" v-if="datalist">
|
||||
<option v-for="data in datalist" :value="data"/>
|
||||
</datalist>
|
||||
</template>
|
||||
<template v-else>
|
||||
<input ref="input"
|
||||
|
|
@ -130,6 +135,10 @@ export default Vue.extend({
|
|||
required: false,
|
||||
default: false
|
||||
},
|
||||
datalist: {
|
||||
type: Array,
|
||||
required: false,
|
||||
},
|
||||
inline: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
|
|
@ -147,7 +156,8 @@ export default Vue.extend({
|
|||
return {
|
||||
v: this.value,
|
||||
focused: false,
|
||||
passwordStrength: ''
|
||||
passwordStrength: '',
|
||||
id: Math.random().toString()
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
|
|||
95
src/client/app/common/views/components/user-lists.vue
Normal file
95
src/client/app/common/views/components/user-lists.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="xkxvokkjlptzyewouewmceqcxhpgzprp">
|
||||
<button class="ui" @click="add">{{ $t('create-list') }}</button>
|
||||
<a v-for="list in lists" :key="list.id" @click="choice(list)">{{ list.name }}</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/user-lists.vue'),
|
||||
data() {
|
||||
return {
|
||||
fetching: true,
|
||||
lists: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$root.api('users/lists/list').then(lists => {
|
||||
this.fetching = false;
|
||||
this.lists = lists;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
this.$root.dialog({
|
||||
title: this.$t('list-name'),
|
||||
input: true
|
||||
}).then(async ({ canceled, result: title }) => {
|
||||
if (canceled) return;
|
||||
const list = await this.$root.api('users/lists/create', {
|
||||
title
|
||||
});
|
||||
|
||||
this.lists.push(list)
|
||||
this.$emit('choosen', list);
|
||||
});
|
||||
},
|
||||
choice(list) {
|
||||
this.$emit('choosen', list);
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
.xkxvokkjlptzyewouewmceqcxhpgzprp
|
||||
padding 16px
|
||||
background: var(--bg)
|
||||
|
||||
> button
|
||||
display block
|
||||
margin-bottom 16px
|
||||
color var(--primaryForeground)
|
||||
background var(--primary)
|
||||
width 100%
|
||||
border-radius 38px
|
||||
user-select none
|
||||
cursor pointer
|
||||
padding 0 16px
|
||||
min-width 100px
|
||||
line-height 38px
|
||||
font-size 14px
|
||||
font-weight 700
|
||||
|
||||
&:hover
|
||||
background var(--primaryLighten10)
|
||||
|
||||
&:active
|
||||
background var(--primaryDarken10)
|
||||
|
||||
a
|
||||
display block
|
||||
margin 8px 0
|
||||
padding 8px
|
||||
color var(--text)
|
||||
background var(--face)
|
||||
box-shadow 0 2px 16px var(--reversiListItemShadow)
|
||||
border-radius 6px
|
||||
cursor pointer
|
||||
line-height 32px
|
||||
|
||||
*
|
||||
pointer-events none
|
||||
user-select none
|
||||
|
||||
&:hover
|
||||
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.05)
|
||||
|
||||
&:active
|
||||
box-shadow 0 0 0 100px inset rgba(0, 0, 0, 0.1)
|
||||
|
||||
</style>
|
||||
|
|
@ -89,8 +89,10 @@ export default Vue.extend({
|
|||
});
|
||||
},
|
||||
|
||||
toggleMute() {
|
||||
async toggleMute() {
|
||||
if (this.user.isMuted) {
|
||||
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
|
||||
|
||||
this.$root.api('mute/delete', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -102,6 +104,8 @@ export default Vue.extend({
|
|||
});
|
||||
});
|
||||
} else {
|
||||
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
|
||||
|
||||
this.$root.api('mute/create', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -115,8 +119,10 @@ export default Vue.extend({
|
|||
}
|
||||
},
|
||||
|
||||
toggleBlock() {
|
||||
async toggleBlock() {
|
||||
if (this.user.isBlocking) {
|
||||
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
|
||||
|
||||
this.$root.api('blocking/delete', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -128,6 +134,8 @@ export default Vue.extend({
|
|||
});
|
||||
});
|
||||
} else {
|
||||
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
|
||||
|
||||
this.$root.api('blocking/create', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -164,7 +172,9 @@ export default Vue.extend({
|
|||
});
|
||||
},
|
||||
|
||||
toggleSilence() {
|
||||
async toggleSilence() {
|
||||
if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
|
||||
|
||||
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -181,7 +191,9 @@ export default Vue.extend({
|
|||
});
|
||||
},
|
||||
|
||||
toggleSuspend() {
|
||||
async toggleSuspend() {
|
||||
if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
|
||||
|
||||
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
||||
userId: this.user.id
|
||||
}).then(() => {
|
||||
|
|
@ -196,7 +208,18 @@ export default Vue.extend({
|
|||
text: e
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async getConfirmed(text: string): Promise<Boolean> {
|
||||
const confirm = await this.$root.dialog({
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
title: 'confirm',
|
||||
text,
|
||||
});
|
||||
|
||||
return !confirm.canceled;
|
||||
},
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue