From f7b1ef0690b7e30f5918ebf518854b28c0654157 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Apr 2019 03:14:04 +0900 Subject: [PATCH 01/20] =?UTF-8?q?=E3=82=A2=E3=83=B3=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=A6=E3=82=A3=E3=82=B8=E3=83=83=E3=83=88=E3=81=A7?= =?UTF-8?q?=E3=82=82MFM=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=20v11=20(#4741)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MFM in poll * use mfm --- src/client/app/desktop/views/widgets/polls.vue | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/client/app/desktop/views/widgets/polls.vue b/src/client/app/desktop/views/widgets/polls.vue index 3567cae5ed..c77762ecdf 100644 --- a/src/client/app/desktop/views/widgets/polls.vue +++ b/src/client/app/desktop/views/widgets/polls.vue @@ -11,7 +11,9 @@ <div class="mkw-polls--body"> <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> <mk-poll :note="poll"/> </div> From 704aabd703de2be4d06b399929e6f33525526b57 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 18 Apr 2019 03:32:45 +0900 Subject: [PATCH 02/20] Use menu instead of prompt Fix #4540, Fix #342 (#4575) * Use menu instead prompt * fix * https://bit.ly/2U0JuVt * fix --- locales/ja-JP.yml | 12 ++- .../app/mobile/views/components/drive.vue | 77 +++++++++---------- src/client/app/mobile/views/pages/drive.vue | 46 +++++++++-- 3 files changed, 89 insertions(+), 46 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index eb87d4f204..6d68f0ca8c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1573,12 +1573,11 @@ mobile/views/components/drive.vue: file-count: "ファイル" nothing-in-drive: "ドライブには何もありません" folder-is-empty: "このフォルダは空です" - prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>" - deletion-alert: "ごめんなさい!フォルダの削除は未実装です...。" folder-name: "フォルダー名" here-is-root: "現在いる場所はルートで、フォルダではありません。" url-prompt: "アップロードしたいファイルのURL" uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。" + folder-name-cannot-empty: "フォルダ名を空白にすることはできません。" mobile/views/components/drive-file-chooser.vue: select-file: "ファイルを選択" @@ -1668,6 +1667,15 @@ mobile/views/components/ui.nav.vue: admin: "管理" 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: title: "リスト" enter-list-name: "リスト名を入力してください" diff --git a/src/client/app/mobile/views/components/drive.vue b/src/client/app/mobile/views/components/drive.vue index 4772333862..b79c0b3806 100644 --- a/src/client/app/mobile/views/components/drive.vue +++ b/src/client/app/mobile/views/components/drive.vue @@ -379,43 +379,30 @@ 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() { (this.$refs.file as any).click(); }, createFolder() { - const name = window.prompt(this.$t('folder-name')); - if (name == null || name == '') return; - this.$root.api('drive/folders/create', { - name: name, - parentId: this.folder ? this.folder.id : undefined - }).then(folder => { - this.addFolder(folder, true); + this.$root.dialog({ + 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', { + name: name, + parentId: this.folder ? this.folder.id : undefined + }).then(folder => { + this.addFolder(folder, true); + }); }); }, @@ -427,13 +414,25 @@ export default Vue.extend({ }); return; } - const name = window.prompt(this.$t('folder-name'), this.folder.name); - if (name == null || name == '') return; - this.$root.api('drive/folders/update', { - name: name, - folderId: this.folder.id - }).then(folder => { - this.cd(folder); + this.$root.dialog({ + 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', { + name: name, + folderId: this.folder.id + }).then(folder => { + this.cd(folder); + }); }); }, diff --git a/src/client/app/mobile/views/pages/drive.vue b/src/client/app/mobile/views/pages/drive.vue index d2f51dc899..05163c6ed9 100644 --- a/src/client/app/mobile/views/pages/drive.vue +++ b/src/client/app/mobile/views/pages/drive.vue @@ -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="!folder && !file"><span style="margin-right:4px;"><fa icon="cloud"/></span>{{ $t('@.drive') }}</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 ref="browser" :init-folder="initFolder" @@ -26,9 +26,12 @@ import Vue from 'vue'; import i18n from '../../../i18n'; 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({ - i18n: i18n(), + i18n: i18n('mobile/views/pages/drive.vue'), components: { XDrive: () => import('../components/drive.vue').then(m => m.default), }, @@ -63,9 +66,6 @@ export default Vue.extend({ (this.$refs as any).browser.goRoot(true); } }, - fn() { - (this.$refs as any).browser.openContextMenu(); - }, onMoveRoot(silent) { const title = `${this.$root.instanceName} Drive`; @@ -104,6 +104,42 @@ export default Vue.extend({ this.file = file; 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, + }); } } }); From adff5382cad8b38381adfbfd0d6b35765948b6b9 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Apr 2019 03:33:51 +0900 Subject: [PATCH 03/20] confirm silence (#4560) --- locales/ja-JP.yml | 2 ++ src/client/app/admin/views/users.vue | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6d68f0ca8c..bcdda6c671 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1336,7 +1336,9 @@ admin/views/users.vue: unsuspend-confirm: "凍結を解除しますか?" unsuspended: "凍結を解除しました" make-silence: "サイレンス" + silence-confirm: "サイレンスしますか?" unmake-silence: "サイレンスの解除" + unsilence-confirm: "サイレンスを解除しますか?" verify: "公式アカウントにする" verify-confirm: "公式アカウントにしますか?" verified: "公式アカウントにしました" diff --git a/src/client/app/admin/views/users.vue b/src/client/app/admin/views/users.vue index 0f46b564a9..2d6aef3371 100644 --- a/src/client/app/admin/views/users.vue +++ b/src/client/app/admin/views/users.vue @@ -232,6 +232,8 @@ export default Vue.extend({ }, async silenceUser() { + if (!await this.getConfirmed(this.$t('silence-confirm'))) return; + const process = async () => { await this.$root.api('admin/silence-user', { userId: this.user.id }); this.$root.dialog({ @@ -251,6 +253,8 @@ export default Vue.extend({ }, async unsilenceUser() { + if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return; + const process = async () => { await this.$root.api('admin/unsilence-user', { userId: this.user.id }); this.$root.dialog({ From 9ec6afa37541a307631c2826b7f923160c6d4e0f Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Apr 2019 03:36:06 +0900 Subject: [PATCH 04/20] confirm on user menu (#4553) --- locales/ja-JP.yml | 8 +++++ .../app/common/views/components/user-menu.vue | 33 ++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index bcdda6c671..0836386c6c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -527,8 +527,12 @@ common/views/components/user-menu.vue: mention: "メンション" mute: "ミュート" unmute: "ミュート解除" + mute-confirm: "このユーザーをミュートしますか?" + unmute-confirm: "このユーザーをミュート解除しますか?" block: "ブロック" unblock: "ブロック解除" + block-confirm: "このユーザーをブロックしますか?" + unblock-confirm: "このユーザーをブロック解除しますか?" push-to-list: "リストに追加" select-list: "リストを選択してください" report-abuse: "スパムを報告" @@ -536,8 +540,12 @@ common/views/components/user-menu.vue: report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。" silence: "サイレンス" unsilence: "サイレンス解除" + silence-confirm: "このユーザーをサイレンスしますか?" + unsilence-confirm: "このユーザーをサイレンス解除しますか?" suspend: "凍結" unsuspend: "凍結解除" + suspend-confirm: "このユーザーを凍結しますか?" + unsuspend-confirm: "このユーザーを凍結解除しますか?" common/views/components/poll.vue: vote-to: "「{}」に投票する" diff --git a/src/client/app/common/views/components/user-menu.vue b/src/client/app/common/views/components/user-menu.vue index a95f7a9225..0af0fdb7e4 100644 --- a/src/client/app/common/views/components/user-menu.vue +++ b/src/client/app/common/views/components/user-menu.vue @@ -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> From 653b8f635290aa0204d507357e62a353942e9248 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Apr 2019 03:37:49 +0900 Subject: [PATCH 05/20] =?UTF-8?q?=E3=82=B9=E3=83=97=E3=83=A9=E3=83=83?= =?UTF-8?q?=E3=82=B7=E3=83=A5=E3=81=8C=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=81=AB=E5=8F=8D=E5=BF=9C=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=20(#4561)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * confirm silence * Resolve #4554 * Revert "confirm silence" This reverts commit e1dbdc2bfc0f41c2b308b142c70e9e4573c98cf9. --- src/client/app/common/views/components/dialog.vue | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue index 6a074769d8..771b1237ee 100644 --- a/src/client/app/common/views/components/dialog.vue +++ b/src/client/app/common/views/components/dialog.vue @@ -183,9 +183,6 @@ export default Vue.extend({ height 100% &.splash - &, * - pointer-events none !important - > .main min-width 0 width initial From 683e5b6abec93f2883c4d4dafc130e2a079f3dda Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 14:29:17 +0900 Subject: [PATCH 06/20] Add type annotations --- test/streaming.ts | 4 ++-- test/utils.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/streaming.ts b/test/streaming.ts index 7594728618..a29517148a 100644 --- a/test/streaming.ts +++ b/test/streaming.ts @@ -32,7 +32,7 @@ describe('Streaming', () => { p.on('message', message => { if (message === 'ok') { (p.channel as any).onread = () => {}; - initDb(true).then(async connection => { + initDb(true).then(async (connection: any) => { Followings = connection.getRepository(Following); done(); }); @@ -44,7 +44,7 @@ describe('Streaming', () => { p.kill(); }); - const follow = async (follower, followee) => { + const follow = async (follower: any, followee: any) => { await Followings.save({ id: 'a', createdAt: new Date(), diff --git a/test/utils.ts b/test/utils.ts index fbba9a68c9..f67baf0048 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -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) => { const ws = new WebSocket(`ws://localhost/streaming?i=${user.token}`); From d78a5c0863b75ad51f739caf6c6eb129cc958b36 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 14:34:47 +0900 Subject: [PATCH 07/20] Fix #4703 --- .../api/stream/channels/hybrid-timeline.ts | 4 +-- test/streaming.ts | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 18e6aa8350..5bf23715ef 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -20,11 +20,11 @@ export default class extends Channel { @autobind private async onNote(note: any) { - // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ + // 自分自身の投稿 または その投稿のユーザーをフォローしている または ホームのローカルの投稿 の場合だけ if (!( this.user!.id === note.userId || this.following.includes(note.userId) || - note.user.host == null + (note.user.host == null && note.visibility === 'home') )) return; if (['followers', 'specified'].includes(note.visibility)) { diff --git a/test/streaming.ts b/test/streaming.ts index a29517148a..a9c90c216f 100644 --- a/test/streaming.ts +++ b/test/streaming.ts @@ -484,6 +484,31 @@ describe('Streaming', () => { }); })); + 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 => { const alice = await signup({ username: 'alice' }); const bob = await signup({ username: 'bob' }); From 73b683bb4d4cdf7ed0bef59dd1058a6347955d86 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 14:39:49 +0900 Subject: [PATCH 08/20] Add test --- test/streaming.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/streaming.ts b/test/streaming.ts index a9c90c216f..2dfede1380 100644 --- a/test/streaming.ts +++ b/test/streaming.ts @@ -484,6 +484,31 @@ 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' }); From 80b83c06248e27e0ea55fe89119d1d6241455411 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 14:41:51 +0900 Subject: [PATCH 09/20] =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=88=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/api/stream/channels/hybrid-timeline.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 5bf23715ef..a8020bfcfa 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -20,11 +20,11 @@ export default class extends Channel { @autobind private async onNote(note: any) { - // 自分自身の投稿 または その投稿のユーザーをフォローしている または ホームのローカルの投稿 の場合だけ + // 自分自身の投稿 または その投稿のユーザーをフォローしている または 全体公開のローカルの投稿 の場合だけ if (!( this.user!.id === note.userId || this.following.includes(note.userId) || - (note.user.host == null && note.visibility === 'home') + (note.user.host == null && note.visibility === 'public') )) return; if (['followers', 'specified'].includes(note.visibility)) { From cce768aaac9002cf23754b579bea79905fe3ac12 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 14:58:43 +0900 Subject: [PATCH 10/20] Fix API definition --- src/server/api/endpoints/notes/hybrid-timeline.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 6dfb143003..effee2b134 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -17,6 +17,8 @@ export const meta = { tags: ['notes'], + requireCredential: true, + params: { limit: { validator: $.optional.num.range(1, 100), From 7827aeb6951bfd0dae1799601dff3f207a3d2363 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 19:40:23 +0900 Subject: [PATCH 11/20] Resolve #4735 --- locales/ja-JP.yml | 1 + src/client/app/common/scripts/search.ts | 64 +++++++++++++++++++ .../app/common/views/components/dialog.vue | 50 +++++++++------ .../views/components/ui.header.search.vue | 27 ++------ .../app/desktop/views/home/timeline.core.vue | 15 +++-- src/client/app/init.ts | 6 +- .../app/mobile/views/components/ui.nav.vue | 26 ++------ .../app/mobile/views/pages/home.timeline.vue | 10 +-- 8 files changed, 128 insertions(+), 71 deletions(-) create mode 100644 src/client/app/common/scripts/search.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0836386c6c..e9284f89a7 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -35,6 +35,7 @@ common: signup: "新規登録" signout: "ログアウト" reload-to-apply-the-setting: "この設定を反映するにはページをリロードする必要があります。今すぐリロードしますか?" + fetching-as-ap-object: "連合に照会中" got-it: "わかった" customization-tips: diff --git a/src/client/app/common/scripts/search.ts b/src/client/app/common/scripts/search.ts new file mode 100644 index 0000000000..c44581817b --- /dev/null +++ b/src/client/app/common/scripts/search.ts @@ -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)}`); +} diff --git a/src/client/app/common/views/components/dialog.vue b/src/client/app/common/views/components/dialog.vue index 771b1237ee..c1ee7958c0 100644 --- a/src/client/app/common/views/components/dialog.vue +++ b/src/client/app/common/views/components/dialog.vue @@ -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) { @@ -240,7 +254,7 @@ export default Vue.extend({ margin-top 8px > .body - margin 16px 0 + margin 16px 0 0 0 > .buttons margin-top 16px diff --git a/src/client/app/desktop/views/components/ui.header.search.vue b/src/client/app/desktop/views/components/ui.header.search.vue index 4ade74bc64..0cf5ca6f32 100644 --- a/src/client/app/desktop/views/components/ui.header.search.vue +++ b/src/client/app/desktop/views/components/ui.header.search.vue @@ -9,6 +9,7 @@ <script lang="ts"> import Vue from 'vue'; import i18n from '../../../i18n'; +import { search } from '../../../common/scripts/search'; export default Vue.extend({ i18n: i18n('desktop/views/components/ui.header.search.vue'), @@ -22,29 +23,11 @@ export default Vue.extend({ async onSubmit() { 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; - 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 = true; + search(this, this.q).finally(() => { this.wait = false; - } else { - this.$router.push(`/search?q=${encodeURIComponent(q)}`); - } + this.q = ''; + }); } } }); diff --git a/src/client/app/desktop/views/home/timeline.core.vue b/src/client/app/desktop/views/home/timeline.core.vue index 12806365d4..654a1cc434 100644 --- a/src/client/app/desktop/views/home/timeline.core.vue +++ b/src/client/app/desktop/views/home/timeline.core.vue @@ -53,6 +53,12 @@ export default Vue.extend({ }, created() { + this.$root.$on('warp', this.warp); + this.$once('hook:beforeDestroy', () => { + this.$root.$off('warp', this.warp); + this.connection.dispose(); + }); + const prepend = note => { (this.$refs.timeline as any).prepend(note); }; @@ -124,13 +130,14 @@ export default Vue.extend({ }); }, - beforeDestroy() { - this.connection.dispose(); - }, - methods: { focus() { (this.$refs.timeline as any).focus(); + }, + + warp(date) { + this.date = date; + (this.$refs.timeline as any).reload(); } } }); diff --git a/src/client/app/init.ts b/src/client/app/init.ts index 4b9f341612..230d8c3f1e 100644 --- a/src/client/app/init.ts +++ b/src/client/app/init.ts @@ -458,10 +458,14 @@ export default (callback: (launch: (router: VueRouter) => [Vue, MiOS], os: MiOS) }, 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('cancel', () => res({ canceled: true })); }); + p.close = () => { + vm.close(); + }; + return p; } }, router, diff --git a/src/client/app/mobile/views/components/ui.nav.vue b/src/client/app/mobile/views/components/ui.nav.vue index 169c7fc07d..b26e2380ab 100644 --- a/src/client/app/mobile/views/components/ui.nav.vue +++ b/src/client/app/mobile/views/components/ui.nav.vue @@ -66,6 +66,7 @@ import i18n from '../../../i18n'; import { lang } from '../../../config'; import { faNewspaper, faHashtag, faHome, faColumns } from '@fortawesome/free-solid-svg-icons'; import { faMoon, faSun } from '@fortawesome/free-regular-svg-icons'; +import { search } from '../../../common/scripts/search'; export default Vue.extend({ i18n: i18n('mobile/views/components/ui.nav.vue'), @@ -133,29 +134,10 @@ export default Vue.extend({ }).then(async ({ canceled, result: query }) => { 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; - 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 = true; + search(this, query).finally(() => { this.searching = false; - } else { - this.$router.push(`/search?q=${encodeURIComponent(q)}`); - } + }); }); }, diff --git a/src/client/app/mobile/views/pages/home.timeline.vue b/src/client/app/mobile/views/pages/home.timeline.vue index 809158dd29..e0754e88b4 100644 --- a/src/client/app/mobile/views/pages/home.timeline.vue +++ b/src/client/app/mobile/views/pages/home.timeline.vue @@ -54,6 +54,12 @@ export default Vue.extend({ }, created() { + this.$root.$on('warp', this.warp); + this.$once('hook:beforeDestroy', () => { + this.$root.$off('warp', this.warp); + this.connection.dispose(); + }); + const prepend = note => { (this.$refs.timeline as any).prepend(note); }; @@ -125,10 +131,6 @@ export default Vue.extend({ }); }, - beforeDestroy() { - this.connection.dispose(); - }, - methods: { focus() { (this.$refs.timeline as any).focus(); From 55f63229cd8f26663a3d9dcc6095997d045f1bd9 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 19:42:40 +0900 Subject: [PATCH 12/20] Update timemachine.vue --- src/client/app/desktop/views/widgets/timemachine.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/desktop/views/widgets/timemachine.vue b/src/client/app/desktop/views/widgets/timemachine.vue index 22a4120403..854b01c13e 100644 --- a/src/client/app/desktop/views/widgets/timemachine.vue +++ b/src/client/app/desktop/views/widgets/timemachine.vue @@ -14,7 +14,7 @@ export default define({ }).extend({ methods: { chosen(date) { - this.$emit('chosen', date); + this.$root.$emit('warp', date); }, func() { if (this.props.design == 5) { From 4beb3e5755b9fe988b7dba9d9d5b1f64f66e3b1a Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 18 Apr 2019 20:46:59 +0900 Subject: [PATCH 13/20] Fix #4734 (#4745) --- src/models/repositories/app.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts index 9266e43cdc..fa5ebf2ac7 100644 --- a/src/models/repositories/app.ts +++ b/src/models/repositories/app.ts @@ -26,6 +26,7 @@ export class AppRepository extends Repository<App> { id: app.id, name: app.name, callbackUrl: app.callbackUrl, + permission: app.permission, ...(opts.includeSecret ? { secret: app.secret } : {}), ...(me ? { isAuthorized: await AccessTokens.count({ From 8b92feac7174558ea2e4d2e0c69dffa69c6aac5d Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 21:29:19 +0900 Subject: [PATCH 14/20] Resolve #4732 --- .../app/common/views/components/settings/api.vue | 12 ++++++++++-- .../app/common/views/components/ui/input.vue | 12 +++++++++++- src/server/api/endpoints/endpoints.ts | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 src/server/api/endpoints/endpoints.ts diff --git a/src/client/app/common/views/components/settings/api.vue b/src/client/app/common/views/components/settings/api.vue index 4f1b755857..74e3eb0661 100644 --- a/src/client/app/common/views/components/settings/api.vue +++ b/src/client/app/common/views/components/settings/api.vue @@ -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({ diff --git a/src/client/app/common/views/components/ui/input.vue b/src/client/app/common/views/components/ui/input.vue index ae9ce249de..bcb87398ba 100644 --- a/src/client/app/common/views/components/ui/input.vue +++ b/src/client/app/common/views/components/ui/input.vue @@ -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: { diff --git a/src/server/api/endpoints/endpoints.ts b/src/server/api/endpoints/endpoints.ts new file mode 100644 index 0000000000..d1e4edaa21 --- /dev/null +++ b/src/server/api/endpoints/endpoints.ts @@ -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); +}); From dab7e527de7aacdd8c4d1755627f96980617e282 Mon Sep 17 00:00:00 2001 From: tamaina <tamaina@hotmail.co.jp> Date: Thu, 18 Apr 2019 21:33:24 +0900 Subject: [PATCH 15/20] Improve user lists index (#4605) * wip * Revert "wip" This reverts commit 6212831ce3bdae5ce17f8ace9945710ba7696185. * improve list index * Update user-lists.vue --- locales/ja-JP.yml | 7 +- .../common/views/components/user-lists.vue | 95 +++++++++++++++++++ .../views/components/ui.header.account.vue | 10 +- .../desktop/views/components/ui.sidebar.vue | 5 +- .../views/components/user-lists-window.vue | 75 +++------------ .../app/mobile/views/pages/user-lists.vue | 36 ++----- 6 files changed, 123 insertions(+), 105 deletions(-) create mode 100644 src/client/app/common/views/components/user-lists.vue diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index e9284f89a7..a475bc2c16 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -748,6 +748,10 @@ common/views/components/user-list-editor.vue: delete-are-you-sure: "リスト「$1」を削除しますか?" deleted: "削除しました" +common/views/components/user-lists.vue: + create-list: "リストを作成" + list-name: "リスト名" + common/views/widgets/broadcast.vue: fetching: "確認中" no-broadcasts: "お知らせはありません" @@ -1154,8 +1158,6 @@ desktop/views/components/received-follow-requests-window.vue: desktop/views/components/user-lists-window.vue: title: "リスト" - create-list: "リストを作成" - list-name: "リスト名" desktop/views/components/user-preview.vue: notes: "投稿" @@ -1689,7 +1691,6 @@ mobile/views/pages/drive.vue: mobile/views/pages/user-lists.vue: title: "リスト" - enter-list-name: "リスト名を入力してください" mobile/views/pages/signup.vue: lets-start: "📦 始めましょう" diff --git a/src/client/app/common/views/components/user-lists.vue b/src/client/app/common/views/components/user-lists.vue new file mode 100644 index 0000000000..786a6766d3 --- /dev/null +++ b/src/client/app/common/views/components/user-lists.vue @@ -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> diff --git a/src/client/app/desktop/views/components/ui.header.account.vue b/src/client/app/desktop/views/components/ui.header.account.vue index effd0ef0fd..7f9decfdcd 100644 --- a/src/client/app/desktop/views/components/ui.header.account.vue +++ b/src/client/app/desktop/views/components/ui.header.account.vue @@ -90,9 +90,8 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import MkUserListsWindow from './user-lists-window.vue'; -import MkUserListWindow from './user-list-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 contains from '../../../common/scripts/contains'; import { faHome, faColumns } from '@fortawesome/free-solid-svg-icons'; @@ -143,12 +142,7 @@ export default Vue.extend({ }, list() { this.close(); - const w = this.$root.new(MkUserListsWindow); - w.$once('choosen', list => { - this.$root.new(MkUserListWindow, { - list - }); - }); + this.$root.new(MkUserListsWindow); }, followRequests() { this.close(); diff --git a/src/client/app/desktop/views/components/ui.sidebar.vue b/src/client/app/desktop/views/components/ui.sidebar.vue index cb0e059c11..1c01f127b9 100644 --- a/src/client/app/desktop/views/components/ui.sidebar.vue +++ b/src/client/app/desktop/views/components/ui.sidebar.vue @@ -148,10 +148,7 @@ export default Vue.extend({ }, list() { - const w = this.$root.new(MkUserListsWindow); - w.$once('choosen', list => { - this.$router.push(`i/lists/${ list.id }`); - }); + this.$root.new(MkUserListsWindow); }, followRequests() { diff --git a/src/client/app/desktop/views/components/user-lists-window.vue b/src/client/app/desktop/views/components/user-lists-window.vue index 7afcd6aa3b..afea01d4a1 100644 --- a/src/client/app/desktop/views/components/user-lists-window.vue +++ b/src/client/app/desktop/views/components/user-lists-window.vue @@ -1,85 +1,36 @@ <template> <mk-window ref="window" width="450px" height="500px" @closed="destroyDom"> <template #header><fa icon="list"/> {{ $t('title') }}</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> + <x-lists :class="$style.content" @choosen="choosen"/> </mk-window> </template> <script lang="ts"> import Vue from 'vue'; import i18n from '../../../i18n'; +import MkUserListWindow from './user-list-window.vue'; export default Vue.extend({ i18n: i18n('desktop/views/components/user-lists-window.vue'), - data() { - return { - fetching: true, - lists: [] - }; - }, - mounted() { - this.$root.api('users/lists/list').then(lists => { - this.fetching = false; - this.lists = lists; - }); + components: { + XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default) }, 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() { (this as any).$refs.window.close(); + }, + choosen(list) { + this.$root.new(MkUserListWindow, { + list + }); } } }); </script> -<style lang="stylus" scoped> -.xkxvokkjlptzyewouewmceqcxhpgzprp - padding 16px - - > 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 lang="stylus" module> +.content + height 100% + overflow auto </style> diff --git a/src/client/app/mobile/views/pages/user-lists.vue b/src/client/app/mobile/views/pages/user-lists.vue index 49006f41f6..a3e9bd78ba 100644 --- a/src/client/app/mobile/views/pages/user-lists.vue +++ b/src/client/app/mobile/views/pages/user-lists.vue @@ -1,20 +1,15 @@ <template> <mk-ui> <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> - <ul> - <li v-for="list in lists" :key="list.id"><router-link :to="`/i/lists/${list.id}`">{{ list.name }}</router-link></li> - </ul> - </main> + <x-lists ref="lists" @choosen="choosen"/> </mk-ui> </template> <script lang="ts"> import Vue from 'vue'; import i18n from '../../../i18n'; -import Progress from '../../../common/scripts/loading'; export default Vue.extend({ i18n: i18n('mobile/views/pages/user-lists.vue'), @@ -24,31 +19,16 @@ export default Vue.extend({ lists: [] }; }, + components: { + XLists: () => import('../../../common/views/components/user-lists.vue').then(m => m.default) + }, mounted() { document.title = this.$t('title'); - - Progress.start(); - - this.$root.api('users/lists/list').then(lists => { - this.fetching = false; - this.lists = lists; - - Progress.done(); - }); }, methods: { - fn() { - this.$root.dialog({ - 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}`); - }); + choosen(list) { + if (!list) return; + this.$router.push(`/i/lists/${list.id}`); } } }); From ae6cc11ad291701966501962c76a1e154ee45dbe Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 21:34:56 +0900 Subject: [PATCH 16/20] Fix icon --- src/client/app/common/views/components/messaging-room.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/client/app/common/views/components/messaging-room.vue b/src/client/app/common/views/components/messaging-room.vue index 83a0c463e0..a8980e068f 100644 --- a/src/client/app/common/views/components/messaging-room.vue +++ b/src/client/app/common/views/components/messaging-room.vue @@ -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 }; }, From 4096ddcbd0eb897a75b5dad5b2f389300e653ab8 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 21:36:44 +0900 Subject: [PATCH 17/20] Use - --- src/client/app/store.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/store.ts b/src/client/app/store.ts index 44b893835c..a6f2a0b00b 100644 --- a/src/client/app/store.ts +++ b/src/client/app/store.ts @@ -358,7 +358,7 @@ export default (os: MiOS) => new Vuex.Store({ ctx.commit('set', x); if (ctx.rootGetters.isSignedIn) { - os.api('i/update_client_setting', { + os.api('i/update-client-setting', { name: x.key, value: x.value }); From 8aaab195c6f6d763446e0635e2745f3e18fe852a Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 21:40:36 +0900 Subject: [PATCH 18/20] Fix bug --- src/client/app/mobile/views/pages/widgets.vue | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue index 33166825cb..7130fddb34 100644 --- a/src/client/app/mobile/views/pages/widgets.vue +++ b/src/client/app/mobile/views/pages/widgets.vue @@ -72,13 +72,13 @@ export default Vue.extend({ computed: { widgets(): any[] { - return this.$store.state.settings.mobileHome; + return this.$store.state.device.mobileHome; } }, created() { if (this.widgets.length == 0) { - this.widgets = [{ + this.$store.commit('device/setMobileHome', [{ name: 'calendar', id: 'a', data: {} }, { @@ -96,8 +96,7 @@ export default Vue.extend({ }, { name: 'version', id: 'g', data: {} - }]; - this.saveHome(); + }]); } }, @@ -123,7 +122,7 @@ export default Vue.extend({ }, addWidget() { - this.$store.commit('settings/addMobileHomeWidget', { + this.$store.commit('device/addMobileHomeWidget', { name: this.widgetAdderSelected, id: uuid(), data: {} @@ -131,11 +130,11 @@ export default Vue.extend({ }, removeWidget(widget) { - this.$store.commit('settings/removeMobileHomeWidget', widget); + this.$store.commit('device/removeMobileHomeWidget', widget); }, saveHome() { - this.$store.commit('settings/setMobileHome', this.widgets); + this.$store.commit('device/setMobileHome', this.widgets); } } }); From fda8cf77ed216567669cf9af5af359f0b33c3e12 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 22:00:11 +0900 Subject: [PATCH 19/20] Improve warp --- .../app/desktop/views/components/notes.vue | 2 +- .../views/components/user-list-timeline.vue | 10 ++++++++++ .../desktop/views/home/user/user.timeline.vue | 10 ++++++---- src/client/app/mobile/views/components/notes.vue | 2 +- .../views/components/user-list-timeline.vue | 12 ++++++++++++ .../mobile/views/components/user-timeline.vue | 16 ++++++++++++++++ src/server/api/endpoints/users/notes.ts | 2 +- 7 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index 3fad7c9226..9044ad3478 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -123,7 +123,7 @@ export default Vue.extend({ }, fetchMore() { - if (!this.more || this.moreFetching) return; + if (!this.more || this.moreFetching || this.notes.length === 0) return; this.moreFetching = true; this.makePromise(this.notes[this.notes.length - 1].id).then(x => { this.notes = this.notes.concat(x.notes); diff --git a/src/client/app/desktop/views/components/user-list-timeline.vue b/src/client/app/desktop/views/components/user-list-timeline.vue index 0cbe5ff05a..4437b838f2 100644 --- a/src/client/app/desktop/views/components/user-list-timeline.vue +++ b/src/client/app/desktop/views/components/user-list-timeline.vue @@ -18,10 +18,12 @@ export default Vue.extend({ data() { return { connection: null, + date: null, makePromise: cursor => this.$root.api('notes/user-list-timeline', { listId: this.list.id, limit: fetchLimit + 1, untilId: cursor ? cursor : undefined, + untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined), includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeLocalRenotes: this.$store.state.settings.showLocalRenotes @@ -46,6 +48,10 @@ export default Vue.extend({ }, mounted() { this.init(); + this.$root.$on('warp', this.warp); + this.$once('hook:beforeDestroy', () => { + this.$root.$off('warp', this.warp); + }); }, beforeDestroy() { this.connection.dispose(); @@ -68,6 +74,10 @@ export default Vue.extend({ }, onUserRemoved() { (this.$refs.timeline as any).reload(); + }, + warp(date) { + this.date = date; + (this.$refs.timeline as any).reload(); } } }); diff --git a/src/client/app/desktop/views/home/user/user.timeline.vue b/src/client/app/desktop/views/home/user/user.timeline.vue index 086ad6a329..0435d67dc7 100644 --- a/src/client/app/desktop/views/home/user/user.timeline.vue +++ b/src/client/app/desktop/views/home/user/user.timeline.vue @@ -36,6 +36,7 @@ export default Vue.extend({ includeReplies: this.mode == 'with-replies', includeMyRenotes: this.mode != 'my-posts', withFiles: this.mode == 'with-media', + untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined), untilId: cursor ? cursor : undefined }).then(notes => { if (notes.length == fetchLimit + 1) { @@ -62,10 +63,11 @@ export default Vue.extend({ mounted() { document.addEventListener('keydown', this.onDocumentKeydown); - }, - - beforeDestroy() { - document.removeEventListener('keydown', this.onDocumentKeydown); + this.$root.$on('warp', this.warp); + this.$once('hook:beforeDestroy', () => { + this.$root.$off('warp', this.warp); + document.removeEventListener('keydown', this.onDocumentKeydown); + }); }, methods: { diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index 5f3f5633d4..2e42300717 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -124,7 +124,7 @@ export default Vue.extend({ }, fetchMore() { - if (!this.more || this.moreFetching) return; + if (!this.more || this.moreFetching || this.notes.length === 0) return; this.moreFetching = true; this.makePromise(this.notes[this.notes.length - 1].id).then(x => { this.notes = this.notes.concat(x.notes); diff --git a/src/client/app/mobile/views/components/user-list-timeline.vue b/src/client/app/mobile/views/components/user-list-timeline.vue index c3f09c9b15..73bc6f3034 100644 --- a/src/client/app/mobile/views/components/user-list-timeline.vue +++ b/src/client/app/mobile/views/components/user-list-timeline.vue @@ -15,10 +15,12 @@ export default Vue.extend({ data() { return { connection: null, + date: null, makePromise: cursor => this.$root.api('notes/user-list-timeline', { listId: this.list.id, limit: fetchLimit + 1, untilId: cursor ? cursor : undefined, + untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined), includeMyRenotes: this.$store.state.settings.showMyRenotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeLocalRenotes: this.$store.state.settings.showLocalRenotes @@ -45,6 +47,11 @@ export default Vue.extend({ mounted() { this.init(); + + this.$root.$on('warp', this.warp); + this.$once('hook:beforeDestroy', () => { + this.$root.$off('warp', this.warp); + }); }, beforeDestroy() { @@ -73,6 +80,11 @@ export default Vue.extend({ onUserRemoved() { (this.$refs.timeline as any).reload(); + }, + + warp(date) { + this.date = date; + (this.$refs.timeline as any).reload(); } } }); diff --git a/src/client/app/mobile/views/components/user-timeline.vue b/src/client/app/mobile/views/components/user-timeline.vue index 70016a49e3..8c7c8c6d7d 100644 --- a/src/client/app/mobile/views/components/user-timeline.vue +++ b/src/client/app/mobile/views/components/user-timeline.vue @@ -17,10 +17,12 @@ export default Vue.extend({ data() { return { + date: null, makePromise: cursor => this.$root.api('users/notes', { userId: this.user.id, limit: fetchLimit + 1, withFiles: this.withMedia, + untilDate: cursor ? undefined : (this.date ? this.date.getTime() : undefined), untilId: cursor ? cursor : undefined }).then(notes => { if (notes.length == fetchLimit + 1) { @@ -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> diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index d3f17bd787..4691a99394 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -142,7 +142,7 @@ export default define(meta, async (ps, me) => { }); //#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 }) .leftJoinAndSelect('note.user', 'user'); From debe648a98b8ed62822d177bdf7aeb15373cc640 Mon Sep 17 00:00:00 2001 From: syuilo <syuilotan@yahoo.co.jp> Date: Thu, 18 Apr 2019 22:00:53 +0900 Subject: [PATCH 20/20] 11.2.0 --- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c45b02b5e..1755ce43b4 100644 --- a/CHANGELOG.md +++ b/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` 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) ------------------- ### Fixes diff --git a/package.json b/package.json index db2d9d0eac..409bdda154 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo <i@syuilo.com>", - "version": "11.1.6", + "version": "11.2.0", "codename": "daybreak", "repository": { "type": "git",