mirror of
https://github.com/misskey-dev/misskey.git
synced 2024-12-14 16:35:53 +01:00
Merge branch 'develop'
This commit is contained in:
commit
7b44727b23
39 changed files with 556 additions and 260 deletions
16
CHANGELOG.md
16
CHANGELOG.md
|
@ -5,6 +5,22 @@ If you encounter any problems with updating, please try the following:
|
||||||
1. `npm run clean` or `npm run cleanall`
|
1. `npm run clean` or `npm run cleanall`
|
||||||
2. Retry update (Don't forget `npm i`)
|
2. Retry update (Don't forget `npm i`)
|
||||||
|
|
||||||
|
11.2.0 (2019/04/18)
|
||||||
|
-------------------
|
||||||
|
### Improvements
|
||||||
|
* 検索で日付(日時)を入力するとタイムラインをその時点まで遡るように
|
||||||
|
* APIコンソールでエンドポイントをサジェストするように
|
||||||
|
* モバイル版でドライブのメニューを使いやすく
|
||||||
|
* サイレンス時に確認を表示するように
|
||||||
|
* ユーザーメニューでブロックなどの操作を行う時に確認するように
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
* アプリケーション連携画面でパーミッションが表示されない問題を修正
|
||||||
|
* アンケートウィジットでもMFMを使用するように
|
||||||
|
* フォローしてないユーザーのホーム投稿がSTLに流れてくる問題を修正
|
||||||
|
* モバイル版でウィジェットを設定できない問題を修正
|
||||||
|
* スプラッシュがクリックに反応するように
|
||||||
|
|
||||||
11.1.6 (2019/04/18)
|
11.1.6 (2019/04/18)
|
||||||
-------------------
|
-------------------
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
|
@ -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:
|
||||||
|
@ -527,8 +528,12 @@ common/views/components/user-menu.vue:
|
||||||
mention: "メンション"
|
mention: "メンション"
|
||||||
mute: "ミュート"
|
mute: "ミュート"
|
||||||
unmute: "ミュート解除"
|
unmute: "ミュート解除"
|
||||||
|
mute-confirm: "このユーザーをミュートしますか?"
|
||||||
|
unmute-confirm: "このユーザーをミュート解除しますか?"
|
||||||
block: "ブロック"
|
block: "ブロック"
|
||||||
unblock: "ブロック解除"
|
unblock: "ブロック解除"
|
||||||
|
block-confirm: "このユーザーをブロックしますか?"
|
||||||
|
unblock-confirm: "このユーザーをブロック解除しますか?"
|
||||||
push-to-list: "リストに追加"
|
push-to-list: "リストに追加"
|
||||||
select-list: "リストを選択してください"
|
select-list: "リストを選択してください"
|
||||||
report-abuse: "スパムを報告"
|
report-abuse: "スパムを報告"
|
||||||
|
@ -536,8 +541,12 @@ common/views/components/user-menu.vue:
|
||||||
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
|
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
|
||||||
silence: "サイレンス"
|
silence: "サイレンス"
|
||||||
unsilence: "サイレンス解除"
|
unsilence: "サイレンス解除"
|
||||||
|
silence-confirm: "このユーザーをサイレンスしますか?"
|
||||||
|
unsilence-confirm: "このユーザーをサイレンス解除しますか?"
|
||||||
suspend: "凍結"
|
suspend: "凍結"
|
||||||
unsuspend: "凍結解除"
|
unsuspend: "凍結解除"
|
||||||
|
suspend-confirm: "このユーザーを凍結しますか?"
|
||||||
|
unsuspend-confirm: "このユーザーを凍結解除しますか?"
|
||||||
|
|
||||||
common/views/components/poll.vue:
|
common/views/components/poll.vue:
|
||||||
vote-to: "「{}」に投票する"
|
vote-to: "「{}」に投票する"
|
||||||
|
@ -739,6 +748,10 @@ common/views/components/user-list-editor.vue:
|
||||||
delete-are-you-sure: "リスト「$1」を削除しますか?"
|
delete-are-you-sure: "リスト「$1」を削除しますか?"
|
||||||
deleted: "削除しました"
|
deleted: "削除しました"
|
||||||
|
|
||||||
|
common/views/components/user-lists.vue:
|
||||||
|
create-list: "リストを作成"
|
||||||
|
list-name: "リスト名"
|
||||||
|
|
||||||
common/views/widgets/broadcast.vue:
|
common/views/widgets/broadcast.vue:
|
||||||
fetching: "確認中"
|
fetching: "確認中"
|
||||||
no-broadcasts: "お知らせはありません"
|
no-broadcasts: "お知らせはありません"
|
||||||
|
@ -1145,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue:
|
||||||
|
|
||||||
desktop/views/components/user-lists-window.vue:
|
desktop/views/components/user-lists-window.vue:
|
||||||
title: "リスト"
|
title: "リスト"
|
||||||
create-list: "リストを作成"
|
|
||||||
list-name: "リスト名"
|
|
||||||
|
|
||||||
desktop/views/components/user-preview.vue:
|
desktop/views/components/user-preview.vue:
|
||||||
notes: "投稿"
|
notes: "投稿"
|
||||||
|
@ -1336,7 +1347,9 @@ admin/views/users.vue:
|
||||||
unsuspend-confirm: "凍結を解除しますか?"
|
unsuspend-confirm: "凍結を解除しますか?"
|
||||||
unsuspended: "凍結を解除しました"
|
unsuspended: "凍結を解除しました"
|
||||||
make-silence: "サイレンス"
|
make-silence: "サイレンス"
|
||||||
|
silence-confirm: "サイレンスしますか?"
|
||||||
unmake-silence: "サイレンスの解除"
|
unmake-silence: "サイレンスの解除"
|
||||||
|
unsilence-confirm: "サイレンスを解除しますか?"
|
||||||
verify: "公式アカウントにする"
|
verify: "公式アカウントにする"
|
||||||
verify-confirm: "公式アカウントにしますか?"
|
verify-confirm: "公式アカウントにしますか?"
|
||||||
verified: "公式アカウントにしました"
|
verified: "公式アカウントにしました"
|
||||||
|
@ -1573,12 +1586,11 @@ mobile/views/components/drive.vue:
|
||||||
file-count: "ファイル"
|
file-count: "ファイル"
|
||||||
nothing-in-drive: "ドライブには何もありません"
|
nothing-in-drive: "ドライブには何もありません"
|
||||||
folder-is-empty: "このフォルダは空です"
|
folder-is-empty: "このフォルダは空です"
|
||||||
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
|
|
||||||
deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。"
|
|
||||||
folder-name: "フォルダー名"
|
folder-name: "フォルダー名"
|
||||||
here-is-root: "現在いる場所はルートで、フォルダではありません。"
|
here-is-root: "現在いる場所はルートで、フォルダではありません。"
|
||||||
url-prompt: "アップロードしたいファイルのURL"
|
url-prompt: "アップロードしたいファイルのURL"
|
||||||
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
|
||||||
|
folder-name-cannot-empty: "フォルダ名を空白にすることはできません。"
|
||||||
|
|
||||||
mobile/views/components/drive-file-chooser.vue:
|
mobile/views/components/drive-file-chooser.vue:
|
||||||
select-file: "ファイルを選択"
|
select-file: "ファイルを選択"
|
||||||
|
@ -1668,9 +1680,17 @@ mobile/views/components/ui.nav.vue:
|
||||||
admin: "管理"
|
admin: "管理"
|
||||||
about: "Misskeyについて"
|
about: "Misskeyについて"
|
||||||
|
|
||||||
|
mobile/views/pages/drive.vue:
|
||||||
|
contextmenu:
|
||||||
|
upload: "ファイルをアップロード"
|
||||||
|
url-upload: "ファイルをURLでアップロード"
|
||||||
|
create-folder: "フォルダーを作成"
|
||||||
|
rename-folder: "フォルダー名を変更"
|
||||||
|
move-folder: "このフォルダを移動"
|
||||||
|
delete-folder: "このフォルダを削除"
|
||||||
|
|
||||||
mobile/views/pages/user-lists.vue:
|
mobile/views/pages/user-lists.vue:
|
||||||
title: "リスト"
|
title: "リスト"
|
||||||
enter-list-name: "リスト名を入力してください"
|
|
||||||
|
|
||||||
mobile/views/pages/signup.vue:
|
mobile/views/pages/signup.vue:
|
||||||
lets-start: "📦 始めましょう"
|
lets-start: "📦 始めましょう"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "11.1.6",
|
"version": "11.2.0",
|
||||||
"codename": "daybreak",
|
"codename": "daybreak",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -232,6 +232,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
async silenceUser() {
|
async silenceUser() {
|
||||||
|
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
|
||||||
|
|
||||||
const process = async () => {
|
const process = async () => {
|
||||||
await this.$root.api('admin/silence-user', { userId: this.user.id });
|
await this.$root.api('admin/silence-user', { userId: this.user.id });
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
@ -251,6 +253,8 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
async unsilenceUser() {
|
async unsilenceUser() {
|
||||||
|
if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
|
||||||
|
|
||||||
const process = async () => {
|
const process = async () => {
|
||||||
await this.$root.api('admin/unsilence-user', { userId: this.user.id });
|
await this.$root.api('admin/unsilence-user', { userId: this.user.id });
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
|
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() {
|
||||||
|
if (this.cancelableByBgClick) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onInputKeydown(e) {
|
onInputKeydown(e) {
|
||||||
|
@ -183,9 +197,6 @@ export default Vue.extend({
|
||||||
height 100%
|
height 100%
|
||||||
|
|
||||||
&.splash
|
&.splash
|
||||||
&, *
|
|
||||||
pointer-events none !important
|
|
||||||
|
|
||||||
> .main
|
> .main
|
||||||
min-width 0
|
min-width 0
|
||||||
width initial
|
width initial
|
||||||
|
@ -243,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
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<p class="init" v-if="init"><fa icon="spinner .spin"/>{{ $t('@.loading') }}</p>
|
<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="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">
|
<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') }}
|
<template v-if="fetchingMoreMessages"><fa icon="spinner" pulse fixed-width/></template>{{ fetchingMoreMessages ? $t('@.loading') : $t('@.load-more') }}
|
||||||
</button>
|
</button>
|
||||||
|
@ -35,6 +35,7 @@ import XMessage from './messaging-room.message.vue';
|
||||||
import XForm from './messaging-room.form.vue';
|
import XForm from './messaging-room.form.vue';
|
||||||
import { url } from '../../../config';
|
import { url } from '../../../config';
|
||||||
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
import { faArrowCircleDown } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import { faFlag } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('common/views/components/messaging-room.vue'),
|
i18n: i18n('common/views/components/messaging-room.vue'),
|
||||||
|
@ -54,7 +55,7 @@ export default Vue.extend({
|
||||||
connection: null,
|
connection: null,
|
||||||
showIndicator: false,
|
showIndicator: false,
|
||||||
timer: null,
|
timer: null,
|
||||||
faArrowCircleDown
|
faArrowCircleDown, faFlag
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<header><fa icon="terminal"/> {{ $t('console.title') }}</header>
|
<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>
|
<span>{{ $t('console.endpoint') }}</span>
|
||||||
</ui-input>
|
</ui-input>
|
||||||
<ui-textarea v-model="body">
|
<ui-textarea v-model="body">
|
||||||
|
@ -39,15 +39,23 @@ import * as JSON5 from 'json5';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('common/views/components/api-settings.vue'),
|
i18n: i18n('common/views/components/api-settings.vue'),
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
body: '{}',
|
body: '{}',
|
||||||
res: null,
|
res: null,
|
||||||
sending: false
|
sending: false,
|
||||||
|
endpoints: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.api('endpoints').then(endpoints => {
|
||||||
|
this.endpoints = endpoints;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
regenerateToken() {
|
regenerateToken() {
|
||||||
this.$root.dialog({
|
this.$root.dialog({
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
@focus="focused = true"
|
@focus="focused = true"
|
||||||
@blur="focused = false"
|
@blur="focused = false"
|
||||||
@keydown="$emit('keydown', $event)"
|
@keydown="$emit('keydown', $event)"
|
||||||
|
:list="id"
|
||||||
>
|
>
|
||||||
<input v-else ref="input"
|
<input v-else ref="input"
|
||||||
:type="type"
|
:type="type"
|
||||||
|
@ -37,7 +38,11 @@
|
||||||
@focus="focused = true"
|
@focus="focused = true"
|
||||||
@blur="focused = false"
|
@blur="focused = false"
|
||||||
@keydown="$emit('keydown', $event)"
|
@keydown="$emit('keydown', $event)"
|
||||||
|
:list="id"
|
||||||
>
|
>
|
||||||
|
<datalist :id="id" v-if="datalist">
|
||||||
|
<option v-for="data in datalist" :value="data"/>
|
||||||
|
</datalist>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<input ref="input"
|
<input ref="input"
|
||||||
|
@ -130,6 +135,10 @@ export default Vue.extend({
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
datalist: {
|
||||||
|
type: Array,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
inline: {
|
inline: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: false,
|
required: false,
|
||||||
|
@ -147,7 +156,8 @@ export default Vue.extend({
|
||||||
return {
|
return {
|
||||||
v: this.value,
|
v: this.value,
|
||||||
focused: false,
|
focused: false,
|
||||||
passwordStrength: ''
|
passwordStrength: '',
|
||||||
|
id: Math.random().toString()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
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 (this.user.isMuted) {
|
||||||
|
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('mute/delete', {
|
this.$root.api('mute/delete', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -102,6 +104,8 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('mute/create', {
|
this.$root.api('mute/create', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -115,8 +119,10 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleBlock() {
|
async toggleBlock() {
|
||||||
if (this.user.isBlocking) {
|
if (this.user.isBlocking) {
|
||||||
|
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('blocking/delete', {
|
this.$root.api('blocking/delete', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -128,6 +134,8 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
|
||||||
|
|
||||||
this.$root.api('blocking/create', {
|
this.$root.api('blocking/create', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).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', {
|
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).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', {
|
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
|
||||||
userId: this.user.id
|
userId: this.user.id
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -196,7 +208,18 @@ export default Vue.extend({
|
||||||
text: e
|
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>
|
</script>
|
||||||
|
|
|
@ -123,7 +123,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchMore() {
|
fetchMore() {
|
||||||
if (!this.more || this.moreFetching) 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 => {
|
||||||
this.notes = this.notes.concat(x.notes);
|
this.notes = this.notes.concat(x.notes);
|
||||||
|
|
|
@ -90,9 +90,8 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import MkUserListsWindow from './user-lists-window.vue';
|
import MkUserListsWindow from './user-lists-window.vue';
|
||||||
import MkUserListWindow from './user-list-window.vue';
|
|
||||||
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
import MkFollowRequestsWindow from './received-follow-requests-window.vue';
|
||||||
import MkSettingsWindow from './settings-window.vue';
|
// import MkSettingsWindow from './settings-window.vue';
|
||||||
import MkDriveWindow from './drive-window.vue';
|
import MkDriveWindow from './drive-window.vue';
|
||||||
import contains from '../../../common/scripts/contains';
|
import contains from '../../../common/scripts/contains';
|
||||||
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
@ -143,12 +142,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
list() {
|
list() {
|
||||||
this.close();
|
this.close();
|
||||||
const w = this.$root.new(MkUserListsWindow);
|
this.$root.new(MkUserListsWindow);
|
||||||
w.$once('choosen', list => {
|
|
||||||
this.$root.new(MkUserListWindow, {
|
|
||||||
list
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
followRequests() {
|
followRequests() {
|
||||||
this.close();
|
this.close();
|
||||||
|
|
|
@ -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();
|
|
||||||
if (q.startsWith('@')) {
|
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.wait = true;
|
this.wait = true;
|
||||||
try {
|
search(this, this.q).finally(() => {
|
||||||
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)}`);
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -148,10 +148,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
list() {
|
list() {
|
||||||
const w = this.$root.new(MkUserListsWindow);
|
this.$root.new(MkUserListsWindow);
|
||||||
w.$once('choosen', list => {
|
|
||||||
this.$router.push(`i/lists/${ list.id }`);
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
followRequests() {
|
followRequests() {
|
||||||
|
|
|
@ -18,10 +18,12 @@ export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
connection: null,
|
connection: null,
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: cursor ? cursor : undefined,
|
untilId: cursor ? cursor : undefined,
|
||||||
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
|
@ -46,6 +48,10 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.dispose();
|
this.connection.dispose();
|
||||||
|
@ -68,6 +74,10 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
onUserRemoved() {
|
onUserRemoved() {
|
||||||
(this.$refs.timeline as any).reload();
|
(this.$refs.timeline as any).reload();
|
||||||
|
},
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,85 +1,36 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
<mk-window ref="window" width="450px" height="500px" @closed="destroyDom">
|
||||||
<template #header><fa icon="list"/> {{ $t('title') }}</template>
|
<template #header><fa icon="list"/> {{ $t('title') }}</template>
|
||||||
|
<x-lists :class="$style.content" @choosen="choosen"/>
|
||||||
<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>
|
|
||||||
</mk-window>
|
</mk-window>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
|
import MkUserListWindow from './user-list-window.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('desktop/views/components/user-lists-window.vue'),
|
i18n: i18n('desktop/views/components/user-lists-window.vue'),
|
||||||
data() {
|
components: {
|
||||||
return {
|
XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
|
||||||
fetching: true,
|
|
||||||
lists: []
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.$root.api('users/lists/list').then(lists => {
|
|
||||||
this.fetching = false;
|
|
||||||
this.lists = lists;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
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.$emit('choosen', list);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
choice(list) {
|
|
||||||
this.$emit('choosen', list);
|
|
||||||
},
|
|
||||||
close() {
|
close() {
|
||||||
(this as any).$refs.window.close();
|
(this as any).$refs.window.close();
|
||||||
|
},
|
||||||
|
choosen(list) {
|
||||||
|
this.$root.new(MkUserListWindow, {
|
||||||
|
list
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" module>
|
||||||
.xkxvokkjlptzyewouewmceqcxhpgzprp
|
.content
|
||||||
padding 16px
|
height 100%
|
||||||
|
overflow auto
|
||||||
> 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
|
|
||||||
padding 16px
|
|
||||||
border solid 1px var(--faceDivider)
|
|
||||||
border-radius 4px
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,7 +36,8 @@ export default Vue.extend({
|
||||||
includeReplies: this.mode == 'with-replies',
|
includeReplies: this.mode == 'with-replies',
|
||||||
includeMyRenotes: this.mode != 'my-posts',
|
includeMyRenotes: this.mode != 'my-posts',
|
||||||
withFiles: this.mode == 'with-media',
|
withFiles: this.mode == 'with-media',
|
||||||
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
|
untilId: cursor ? cursor : undefined
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
notes.pop();
|
notes.pop();
|
||||||
|
@ -62,10 +63,11 @@ export default Vue.extend({
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
document.addEventListener('keydown', this.onDocumentKeydown);
|
document.addEventListener('keydown', this.onDocumentKeydown);
|
||||||
},
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
beforeDestroy() {
|
this.$root.$off('warp', this.warp);
|
||||||
document.removeEventListener('keydown', this.onDocumentKeydown);
|
document.removeEventListener('keydown', this.onDocumentKeydown);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
|
|
||||||
<div class="mkw-polls--body">
|
<div class="mkw-polls--body">
|
||||||
<div class="poll" v-if="!fetching && poll != null">
|
<div class="poll" v-if="!fetching && poll != null">
|
||||||
<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p>
|
<p v-if="poll.text"><router-link :to="poll | notePage">
|
||||||
|
<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
|
||||||
|
</router-link></p>
|
||||||
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
|
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
|
||||||
<mk-poll :note="poll"/>
|
<mk-poll :note="poll"/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,7 +14,7 @@ export default define({
|
||||||
}).extend({
|
}).extend({
|
||||||
methods: {
|
methods: {
|
||||||
chosen(date) {
|
chosen(date) {
|
||||||
this.$emit('chosen', date);
|
this.$root.$emit('warp', date);
|
||||||
},
|
},
|
||||||
func() {
|
func() {
|
||||||
if (this.props.design == 5) {
|
if (this.props.design == 5) {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -379,44 +379,31 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
openContextMenu() {
|
|
||||||
const fn = window.prompt(this.$t('prompt'));
|
|
||||||
if (fn == null || fn == '') return;
|
|
||||||
switch (fn) {
|
|
||||||
case '1':
|
|
||||||
this.selectLocalFile();
|
|
||||||
break;
|
|
||||||
case '2':
|
|
||||||
this.urlUpload();
|
|
||||||
break;
|
|
||||||
case '3':
|
|
||||||
this.createFolder();
|
|
||||||
break;
|
|
||||||
case '4':
|
|
||||||
this.renameFolder();
|
|
||||||
break;
|
|
||||||
case '5':
|
|
||||||
this.moveFolder();
|
|
||||||
break;
|
|
||||||
case '6':
|
|
||||||
this.deleteFolder();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
selectLocalFile() {
|
selectLocalFile() {
|
||||||
(this.$refs.file as any).click();
|
(this.$refs.file as any).click();
|
||||||
},
|
},
|
||||||
|
|
||||||
createFolder() {
|
createFolder() {
|
||||||
const name = window.prompt(this.$t('folder-name'));
|
this.$root.dialog({
|
||||||
if (name == null || name == '') return;
|
title: this.$t('folder-name')
|
||||||
|
input: {
|
||||||
|
default: this.folder.name
|
||||||
|
}
|
||||||
|
}).then(({ result: name }) => {
|
||||||
|
if (!name) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('folder-name-cannot-empty')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$root.api('drive/folders/create', {
|
this.$root.api('drive/folders/create', {
|
||||||
name: name,
|
name: name,
|
||||||
parentId: this.folder ? this.folder.id : undefined
|
parentId: this.folder ? this.folder.id : undefined
|
||||||
}).then(folder => {
|
}).then(folder => {
|
||||||
this.addFolder(folder, true);
|
this.addFolder(folder, true);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
renameFolder() {
|
renameFolder() {
|
||||||
|
@ -427,14 +414,26 @@ export default Vue.extend({
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const name = window.prompt(this.$t('folder-name'), this.folder.name);
|
this.$root.dialog({
|
||||||
if (name == null || name == '') return;
|
title: this.$t('folder-name')
|
||||||
|
input: {
|
||||||
|
default: this.folder.name
|
||||||
|
}
|
||||||
|
}).then(({ result: name }) => {
|
||||||
|
if (!name) {
|
||||||
|
this.$root.dialog({
|
||||||
|
type: 'error',
|
||||||
|
text: this.$t('cannot-empty')
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$root.api('drive/folders/update', {
|
this.$root.api('drive/folders/update', {
|
||||||
name: name,
|
name: name,
|
||||||
folderId: this.folder.id
|
folderId: this.folder.id
|
||||||
}).then(folder => {
|
}).then(folder => {
|
||||||
this.cd(folder);
|
this.cd(folder);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
moveFolder() {
|
moveFolder() {
|
||||||
|
|
|
@ -124,7 +124,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchMore() {
|
fetchMore() {
|
||||||
if (!this.more || this.moreFetching) 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 => {
|
||||||
this.notes = this.notes.concat(x.notes);
|
this.notes = this.notes.concat(x.notes);
|
||||||
|
|
|
@ -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();
|
|
||||||
if (q.startsWith('@')) {
|
|
||||||
this.$router.push(`/${q}`);
|
|
||||||
} else if (q.startsWith('#')) {
|
|
||||||
this.$router.push(`/tags/${encodeURIComponent(q.substr(1))}`);
|
|
||||||
} else if (q.startsWith('https://')) {
|
|
||||||
this.searching = true;
|
this.searching = true;
|
||||||
try {
|
search(this, query).finally(() => {
|
||||||
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)}`);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -15,10 +15,12 @@ export default Vue.extend({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
connection: null,
|
connection: null,
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
makePromise: cursor => this.$root.api('notes/user-list-timeline', {
|
||||||
listId: this.list.id,
|
listId: this.list.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
untilId: cursor ? cursor : undefined,
|
untilId: cursor ? cursor : undefined,
|
||||||
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
includeMyRenotes: this.$store.state.settings.showMyRenotes,
|
||||||
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
|
||||||
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
includeLocalRenotes: this.$store.state.settings.showLocalRenotes
|
||||||
|
@ -45,6 +47,11 @@ export default Vue.extend({
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
@ -73,6 +80,11 @@ export default Vue.extend({
|
||||||
|
|
||||||
onUserRemoved() {
|
onUserRemoved() {
|
||||||
(this.$refs.timeline as any).reload();
|
(this.$refs.timeline as any).reload();
|
||||||
|
},
|
||||||
|
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,11 +17,13 @@ export default Vue.extend({
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
date: null,
|
||||||
makePromise: cursor => this.$root.api('users/notes', {
|
makePromise: cursor => this.$root.api('users/notes', {
|
||||||
userId: this.user.id,
|
userId: this.user.id,
|
||||||
limit: fetchLimit + 1,
|
limit: fetchLimit + 1,
|
||||||
withFiles: this.withMedia,
|
withFiles: this.withMedia,
|
||||||
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
|
untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined),
|
||||||
|
untilId: cursor ? cursor : undefined
|
||||||
}).then(notes => {
|
}).then(notes => {
|
||||||
if (notes.length == fetchLimit + 1) {
|
if (notes.length == fetchLimit + 1) {
|
||||||
notes.pop();
|
notes.pop();
|
||||||
|
@ -37,6 +39,20 @@ export default Vue.extend({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
this.$root.$on('warp', this.warp);
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
this.$root.$off('warp', this.warp);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
warp(date) {
|
||||||
|
this.date = date;
|
||||||
|
(this.$refs.timeline as any).reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
|
<template v-if="file"><mk-file-type-icon data-icon :type="file.type" style="margin-right:4px;"/>{{ file.name }}</template>
|
||||||
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
|
<template v-if="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</template>
|
||||||
</template>
|
</template>
|
||||||
<template #func><button @click="fn"><fa icon="ellipsis-h"/></button></template>
|
<template #func v-if="folder || (!folder && !file)"><button @click="openContextMenu" ref="contextSource"><fa icon="ellipsis-h"/></button></template>
|
||||||
<x-drive
|
<x-drive
|
||||||
ref="browser"
|
ref="browser"
|
||||||
:init-folder="initFolder"
|
:init-folder="initFolder"
|
||||||
|
@ -26,9 +26,12 @@
|
||||||
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 XMenu from '../../../common/views/components/menu.vue';
|
||||||
|
import { faTrashAlt } from '@fortawesome/free-regular-svg-icons';
|
||||||
|
import { faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n(),
|
i18n: i18n('mobile/views/pages/drive.vue'),
|
||||||
components: {
|
components: {
|
||||||
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
XDrive: () => import('../components/drive.vue').then(m => m.default),
|
||||||
},
|
},
|
||||||
|
@ -63,9 +66,6 @@ export default Vue.extend({
|
||||||
(this.$refs as any).browser.goRoot(true);
|
(this.$refs as any).browser.goRoot(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fn() {
|
|
||||||
(this.$refs as any).browser.openContextMenu();
|
|
||||||
},
|
|
||||||
onMoveRoot(silent) {
|
onMoveRoot(silent) {
|
||||||
const title = `${this.$root.instanceName} Drive`;
|
const title = `${this.$root.instanceName} Drive`;
|
||||||
|
|
||||||
|
@ -104,6 +104,42 @@ export default Vue.extend({
|
||||||
|
|
||||||
this.file = file;
|
this.file = file;
|
||||||
this.folder = null;
|
this.folder = null;
|
||||||
|
},
|
||||||
|
openContextMenu() {
|
||||||
|
this.$root.new(XMenu, {
|
||||||
|
items: [{
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.upload'),
|
||||||
|
icon: 'upload',
|
||||||
|
action: this.$refs.browser.selectLocalFile
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.url-upload'),
|
||||||
|
icon: faCloudUploadAlt,
|
||||||
|
action: this.$refs.browser.urlUpload
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.create-folder'),
|
||||||
|
icon: ['far', 'folder'],
|
||||||
|
action: this.$refs.browser.createFolder
|
||||||
|
}, ...(this.folder ? [{
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.rename-folder'),
|
||||||
|
icon: 'i-cursor',
|
||||||
|
action: this.$refs.browser.renameFolder
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.move-folder'),
|
||||||
|
icon: ['far', 'folder-open'],
|
||||||
|
action: this.$refs.browser.moveFolder
|
||||||
|
}, {
|
||||||
|
type: 'item',
|
||||||
|
text: this.$t('contextmenu.delete-folder'),
|
||||||
|
icon: faTrashAlt,
|
||||||
|
action: this.$refs.browser.deleteFolder
|
||||||
|
}] : [])],
|
||||||
|
source: this.$refs.contextSource,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -1,20 +1,15 @@
|
||||||
<template>
|
<template>
|
||||||
<mk-ui>
|
<mk-ui>
|
||||||
<template #header><fa icon="list"/>{{ $t('title') }}</template>
|
<template #header><fa icon="list"/>{{ $t('title') }}</template>
|
||||||
<template #func><button @click="fn"><fa icon="plus"/></button></template>
|
<template #func><button @click="$refs.lists.add()"><fa icon="plus"/></button></template>
|
||||||
|
|
||||||
<main>
|
<x-lists ref="lists" @choosen="choosen"/>
|
||||||
<ul>
|
|
||||||
<li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
</mk-ui>
|
</mk-ui>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import i18n from '../../../i18n';
|
import i18n from '../../../i18n';
|
||||||
import Progress from '../../../common/scripts/loading';
|
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
i18n: i18n('mobile/views/pages/user-lists.vue'),
|
i18n: i18n('mobile/views/pages/user-lists.vue'),
|
||||||
|
@ -24,31 +19,16 @@ export default Vue.extend({
|
||||||
lists: []
|
lists: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default)
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
document.title = this.$t('title');
|
document.title = this.$t('title');
|
||||||
|
|
||||||
Progress.start();
|
|
||||||
|
|
||||||
this.$root.api('users/lists/list').then(lists => {
|
|
||||||
this.fetching = false;
|
|
||||||
this.lists = lists;
|
|
||||||
|
|
||||||
Progress.done();
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fn() {
|
choosen(list) {
|
||||||
this.$root.dialog({
|
if (!list) return;
|
||||||
title: this.$t('enter-list-name'),
|
|
||||||
input: true
|
|
||||||
}).then(async ({ canceled, result: title }) => {
|
|
||||||
if (canceled) return;
|
|
||||||
const list = await this.$root.api('users/lists/create', {
|
|
||||||
title
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$router.push(`/i/lists/${list.id}`);
|
this.$router.push(`/i/lists/${list.id}`);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -72,13 +72,13 @@ export default Vue.extend({
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
widgets(): any[] {
|
widgets(): any[] {
|
||||||
return this.$store.state.settings.mobileHome;
|
return this.$store.state.device.mobileHome;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
if (this.widgets.length == 0) {
|
if (this.widgets.length == 0) {
|
||||||
this.widgets = [{
|
this.$store.commit('device/setMobileHome', [{
|
||||||
name: 'calendar',
|
name: 'calendar',
|
||||||
id: 'a', data: {}
|
id: 'a', data: {}
|
||||||
}, {
|
}, {
|
||||||
|
@ -96,8 +96,7 @@ export default Vue.extend({
|
||||||
}, {
|
}, {
|
||||||
name: 'version',
|
name: 'version',
|
||||||
id: 'g', data: {}
|
id: 'g', data: {}
|
||||||
}];
|
}]);
|
||||||
this.saveHome();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -123,7 +122,7 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
addWidget() {
|
addWidget() {
|
||||||
this.$store.commit('settings/addMobileHomeWidget', {
|
this.$store.commit('device/addMobileHomeWidget', {
|
||||||
name: this.widgetAdderSelected,
|
name: this.widgetAdderSelected,
|
||||||
id: uuid(),
|
id: uuid(),
|
||||||
data: {}
|
data: {}
|
||||||
|
@ -131,11 +130,11 @@ export default Vue.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
removeWidget(widget) {
|
removeWidget(widget) {
|
||||||
this.$store.commit('settings/removeMobileHomeWidget', widget);
|
this.$store.commit('device/removeMobileHomeWidget', widget);
|
||||||
},
|
},
|
||||||
|
|
||||||
saveHome() {
|
saveHome() {
|
||||||
this.$store.commit('settings/setMobileHome', this.widgets);
|
this.$store.commit('device/setMobileHome', this.widgets);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -358,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({
|
||||||
ctx.commit('set', x);
|
ctx.commit('set', x);
|
||||||
|
|
||||||
if (ctx.rootGetters.isSignedIn) {
|
if (ctx.rootGetters.isSignedIn) {
|
||||||
os.api('i/update_client_setting', {
|
os.api('i/update-client-setting', {
|
||||||
name: x.key,
|
name: x.key,
|
||||||
value: x.value
|
value: x.value
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,6 +26,7 @@ export class AppRepository extends Repository<App> {
|
||||||
id: app.id,
|
id: app.id,
|
||||||
name: app.name,
|
name: app.name,
|
||||||
callbackUrl: app.callbackUrl,
|
callbackUrl: app.callbackUrl,
|
||||||
|
permission: app.permission,
|
||||||
...(opts.includeSecret ? { secret: app.secret } : {}),
|
...(opts.includeSecret ? { secret: app.secret } : {}),
|
||||||
...(me ? {
|
...(me ? {
|
||||||
isAuthorized: await AccessTokens.count({
|
isAuthorized: await AccessTokens.count({
|
||||||
|
|
15
src/server/api/endpoints/endpoints.ts
Normal file
15
src/server/api/endpoints/endpoints.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import define from '../define';
|
||||||
|
import endpoints from '../endpoints';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
requireCredential: false,
|
||||||
|
|
||||||
|
tags: ['meta'],
|
||||||
|
|
||||||
|
params: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default define(meta, async () => {
|
||||||
|
return endpoints.map(x => x.name);
|
||||||
|
});
|
|
@ -17,6 +17,8 @@ export const meta = {
|
||||||
|
|
||||||
tags: ['notes'],
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
|
||||||
params: {
|
params: {
|
||||||
limit: {
|
limit: {
|
||||||
validator: $.optional.num.range(1, 100),
|
validator: $.optional.num.range(1, 100),
|
||||||
|
|
|
@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
//#region Construct query
|
//#region Construct query
|
||||||
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
|
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
|
||||||
.andWhere('note.userId = :userId', { userId: user.id })
|
.andWhere('note.userId = :userId', { userId: user.id })
|
||||||
.leftJoinAndSelect('note.user', 'user');
|
.leftJoinAndSelect('note.user', 'user');
|
||||||
|
|
||||||
|
|
|
@ -20,11 +20,11 @@ export default class extends Channel {
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
private async onNote(note: any) {
|
private async onNote(note: any) {
|
||||||
// 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ
|
// 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ
|
||||||
if (!(
|
if (!(
|
||||||
this.user!.id === note.userId ||
|
this.user!.id === note.userId ||
|
||||||
this.following.includes(note.userId) ||
|
this.following.includes(note.userId) ||
|
||||||
note.user.host == null
|
(note.user.host == null && note.visibility === 'public')
|
||||||
)) return;
|
)) return;
|
||||||
|
|
||||||
if (['followers', 'specified'].includes(note.visibility)) {
|
if (['followers', 'specified'].includes(note.visibility)) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ describe('Streaming', () => {
|
||||||
p.on('message', message => {
|
p.on('message', message => {
|
||||||
if (message === 'ok') {
|
if (message === 'ok') {
|
||||||
(p.channel as any).onread = () => {};
|
(p.channel as any).onread = () => {};
|
||||||
initDb(true).then(async connection => {
|
initDb(true).then(async (connection: any) => {
|
||||||
Followings = connection.getRepository(Following);
|
Followings = connection.getRepository(Following);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
@ -44,7 +44,7 @@ describe('Streaming', () => {
|
||||||
p.kill();
|
p.kill();
|
||||||
});
|
});
|
||||||
|
|
||||||
const follow = async (follower, followee) => {
|
const follow = async (follower: any, followee: any) => {
|
||||||
await Followings.save({
|
await Followings.save({
|
||||||
id: 'a',
|
id: 'a',
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
|
@ -484,6 +484,56 @@ describe('Streaming', () => {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('フォローしているユーザーのホーム投稿が流れる', () => new Promise(async done => {
|
||||||
|
const alice = await signup({ username: 'alice' });
|
||||||
|
const bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
// Alice が Bob をフォロー
|
||||||
|
await request('/following/create', {
|
||||||
|
userId: bob.id
|
||||||
|
}, alice);
|
||||||
|
|
||||||
|
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
|
||||||
|
if (type == 'note') {
|
||||||
|
assert.deepStrictEqual(body.userId, bob.id);
|
||||||
|
assert.deepStrictEqual(body.text, 'foo');
|
||||||
|
ws.close();
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ホーム投稿
|
||||||
|
post(bob, {
|
||||||
|
text: 'foo',
|
||||||
|
visibility: 'home'
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('フォローしていないローカルユーザーのホーム投稿は流れない', () => new Promise(async done => {
|
||||||
|
const alice = await signup({ username: 'alice' });
|
||||||
|
const bob = await signup({ username: 'bob' });
|
||||||
|
|
||||||
|
let fired = false;
|
||||||
|
|
||||||
|
const ws = await connectStream(alice, 'hybridTimeline', ({ type, body }) => {
|
||||||
|
if (type == 'note') {
|
||||||
|
fired = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ホーム投稿
|
||||||
|
post(bob, {
|
||||||
|
text: 'foo',
|
||||||
|
visibility: 'home'
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
assert.strictEqual(fired, false);
|
||||||
|
ws.close();
|
||||||
|
done();
|
||||||
|
}, 3000);
|
||||||
|
}));
|
||||||
|
|
||||||
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
|
it('フォローしていないローカルユーザーのフォロワー宛て投稿は流れない', () => new Promise(async done => {
|
||||||
const alice = await signup({ username: 'alice' });
|
const alice = await signup({ username: 'alice' });
|
||||||
const bob = await signup({ username: 'bob' });
|
const bob = await signup({ username: 'bob' });
|
||||||
|
|
|
@ -76,7 +76,7 @@ export const uploadFile = (user: any, path?: string): Promise<any> => new Promis
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function connectStream(user: any, channel: string, listener: any, params?: any): Promise<WebSocket> {
|
export function connectStream(user: any, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
|
const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue