diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f366ecc9..5ca658347d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ ChangeLog ========= +unreleased +---------- +* Pleromaとのフェデレーションを修正 +* インスタンスのキャラクター画像を設定できるように +* Catモードの朝鮮語対応 +* CWが付いた投稿に返信する際、そのCWを引き継ぐように + 10.73.0 ------- * テーマの強化 diff --git a/locales/en-US.yml b/locales/en-US.yml index 89f8236c7d..31741af150 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -992,6 +992,7 @@ admin/views/instance.vue: instance-name: "Instance name" instance-description: "Instance description" host: "Host" + logo-url: "Logo image URL" banner-url: "Banner image URL" error-image-url: "Error image URL" languages: "Language of this instance" diff --git a/package.json b/package.json index 6479252868..b6517135dc 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@fortawesome/fontawesome-svg-core": "1.2.12", - "@fortawesome/free-brands-svg-icons": "5.6.0", + "@fortawesome/free-brands-svg-icons": "5.6.3", "@fortawesome/free-regular-svg-icons": "5.5.0", "@fortawesome/free-solid-svg-icons": "5.6.3", "@fortawesome/vue-fontawesome": "0.1.2", @@ -34,7 +34,7 @@ "@types/debug": "0.0.31", "@types/deep-equal": "1.0.1", "@types/double-ended-queue": "2.1.0", - "@types/elasticsearch": "5.0.29", + "@types/elasticsearch": "5.0.30", "@types/file-type": "10.6.0", "@types/gulp": "3.8.36", "@types/gulp-mocha": "0.0.32", @@ -46,7 +46,7 @@ "@types/is-url": "1.2.28", "@types/js-yaml": "3.11.4", "@types/katex": "0.5.0", - "@types/koa": "2.0.47", + "@types/koa": "2.0.48", "@types/koa-bodyparser": "5.0.2", "@types/koa-compress": "2.0.8", "@types/koa-favicon": "2.0.19", @@ -70,7 +70,7 @@ "@types/pug": "2.0.4", "@types/qrcode": "1.3.0", "@types/ratelimiter": "2.1.28", - "@types/redis": "2.8.8", + "@types/redis": "2.8.10", "@types/request": "2.48.1", "@types/request-promise-native": "1.0.15", "@types/rimraf": "2.0.2", @@ -87,7 +87,7 @@ "@types/websocket": "0.0.40", "@types/ws": "6.0.1", "animejs": "2.2.0", - "apexcharts": "2.4.2", + "apexcharts": "2.5.1", "autobind-decorator": "2.4.0", "autosize": "4.0.2", "autwh": "0.1.0", @@ -116,7 +116,7 @@ "eventemitter3": "3.1.0", "feed": "2.0.2", "file-loader": "2.0.0", - "file-type": "10.6.0", + "file-type": "10.7.0", "fuckadblock": "3.2.1", "gulp": "3.9.1", "gulp-cssnano": "2.1.3", @@ -222,7 +222,7 @@ "vue-color": "2.7.0", "vue-content-loading": "1.5.3", "vue-cropperjs": "3.0.0", - "vue-i18n": "8.3.2", + "vue-i18n": "8.6.0", "vue-js-modal": "1.3.28", "vue-loader": "15.4.2", "vue-marquee-text-component": "1.1.0", @@ -237,7 +237,7 @@ "vuex-persistedstate": "2.5.4", "web-push": "3.3.3", "webfinger.js": "2.7.0", - "webpack": "4.26.1", + "webpack": "4.28.3", "webpack-cli": "3.1.2", "websocket": "1.0.28", "ws": "6.1.2", diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index 5a29da0345..6fd92a779f 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -6,6 +6,7 @@ <ui-input :value="host" readonly>{{ $t('host') }}</ui-input> <ui-input v-model="name">{{ $t('instance-name') }}</ui-input> <ui-textarea v-model="description">{{ $t('instance-description') }}</ui-textarea> + <ui-input v-model="mascotImageUrl"><i slot="icon"><fa icon="link"/></i>{{ $t('logo-url') }}</ui-input> <ui-input v-model="bannerUrl"><i slot="icon"><fa icon="link"/></i>{{ $t('banner-url') }}</ui-input> <ui-input v-model="errorImageUrl"><i slot="icon"><fa icon="link"/></i>{{ $t('error-image-url') }}</ui-input> <ui-input v-model="languages"><i slot="icon"><fa icon="language"/></i>{{ $t('languages') }}<span slot="desc">{{ $t('languages-desc') }}</span></ui-input> @@ -149,6 +150,7 @@ export default Vue.extend({ maintainerEmail: null, disableRegistration: false, disableLocalTimeline: false, + mascotImageUrl: null, bannerUrl: null, errorImageUrl: null, name: null, @@ -196,6 +198,7 @@ export default Vue.extend({ this.maintainerEmail = meta.maintainer.email; this.disableRegistration = meta.disableRegistration; this.disableLocalTimeline = meta.disableLocalTimeline; + this.mascotImageUrl = meta.mascotImageUrl; this.bannerUrl = meta.bannerUrl; this.errorImageUrl = meta.errorImageUrl; this.name = meta.name; @@ -253,6 +256,7 @@ export default Vue.extend({ maintainerEmail: this.maintainerEmail, disableRegistration: this.disableRegistration, disableLocalTimeline: this.disableLocalTimeline, + mascotImageUrl: this.mascotImageUrl, bannerUrl: this.bannerUrl, errorImageUrl: this.errorImageUrl, name: this.name, diff --git a/src/client/app/common/views/components/acct.vue b/src/client/app/common/views/components/acct.vue index 69259ab60f..2730e4139c 100644 --- a/src/client/app/common/views/components/acct.vue +++ b/src/client/app/common/views/components/acct.vue @@ -2,6 +2,7 @@ <span class="mk-acct"> <span class="name">@{{ user.username }}</span> <span class="host" :class="{ fade: $store.state.settings.contrastedAcct }" v-if="user.host || detail || $store.state.settings.showFullAcct">@{{ user.host || host }}</span> + <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/> </span> </template> @@ -23,4 +24,8 @@ export default Vue.extend({ .mk-acct > .host.fade opacity 0.5 + + > .locked + opacity 0.8 + margin-left 0.5em </style> diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue index 038541ba05..958abe00f0 100644 --- a/src/client/app/common/views/components/url-preview.vue +++ b/src/client/app/common/views/components/url-preview.vue @@ -8,16 +8,16 @@ </blockquote> </div> <div v-else class="mk-url-preview"> - <a :class="{ mini }" :href="url" target="_blank" :title="url" v-if="!fetching"> + <a :class="{ mini, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> <div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div> <article> <header> - <h1>{{ title }}</h1> + <h1 :title="title">{{ title }}</h1> </header> - <p v-if="description">{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p> + <p v-if="description" :title="description">{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p> <footer> <img class="icon" v-if="icon" :src="icon"/> - <p>{{ sitename }}</p> + <p :title="sitename">{{ sitename }}</p> </footer> </article> </a> @@ -120,6 +120,12 @@ export default Vue.extend({ default: false }, + compact: { + type: Boolean, + required: false, + default: false + }, + mini: { type: Boolean, required: false, @@ -302,6 +308,23 @@ export default Vue.extend({ width 12px height 12px + &.compact + > .thumbnail + position: absolute + width 56px + height 100% + + > article + left 56px + width calc(100% - 56px) + padding 4px + + > header + margin-bottom 2px + + > footer + margin-top 2px + &.mini font-size 10px @@ -325,4 +348,27 @@ export default Vue.extend({ width 12px height 12px + &.compact + > .thumbnail + position: absolute + width 56px + height 100% + + > article + left 56px + width calc(100% - 56px) + padding 4px + + > header + margin-bottom 2px + + > footer + margin-top 2px + + &.compact + > article + > header h1, p, footer + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; </style> diff --git a/src/client/app/desktop/views/components/note.vue b/src/client/app/desktop/views/components/note.vue index a52eaac7e9..e5f233a552 100644 --- a/src/client/app/desktop/views/components/note.vue +++ b/src/client/app/desktop/views/components/note.vue @@ -36,7 +36,7 @@ <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote" :mini="mini"/></div> - <mk-url-preview v-for="url in urls" :url="url" :key="url" :mini="mini"/> + <mk-url-preview v-for="url in urls" :url="url" :key="url" :mini="mini" :compact="compact"/> </div> </div> <footer v-if="appearNote.deletedAt == null"> @@ -102,6 +102,11 @@ export default Vue.extend({ required: false, default: false }, + compact: { + type: Boolean, + required: false, + default: false + }, mini: { type: Boolean, required: false, diff --git a/src/client/app/desktop/views/components/notes.vue b/src/client/app/desktop/views/components/notes.vue index da11893225..5cf51d9cc4 100644 --- a/src/client/app/desktop/views/components/notes.vue +++ b/src/client/app/desktop/views/components/notes.vue @@ -15,7 +15,7 @@ <!-- トランジションを有効にするとなぜかメモリリークする --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="notes transition" tag="div" ref="notes"> <template v-for="(note, i) in _notes"> - <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" ref="note"/> + <x-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true" ref="note"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span> diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 98c16cd5d0..27e487946b 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -77,6 +77,7 @@ import extractMentions from '../../../../../misc/extract-mentions'; export default Vue.extend({ i18n: i18n('desktop/views/components/post-form.vue'), + components: { XDraggable, MkVisibilityChooser @@ -197,11 +198,11 @@ export default Vue.extend({ const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; // 自分は除外 - if (this.$store.state.i.username == x.username && x.host == null) return; - if (this.$store.state.i.username == x.username && x.host == host) return; + if (this.$store.state.i.username == x.username && x.host == null) continue; + if (this.$store.state.i.username == x.username && x.host == host) continue; // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) return; + if (this.text.indexOf(`${mention} `) != -1) continue; this.text += `${mention} `; } @@ -221,6 +222,12 @@ export default Vue.extend({ }); } + // keep cw when reply + if (this.reply && this.reply.cw != null) { + this.useCw = true; + this.cw = this.reply.cw; + } + this.$nextTick(() => { // 書きかけの投稿を復元 if (!this.instant && !this.mention) { diff --git a/src/client/app/desktop/views/components/renote-form.vue b/src/client/app/desktop/views/components/renote-form.vue index e6a09c3eea..d8f38bedfc 100644 --- a/src/client/app/desktop/views/components/renote-form.vue +++ b/src/client/app/desktop/views/components/renote-form.vue @@ -21,7 +21,14 @@ import i18n from '../../../i18n'; export default Vue.extend({ i18n: i18n('desktop/views/components/renote-form.vue'), - props: ['note'], + + props: { + note: { + type: Object, + required: true + } + }, + data() { return { wait: false, @@ -29,6 +36,7 @@ export default Vue.extend({ visibility: this.$store.state.settings.defaultNoteVisibility }; }, + methods: { ok(v: string) { this.wait = true; @@ -44,9 +52,11 @@ export default Vue.extend({ this.wait = false; }); }, + cancel() { this.$emit('canceled'); }, + onQuote() { this.quote = true; @@ -54,6 +64,7 @@ export default Vue.extend({ (this.$refs.form as any).focus(); }); }, + onChildFormPosted() { this.$emit('posted'); } diff --git a/src/client/app/desktop/views/components/user-card.vue b/src/client/app/desktop/views/components/user-card.vue index c59467fd09..6a42b5da00 100644 --- a/src/client/app/desktop/views/components/user-card.vue +++ b/src/client/app/desktop/views/components/user-card.vue @@ -7,7 +7,8 @@ <router-link :to="user | userPage" class="name"> <mk-user-name :user="user"/> </router-link> - <span class="username">@{{ user | acct }}</span> + <span class="username">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span> + <div class="description"> <misskey-flavored-markdown v-if="user.description" :text="user.description" :author="user" :i="$store.state.i" :custom-emojis="user.emojis"/> </div> @@ -75,6 +76,9 @@ export default Vue.extend({ display block opacity 0.7 + > .locked + opacity 0.8 + > .description margin 8px 0 16px 0 diff --git a/src/client/app/desktop/views/pages/deck/deck.notes.vue b/src/client/app/desktop/views/pages/deck/deck.notes.vue index 994e12a04b..54e01a0012 100644 --- a/src/client/app/desktop/views/pages/deck/deck.notes.vue +++ b/src/client/app/desktop/views/pages/deck/deck.notes.vue @@ -18,6 +18,7 @@ :key="note.id" @update:note="onNoteUpdated(i, $event)" :media-view="mediaView" + :compact="true" :mini="true"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> diff --git a/src/client/app/desktop/views/pages/deck/deck.user-column.vue b/src/client/app/desktop/views/pages/deck/deck.user-column.vue index 0ed7916a4b..65feea9693 100644 --- a/src/client/app/desktop/views/pages/deck/deck.user-column.vue +++ b/src/client/app/desktop/views/pages/deck/deck.user-column.vue @@ -19,7 +19,7 @@ <span class="name"> <mk-user-name :user="user"/> </span> - <span class="acct">@{{ user | acct }}</span> + <span class="acct">@{{ user | acct }} <fa v-if="user.isLocked == true" class="locked" icon="lock" fixed-width/></span> </div> </header> <div class="info"> @@ -411,6 +411,9 @@ export default Vue.extend({ opacity 0.7 text-shadow 0 0 8px #000 + > .locked + opacity 0.8 + > .info padding 16px font-size 12px diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue index 8abadf2c69..88a11eafa6 100644 --- a/src/client/app/desktop/views/pages/welcome.vue +++ b/src/client/app/desktop/views/pages/welcome.vue @@ -35,7 +35,7 @@ <span class="signin" @click="signin">{{ $t('signin') }}</span> </p> - <img src="/assets/ai.png" alt="" title="藍" class="char"> + <img :src="meta.mascotImageUrl" alt="" title="藍" class="char"> </div> </div> diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index 57d8fb2a08..1533e0a62a 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -30,7 +30,7 @@ <mk-media-list :media-list="appearNote.files"/> </div> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> - <mk-url-preview v-for="url in urls" :url="url" :key="url"/> + <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> </div> @@ -90,6 +90,11 @@ export default Vue.extend({ note: { type: Object, required: true + }, + compact: { + type: Boolean, + required: false, + default: false } } }); diff --git a/src/client/app/mobile/views/components/notes.vue b/src/client/app/mobile/views/components/notes.vue index fe88fee84d..1d0375cfa9 100644 --- a/src/client/app/mobile/views/components/notes.vue +++ b/src/client/app/mobile/views/components/notes.vue @@ -15,7 +15,7 @@ <!-- トランジションを有効にするとなぜかメモリリークする --> <component :is="!$store.state.device.reduceMotion ? 'transition-group' : 'div'" name="mk-notes" class="transition" tag="div"> <template v-for="(note, i) in _notes"> - <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)"/> + <mk-note :note="note" :key="note.id" @update:note="onNoteUpdated(i, $event)" :compact="true"/> <p class="date" :key="note.id + '_date'" v-if="i != notes.length - 1 && note._date != _notes[i + 1]._date"> <span><fa icon="angle-up"/>{{ note._datetext }}</span> <span><fa icon="angle-down"/>{{ _notes[i + 1]._datetext }}</span> diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 00d0eebd6a..47af21c2a5 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -187,11 +187,11 @@ export default Vue.extend({ const mention = x.host ? `@${x.username}@${toASCII(x.host)}` : `@${x.username}`; // 自分は除外 - if (this.$store.state.i.username == x.username && x.host == null) return; - if (this.$store.state.i.username == x.username && x.host == host) return; + if (this.$store.state.i.username == x.username && x.host == null) continue; + if (this.$store.state.i.username == x.username && x.host == host) continue; // 重複は除外 - if (this.text.indexOf(`${mention} `) != -1) return; + if (this.text.indexOf(`${mention} `) != -1) continue; this.text += `${mention} `; } @@ -211,6 +211,12 @@ export default Vue.extend({ }); } + // keep cw when reply + if (this.reply && this.reply.cw != null) { + this.useCw = true; + this.cw = this.reply.cw; + } + this.focus(); this.$nextTick(() => { diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts index a49e4dbe78..e6488da395 100644 --- a/src/misc/fetch-meta.ts +++ b/src/misc/fetch-meta.ts @@ -19,6 +19,7 @@ const defaultMeta: any = { enableExternalUserRecommendation: false, externalUserRecommendationEngine: 'https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}', externalUserRecommendationTimeout: 300000, + mascotImageUrl: '/assets/ai.png', errorImageUrl: 'https://ai.misskey.xyz/aiart/yubitun.png', enableServiceWorker: false }; diff --git a/src/models/meta.ts b/src/models/meta.ts index fd07438a81..daa7964d6f 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -185,6 +185,7 @@ export type IMeta = { disableRegistration?: boolean; disableLocalTimeline?: boolean; hidedTags?: string[]; + mascotImageUrl?: string; bannerUrl?: string; errorImageUrl?: string; diff --git a/src/models/note.ts b/src/models/note.ts index f334970750..f2fb39051b 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -372,7 +372,14 @@ export const pack = async ( //#endregion if (_note.user.isCat && _note.text) { - _note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ'); + _note.text = (_note.text + // ja-JP + .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') + // ko-KR + .replace(/[나-낳]/g, (match: string) => String.fromCharCode( + match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0) + )) + ); } if (!opts.skipHide) { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index 42d90ab91f..9adc3dd943 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -83,7 +83,7 @@ router.get('/notes/:note', async (ctx, next) => { } ctx.body = pack(await renderNote(note, false)); - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + ctx.set('Cache-Control', 'public, max-age=180'); setResponseType(ctx); }); @@ -162,7 +162,9 @@ async function userInfo(ctx: Router.IRouterContext, user: IUser) { setResponseType(ctx); } -router.get('/users/:user', async ctx => { +router.get('/users/:user', async (ctx, next) => { + if (!isActivityPubReq(ctx)) return await next(); + if (!ObjectID.isValid(ctx.params.user)) { ctx.status = 404; return; diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 6ceb2a98dc..cf117ebd71 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -39,6 +39,13 @@ export const meta = { } }, + mascotImageUrl: { + validator: $.str.optional.nullable, + desc: { + 'ja-JP': 'インスタンスキャラクター画像のURL' + } + }, + bannerUrl: { validator: $.str.optional.nullable, desc: { @@ -328,6 +335,10 @@ export default define(meta, (ps) => new Promise(async (res, rej) => { set.hidedTags = ps.hidedTags; } + if (ps.mascotImageUrl !== undefined) { + set.mascotImageUrl = ps.mascotImageUrl; + } + if (ps.bannerUrl !== undefined) { set.bannerUrl = ps.bannerUrl; } diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index d406f59dc9..fa933513b7 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -65,6 +65,7 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => { enableRecaptcha: instance.enableRecaptcha, recaptchaSiteKey: instance.recaptchaSiteKey, swPublickey: instance.swPublicKey, + mascotImageUrl: instance.mascotImageUrl, bannerUrl: instance.bannerUrl, errorImageUrl: instance.errorImageUrl, maxNoteTextLength: instance.maxNoteTextLength, diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index c9f70d9658..6c90898250 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -45,7 +45,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => { } if (note.deletedAt != null) { - return rej('this not is already deleted'); + return rej('this note is already deleted'); } try { diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 998fd2adf2..59b8390ab5 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -148,6 +148,27 @@ router.get('/@:user', async (ctx, next) => { } }); +router.get('/users/:user', async ctx => { + if (!ObjectID.isValid(ctx.params.user)) { + ctx.status = 404; + return; + } + + const userId = new ObjectID(ctx.params.user); + + const user = await User.findOne({ + _id: userId, + host: null + }); + + if (user === null) { + ctx.status = 404; + return; + } + + ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); +}); + // Note router.get('/notes/:note', async ctx => { if (ObjectID.isValid(ctx.params.note)) { @@ -159,7 +180,12 @@ router.get('/notes/:note', async ctx => { note: _note, summary: getNoteSummary(_note) }); - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + + if (['public', 'home'].includes(note.visibility)) { + ctx.set('Cache-Control', 'public, max-age=180'); + } else { + ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + } return; }