From 361069314ffaa61a81b2189c2eec000a3d1d9c35 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 17 Sep 2021 22:39:15 +0900 Subject: Refine UI (#7806) * wip * wip * wip * wip * wip * wip * wip * wip * Update default.vue * wip --- src/client/components/global/avatar.vue | 26 ++++++++++++++++++++++++++ src/client/components/ui/folder.vue | 3 ++- src/client/components/ui/input.vue | 2 +- src/client/components/ui/textarea.vue | 2 +- 4 files changed, 30 insertions(+), 3 deletions(-) (limited to 'src/client/components') diff --git a/src/client/components/global/avatar.vue b/src/client/components/global/avatar.vue index eea970ec9a..395ed5d8ce 100644 --- a/src/client/components/global/avatar.vue +++ b/src/client/components/global/avatar.vue @@ -73,6 +73,22 @@ export default defineComponent({ diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue index 1f3593a74a..eecf1d8be1 100644 --- a/src/client/components/ui/folder.vue +++ b/src/client/components/ui/folder.vue @@ -99,7 +99,8 @@ export default defineComponent({ z-index: 10; position: sticky; top: var(--stickyTop, 0px); - background: var(--panel); + padding: var(--x-padding); + background: var(--x-header, var(--panel)); /* TODO panelの半透明バージョンをプログラマティックに作りたい background: var(--X17); -webkit-backdrop-filter: var(--blur, blur(8px)); diff --git a/src/client/components/ui/input.vue b/src/client/components/ui/input.vue index 05ce5d3e15..a916a0b035 100644 --- a/src/client/components/ui/input.vue +++ b/src/client/components/ui/input.vue @@ -245,7 +245,7 @@ export default defineComponent({ font-size: 1em; color: var(--fg); background: var(--panel); - border: solid 1px var(--inputBorder); + border: solid 0.5px var(--inputBorder); border-radius: 6px; outline: none; box-shadow: none; diff --git a/src/client/components/ui/textarea.vue b/src/client/components/ui/textarea.vue index 53a141f011..08ac3182a9 100644 --- a/src/client/components/ui/textarea.vue +++ b/src/client/components/ui/textarea.vue @@ -212,7 +212,7 @@ export default defineComponent({ font-size: 1em; color: var(--fg); background: var(--panel); - border: solid 1px var(--inputBorder); + border: solid 0.5px var(--inputBorder); border-radius: 6px; outline: none; box-shadow: none; -- cgit v1.2.3-freya From 6d4e96dea257bbc0d1137dc2701d6e14fd539bc3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 18 Sep 2021 21:26:31 +0900 Subject: fix style --- src/client/components/page-window.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/components') diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index c83b040dd8..fbc9f0b7fd 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -8,7 +8,7 @@ @closed="$emit('closed')" >
-- cgit v1.2.3-freya From 54e0a7f8a8d977c7befc255cc4950a86ac2e72fb Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 19 Sep 2021 02:23:12 +0900 Subject: feat: 凍結された場合のダイアログを実装 (#7811) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 凍結された場合のダイアログを実装 * Update CHANGELOG.md * Update basic.js * improve error handling * cypressなんもわからん * Update basic.js --- CHANGELOG.md | 2 + cypress/integration/basic.js | 142 ++++++++++++++++++++++------ locales/ja-JP.yml | 2 + src/client/account.ts | 22 +++-- src/client/components/signin.vue | 41 +++++--- src/client/scripts/show-suspended-dialog.ts | 10 ++ src/server/api/endpoints/reset-db.ts | 2 + src/server/api/private/signin.ts | 37 ++++---- 8 files changed, 189 insertions(+), 69 deletions(-) create mode 100644 src/client/scripts/show-suspended-dialog.ts (limited to 'src/client/components') diff --git a/CHANGELOG.md b/CHANGELOG.md index dce25340f9..8a15faf6a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### Improvements - ActivityPub: リモートユーザーのDeleteアクティビティに対応 - ActivityPub: add resolver check for blocked instance +- アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように +- 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように - UIの改善 ### Bugfixes diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index 69d59bc2c6..52bcdb58d0 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -1,12 +1,18 @@ describe('Basic', () => { - before(() => { - cy.request('POST', '/api/reset-db'); - }); - beforeEach(() => { + cy.request('POST', '/api/reset-db').as('reset'); + cy.get('@reset').its('status').should('equal', 204); + cy.clearLocalStorage(); + cy.clearCookies(); cy.reload(true); }); + afterEach(() => { + // テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。 + // waitを入れることでそれを防止できる + cy.wait(1000); + }); + it('successfully loads', () => { cy.visit('/'); }); @@ -14,56 +20,130 @@ describe('Basic', () => { it('setup instance', () => { cy.visit('/'); + cy.intercept('POST', '/api/admin/accounts/create').as('signup'); + cy.get('[data-cy-admin-username] input').type('admin'); - cy.get('[data-cy-admin-password] input').type('admin1234'); - cy.get('[data-cy-admin-ok]').click(); + + // なぜか動かない + //cy.wait('@signup').should('have.property', 'response.statusCode'); + cy.wait('@signup'); }); it('signup', () => { - cy.visit('/'); + // インスタンス初期セットアップ + cy.request('POST', '/api/admin/accounts/create', { + username: 'admin', + password: 'pass', + }).as('setup'); - cy.get('[data-cy-signup]').click(); + cy.get('@setup').then(() => { + cy.visit('/'); - cy.get('[data-cy-signup-username] input').type('alice'); + cy.intercept('POST', '/api/signup').as('signup'); - cy.get('[data-cy-signup-password] input').type('alice1234'); - - cy.get('[data-cy-signup-password-retype] input').type('alice1234'); + cy.get('[data-cy-signup]').click(); + cy.get('[data-cy-signup-username] input').type('alice'); + cy.get('[data-cy-signup-password] input').type('alice1234'); + cy.get('[data-cy-signup-password-retype] input').type('alice1234'); + cy.get('[data-cy-signup-submit]').click(); - cy.get('[data-cy-signup-submit]').click(); + cy.wait('@signup'); + }); }); it('signin', () => { - cy.visit('/'); - - cy.get('[data-cy-signin]').click(); - - cy.get('[data-cy-signin-username] input').type('alice'); - - // Enterキーでサインインできるかの確認も兼ねる - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + // インスタンス初期セットアップ + cy.request('POST', '/api/admin/accounts/create', { + username: 'admin', + password: 'pass', + }).as('setup'); + + cy.get('@setup').then(() => { + // ユーザー作成 + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).as('signup'); + }); + + cy.get('@signup').then(() => { + cy.visit('/'); + + cy.intercept('POST', '/api/signin').as('signin'); + + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type('alice'); + // Enterキーでサインインできるかの確認も兼ねる + cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + + cy.wait('@signin'); + }); }); it('note', () => { cy.visit('/'); - //#region TODO: この辺はUI操作ではなくAPI操作でログインする - cy.get('[data-cy-signin]').click(); + // インスタンス初期セットアップ + cy.request('POST', '/api/admin/accounts/create', { + username: 'admin', + password: 'pass', + }).as('setup'); + + cy.get('@setup').then(() => { + // ユーザー作成 + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'alice1234', + }).as('signup'); + }); - cy.get('[data-cy-signin-username] input').type('alice'); + cy.get('@signup').then(() => { + cy.visit('/'); - // Enterキーでサインインできるかの確認も兼ねる - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - //#endregion + cy.intercept('POST', '/api/signin').as('signin'); - cy.get('[data-cy-open-post-form]').click(); + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type('alice'); + cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); + cy.wait('@signin').as('signinEnd'); + }); - cy.get('[data-cy-open-post-form-submit]').click(); + cy.get('@signinEnd').then(() => { + cy.get('[data-cy-open-post-form]').click(); + cy.get('[data-cy-post-form-text]').type('Hello, Misskey!'); + cy.get('[data-cy-open-post-form-submit]').click(); - // TODO: 投稿した文字列が画面内にあるか(=タイムラインに流れてきたか)のテスト + cy.contains('Hello, Misskey!'); + }); }); + + it('suspend', function() { + cy.request('POST', '/api/admin/accounts/create', { + username: 'admin', + password: 'pass', + }).its('body').as('admin'); + + cy.request('POST', '/api/signup', { + username: 'alice', + password: 'pass', + }).its('body').as('alice'); + + cy.then(() => { + cy.request('POST', '/api/admin/suspend-user', { + i: this.admin.token, + userId: this.alice.id, + }); + + cy.visit('/'); + + cy.get('[data-cy-signin]').click(); + cy.get('[data-cy-signin-username] input').type('alice'); + cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + + cy.contains('アカウントが凍結されています'); + }); + }); }); diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index b9623ef0d0..2c0663cf87 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -529,6 +529,8 @@ removeAllFollowing: "フォローを全解除" removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。" userSuspended: "このユーザーは凍結されています。" userSilenced: "このユーザーはサイレンスされています。" +yourAccountSuspendedTitle: "アカウントが凍結されています" +yourAccountSuspendedDescription: "このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。" menu: "メニュー" divider: "分割線" addItem: "項目を追加" diff --git a/src/client/account.ts b/src/client/account.ts index e469bae5a2..6e26ac1f7d 100644 --- a/src/client/account.ts +++ b/src/client/account.ts @@ -3,6 +3,7 @@ import { reactive } from 'vue'; import { apiUrl } from '@client/config'; import { waiting } from '@client/os'; import { unisonReload, reloadChannel } from '@client/scripts/unison-reload'; +import { showSuspendedDialog } from './scripts/show-suspended-dialog'; // TODO: 他のタブと永続化されたstateを同期 @@ -82,17 +83,20 @@ function fetchAccount(token): Promise { i: token }) }) + .then(res => res.json()) .then(res => { - // When failed to authenticate user - if (res.status !== 200 && res.status < 500) { - return signout(); + if (res.error) { + if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') { + showSuspendedDialog().then(() => { + signout(); + }); + } else { + signout(); + } + } else { + res.token = token; + done(res); } - - // Parse response - res.json().then(i => { - i.token = token; - done(i); - }); }) .catch(fail); }); diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue index c051288d0a..69f527b7d6 100755 --- a/src/client/components/signin.vue +++ b/src/client/components/signin.vue @@ -54,6 +54,7 @@ import { apiUrl, host } from '@client/config'; import { byteify, hexify } from '@client/scripts/2fa'; import * as os from '@client/os'; import { login } from '@client/account'; +import { showSuspendedDialog } from '../scripts/show-suspended-dialog'; export default defineComponent({ components: { @@ -169,15 +170,7 @@ export default defineComponent({ this.signing = false; this.challengeData = res; return this.queryKey(); - }).catch(() => { - os.dialog({ - type: 'error', - text: this.$ts.signinFailed - }); - this.challengeData = null; - this.totpLogin = false; - this.signing = false; - }); + }).catch(this.loginFailed); } else { this.totpLogin = true; this.signing = false; @@ -190,14 +183,36 @@ export default defineComponent({ }).then(res => { this.$emit('login', res); this.onLogin(res); - }).catch(() => { + }).catch(this.loginFailed); + } + }, + + loginFailed(err) { + switch (err.id) { + case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { os.dialog({ type: 'error', - text: this.$ts.loginFailed + title: this.$ts.loginFailed, + text: this.$ts.noSuchUser }); - this.signing = false; - }); + break; + } + case 'e03a5f46-d309-4865-9b69-56282d94e1eb': { + showSuspendedDialog(); + break; + } + default: { + os.dialog({ + type: 'error', + title: this.$ts.loginFailed, + text: JSON.stringify(err) + }); + } } + + this.challengeData = null; + this.totpLogin = false; + this.signing = false; }, resetPassword() { diff --git a/src/client/scripts/show-suspended-dialog.ts b/src/client/scripts/show-suspended-dialog.ts new file mode 100644 index 0000000000..dde829cdae --- /dev/null +++ b/src/client/scripts/show-suspended-dialog.ts @@ -0,0 +1,10 @@ +import * as os from '@client/os'; +import { i18n } from '@client/i18n'; + +export function showSuspendedDialog() { + return os.dialog({ + type: 'error', + title: i18n.locale.yourAccountSuspendedTitle, + text: i18n.locale.yourAccountSuspendedDescription + }); +} diff --git a/src/server/api/endpoints/reset-db.ts b/src/server/api/endpoints/reset-db.ts index f430869302..f0a9dae4ff 100644 --- a/src/server/api/endpoints/reset-db.ts +++ b/src/server/api/endpoints/reset-db.ts @@ -18,4 +18,6 @@ export default define(meta, async (ps, user) => { if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test'; await resetDb(); + + await new Promise(resolve => setTimeout(resolve, 1000)); }); diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index fff1037ff9..83c3dfee94 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -18,6 +18,11 @@ export default async (ctx: Koa.Context) => { const password = body['password']; const token = body['token']; + function error(status: number, error: { id: string }) { + ctx.status = status; + ctx.body = { error }; + } + if (typeof username != 'string') { ctx.status = 400; return; @@ -40,15 +45,15 @@ export default async (ctx: Koa.Context) => { }) as ILocalUser; if (user == null) { - ctx.throw(404, { - error: 'user not found' + error(404, { + id: '6cc579cc-885d-43d8-95c2-b8c7fc963280', }); return; } if (user.isSuspended) { - ctx.throw(403, { - error: 'user is suspended' + error(403, { + id: 'e03a5f46-d309-4865-9b69-56282d94e1eb', }); return; } @@ -58,7 +63,7 @@ export default async (ctx: Koa.Context) => { // Compare password const same = await bcrypt.compare(password, profile.password!); - async function fail(status?: number, failure?: { error: string }) { + async function fail(status?: number, failure?: { id: string }) { // Append signin history await Signins.insert({ id: genId(), @@ -69,7 +74,7 @@ export default async (ctx: Koa.Context) => { success: false }); - ctx.throw(status || 500, failure || { error: 'someting happened' }); + error(status || 500, failure || { id: '4e30e80c-e338-45a0-8c8f-44455efa3b76' }); } if (!profile.twoFactorEnabled) { @@ -78,7 +83,7 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - error: 'incorrect password' + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c' }); return; } @@ -87,7 +92,7 @@ export default async (ctx: Koa.Context) => { if (token) { if (!same) { await fail(403, { - error: 'incorrect password' + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c' }); return; } @@ -104,14 +109,14 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - error: 'invalid token' + id: 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f' }); return; } } else if (body.credentialId) { if (!same && !profile.usePasswordLessLogin) { await fail(403, { - error: 'incorrect password' + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c' }); return; } @@ -127,7 +132,7 @@ export default async (ctx: Koa.Context) => { if (!challenge) { await fail(403, { - error: 'non-existent challenge' + id: '2715a88a-2125-4013-932f-aa6fe72792da' }); return; } @@ -139,7 +144,7 @@ export default async (ctx: Koa.Context) => { if (new Date().getTime() - challenge.createdAt.getTime() >= 5 * 60 * 1000) { await fail(403, { - error: 'non-existent challenge' + id: '2715a88a-2125-4013-932f-aa6fe72792da' }); return; } @@ -155,7 +160,7 @@ export default async (ctx: Koa.Context) => { if (!securityKey) { await fail(403, { - error: 'invalid credentialId' + id: '66269679-aeaf-4474-862b-eb761197e046' }); return; } @@ -174,14 +179,14 @@ export default async (ctx: Koa.Context) => { return; } else { await fail(403, { - error: 'invalid challenge data' + id: '93b86c4b-72f9-40eb-9815-798928603d1e' }); return; } } else { if (!same && !profile.usePasswordLessLogin) { await fail(403, { - error: 'incorrect password' + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c' }); return; } @@ -192,7 +197,7 @@ export default async (ctx: Koa.Context) => { if (keys.length === 0) { await fail(403, { - error: 'no keys found' + id: 'f27fd449-9af4-4841-9249-1f989b9fa4a4' }); return; } -- cgit v1.2.3-freya From 90bf976fe26965280e6b0c90bfe8a55ea30ab0d7 Mon Sep 17 00:00:00 2001 From: tamaina Date: Mon, 20 Sep 2021 22:14:49 +0900 Subject: enhance: ノートヘッダーにflex-shrinkを設定し、Acctを優先的に縮小して見栄えをよくするように (#7752) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * MAKE NOTE HEADER FLEX AGAIN * span => div * remove submodules --- src/client/components/note-header.vue | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/client/components') diff --git a/src/client/components/note-header.vue b/src/client/components/note-header.vue index 7758dea3ae..80bfea9b07 100644 --- a/src/client/components/note-header.vue +++ b/src/client/components/note-header.vue @@ -3,10 +3,10 @@ - bot - - - +
bot
+
+
+
@@ -55,6 +55,7 @@ export default defineComponent({ white-space: nowrap; > .name { + flex-shrink: 1; display: block; margin: 0 .5em 0 0; padding: 0; @@ -81,17 +82,20 @@ export default defineComponent({ > .admin, > .moderator { + flex-shrink: 0; margin-right: 0.5em; color: var(--badge); } > .username { + flex-shrink: 9999999; margin: 0 .5em 0 0; overflow: hidden; text-overflow: ellipsis; } > .info { + flex-shrink: 0; margin-left: auto; font-size: 0.9em; -- cgit v1.2.3-freya From 72a49f334a58db61e2f977f5e53f28c1491f9da8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 21 Sep 2021 21:04:59 +0900 Subject: enhance(client): リスト、アンテナタイムラインを個別ページとして分割 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 3 +- src/client/components/ui/menu.vue | 35 ++++---- src/client/menu.ts | 24 +++++- src/client/os.ts | 2 +- src/client/pages/antenna-timeline.vue | 147 ++++++++++++++++++++++++++++++++ src/client/pages/timeline.vue | 58 ++----------- src/client/pages/user-list-timeline.vue | 147 ++++++++++++++++++++++++++++++++ src/client/router.ts | 2 + src/client/ui/_common_/sidebar.vue | 2 +- 9 files changed, 349 insertions(+), 71 deletions(-) create mode 100644 src/client/pages/antenna-timeline.vue create mode 100644 src/client/pages/user-list-timeline.vue (limited to 'src/client/components') diff --git a/CHANGELOG.md b/CHANGELOG.md index b60fd7cfa0..cf5621fc05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,14 @@ --> ## 12.x.x (unreleased) -- ActivityPub: deliverキューのメモリ使用量を削減 ### Improvements - ActivityPub: リモートユーザーのDeleteアクティビティに対応 - ActivityPub: add resolver check for blocked instance +- ActivityPub: deliverキューのメモリ使用量を削減 - アカウントが凍結された場合に、凍結された旨を表示してからログアウトするように - 凍結されたアカウントにログインしようとしたときに、凍結されている旨を表示するように +- リスト、アンテナタイムラインを個別ページとして分割 - UIの改善 ### Bugfixes diff --git a/src/client/components/ui/menu.vue b/src/client/components/ui/menu.vue index d652d9b84f..26b4b04b11 100644 --- a/src/client/components/ui/menu.vue +++ b/src/client/components/ui/menu.vue @@ -41,7 +41,7 @@ + + diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue index 4b5b90e6ae..9dda82462d 100644 --- a/src/client/pages/timeline.vue +++ b/src/client/pages/timeline.vue @@ -6,11 +6,8 @@
({ title: this.$ts.timeline, @@ -116,32 +109,10 @@ export default defineComponent({ src() { this.showNav = false; }, - list(x) { - this.showNav = false; - if (x != null) this.antenna = null; - if (x != null) this.channel = null; - }, - antenna(x) { - this.showNav = false; - if (x != null) this.list = null; - if (x != null) this.channel = null; - }, - channel(x) { - this.showNav = false; - if (x != null) this.antenna = null; - if (x != null) this.list = null; - }, }, created() { this.src = this.$store.state.tl.src; - if (this.src === 'list') { - this.list = this.$store.state.tl.arg; - } else if (this.src === 'antenna') { - this.antenna = this.$store.state.tl.arg; - } else if (this.src === 'channel') { - this.channel = this.$store.state.tl.arg; - } }, methods: { @@ -164,12 +135,9 @@ export default defineComponent({ async chooseList(ev) { const lists = await os.api('users/lists/list'); const items = lists.map(list => ({ + type: 'link', text: list.name, - action: () => { - this.list = list; - this.src = 'list'; - this.saveSrc(); - } + to: `/timeline/list/${list.id}` })); os.popupMenu(items, ev.currentTarget || ev.target); }, @@ -177,13 +145,10 @@ export default defineComponent({ async chooseAntenna(ev) { const antennas = await os.api('antennas/list'); const items = antennas.map(antenna => ({ + type: 'link', text: antenna.name, indicate: antenna.hasUnreadNote, - action: () => { - this.antenna = antenna; - this.src = 'antenna'; - this.saveSrc(); - } + to: `/timeline/antenna/${antenna.id}` })); os.popupMenu(items, ev.currentTarget || ev.target); }, @@ -191,15 +156,10 @@ export default defineComponent({ async chooseChannel(ev) { const channels = await os.api('channels/followed'); const items = channels.map(channel => ({ + type: 'link', text: channel.name, indicate: channel.hasUnreadNote, - action: () => { - // NOTE: チャンネルタイムラインをこのコンポーネントで表示するようにすると投稿フォームはどうするかなどの問題が生じるのでとりあえずページ遷移で - //this.channel = channel; - //this.src = 'channel'; - //this.saveSrc(); - this.$router.push(`/channels/${channel.id}`); - } + to: `/channels/${channel.id}` })); os.popupMenu(items, ev.currentTarget || ev.target); }, @@ -207,10 +167,6 @@ export default defineComponent({ saveSrc() { this.$store.set('tl', { src: this.src, - arg: - this.src === 'list' ? this.list : - this.src === 'antenna' ? this.antenna : - this.channel }); }, diff --git a/src/client/pages/user-list-timeline.vue b/src/client/pages/user-list-timeline.vue new file mode 100644 index 0000000000..491fe948c1 --- /dev/null +++ b/src/client/pages/user-list-timeline.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/src/client/router.ts b/src/client/router.ts index 225ee44e32..573f285c79 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -48,6 +48,8 @@ const defaultRoutes = [ { path: '/channels/:channelId/edit', component: page('channel-editor'), props: true }, { path: '/channels/:channelId', component: page('channel'), props: route => ({ channelId: route.params.channelId }) }, { path: '/clips/:clipId', component: page('clip'), props: route => ({ clipId: route.params.clipId }) }, + { path: '/timeline/list/:listId', component: page('user-list-timeline'), props: route => ({ listId: route.params.listId }) }, + { path: '/timeline/antenna/:antennaId', component: page('antenna-timeline'), props: route => ({ antennaId: route.params.antennaId }) }, { path: '/my/notifications', component: page('notifications') }, { path: '/my/favorites', component: page('favorites') }, { path: '/my/messages', component: page('messages') }, diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index 43b64d133e..65f3d7dbdd 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -19,7 +19,7 @@ @@ -294,7 +304,7 @@ export default defineComponent({ preview_hashtag: '#test', preview_url: `https://example.com`, preview_link: `[${this.$ts._mfm.dummy}](https://example.com)`, - preview_emoji: `:${this.$instance.emojis[0].name}:`, + preview_emoji: this.$instance.emojis.length ? `:${this.$instance.emojis[0].name}:` : `:emojiname:`, preview_bold: `**${this.$ts._mfm.dummy}**`, preview_small: `${this.$ts._mfm.dummy}`, preview_center: `
${this.$ts._mfm.dummy}
`, @@ -317,6 +327,7 @@ export default defineComponent({ preview_x4: `$[x4 🍮]`, preview_blur: `$[blur ${this.$ts._mfm.dummy}]`, preview_rainbow: `$[rainbow 🍮]`, + preview_sparkle: `$[sparkle 🍮]`, } }, }); -- cgit v1.2.3-freya