diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index c81506384c..446df15541 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -232,8 +232,43 @@ const getFeed = async (acct: string) => { return user && await packFeed(user); }; +// As the /@user[.json|.rss|.atom]/sub endpoint is complicated, we will use a regex to switch between them. +const reUser = new RegExp(`^/@(?[^/]+?)(?:\.(?json|rss|atom))?(?:/(?[^/]+))?$`); +router.get(reUser, async (ctx, next) => { + const groups = reUser.exec(ctx.originalUrl)?.groups; + if (!groups) { + await next(); + return; + } + + ctx.params = groups; + + console.log(ctx, ctx.params) + if (groups.feed) { + if (groups.sub) { + await next(); + return; + } + + switch (groups.feed) { + case 'json': + await jsonFeed(ctx, next); + break; + case 'rss': + await rssFeed(ctx, next); + break; + case 'atom': + await atomFeed(ctx, next); + break; + } + return; + } + + await userPage(ctx, next); +}); + // Atom -router.get('/@:user.atom', async ctx => { +const atomFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -242,10 +277,10 @@ router.get('/@:user.atom', async ctx => { } else { ctx.status = 404; } -}); +}; // RSS -router.get('/@:user.rss', async ctx => { +const rssFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -254,10 +289,10 @@ router.get('/@:user.rss', async ctx => { } else { ctx.status = 404; } -}); +}; // JSON -router.get('/@:user.json', async ctx => { +const jsonFeed: Router.Middleware = async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { @@ -266,43 +301,47 @@ router.get('/@:user.json', async ctx => { } else { ctx.status = 404; } -}); +}; //#region SSR (for crawlers) // User -router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { - const { username, host } = Acct.parse(ctx.params.user); +const userPage: Router.Middleware = async (ctx, next) => { + const userParam = ctx.params.user; + const subParam = ctx.params.sub; + const { username, host } = Acct.parse(userParam); + const user = await Users.findOneBy({ usernameLower: username.toLowerCase(), host: host ?? IsNull(), isSuspended: false, }); - if (user != null) { - const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); - const meta = await fetchMeta(); - const me = profile.fields - ? profile.fields - .filter(filed => filed.value != null && filed.value.match(/^https?:/)) - .map(field => field.value) - : []; - - await ctx.render('user', { - user, profile, me, - avatarUrl: await Users.getAvatarUrl(user), - sub: ctx.params.sub, - instanceName: meta.name || 'Calckey', - icon: meta.iconUrl, - themeColor: meta.themeColor, - privateMode: meta.privateMode, - }); - ctx.set('Cache-Control', 'public, max-age=15'); - } else { - // リモートユーザーなので - // モデレータがAPI経由で参照可能にするために404にはしない + if (user === null) { await next(); + return; } -}); + + const profile = await UserProfiles.findOneByOrFail({ userId: user.id }); + const meta = await fetchMeta(); + const me = profile.fields + ? profile.fields + .filter(filed => filed.value != null && filed.value.match(/^https?:/)) + .map(field => field.value) + : []; + + const userDetail = { + user, profile, me, + avatarUrl: await Users.getAvatarUrl(user), + sub: subParam, + instanceName: meta.name || 'Calckey', + icon: meta.iconUrl, + themeColor: meta.themeColor, + privateMode: meta.privateMode, + }; + + await ctx.render('user', userDetail); + ctx.set('Cache-Control', 'public, max-age=15'); +}; router.get('/users/:user', async ctx => { const user = await Users.findOneBy({ diff --git a/packages/client/src/components/MkNote.vue b/packages/client/src/components/MkNote.vue index 80441f34d9..597d20c5bf 100644 --- a/packages/client/src/components/MkNote.vue +++ b/packages/client/src/components/MkNote.vue @@ -426,7 +426,8 @@ function readPromo() { > .article { display: flex; padding: 28px 32px 18px; - + cursor: pointer; + > .avatar { flex-shrink: 0; display: block; diff --git a/packages/client/src/components/MkNoteDetailed.vue b/packages/client/src/components/MkNoteDetailed.vue index 19feacec4e..233143ca67 100644 --- a/packages/client/src/components/MkNoteDetailed.vue +++ b/packages/client/src/components/MkNoteDetailed.vue @@ -346,6 +346,8 @@ if (appearNote.replyId) { > .reply-to-more { opacity: 0.7; + cursor: pointer; + } > .renote { @@ -543,6 +545,7 @@ if (appearNote.replyId) { > .reply { border-top: solid 0.5px var(--divider); + cursor: pointer; } > .reply, .reply-to, .reply-to-more { diff --git a/packages/client/src/components/MkNoteSub.vue b/packages/client/src/components/MkNoteSub.vue index c8041be421..c1943920df 100644 --- a/packages/client/src/components/MkNoteSub.vue +++ b/packages/client/src/components/MkNoteSub.vue @@ -65,6 +65,7 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item &.children { padding: 10px 0 0 16px; font-size: 1em; + cursor: auto; &.max-width_450px { padding: 10px 0 0 8px; @@ -86,9 +87,11 @@ const replies: misskey.entities.Note[] = props.conversation?.filter(item => item > .body { flex: 1; min-width: 0; - + cursor: pointer; + > .header { margin-bottom: 2px; + cursor: auto; } > .body {