diff options
| author | syuilo <syuilotan@yahoo.co.jp> | 2019-05-14 21:25:24 +0900 |
|---|---|---|
| committer | syuilo <syuilotan@yahoo.co.jp> | 2019-05-14 21:25:24 +0900 |
| commit | 466fe9c3684082e352bc294bed82c7769fedf495 (patch) | |
| tree | d963c2c09d898b22f7530c717181d64ca98ab9c8 /src | |
| parent | Merge branch 'develop' (diff) | |
| parent | 11.13.0 (diff) | |
| download | misskey-466fe9c3684082e352bc294bed82c7769fedf495.tar.gz misskey-466fe9c3684082e352bc294bed82c7769fedf495.tar.bz2 misskey-466fe9c3684082e352bc294bed82c7769fedf495.zip | |
Merge branch 'develop'
Diffstat (limited to 'src')
40 files changed, 265 insertions, 73 deletions
diff --git a/src/client/app/admin/views/federation.vue b/src/client/app/admin/views/federation.vue index 6b09a3c28e..8b7719069c 100644 --- a/src/client/app/admin/views/federation.vue +++ b/src/client/app/admin/views/federation.vue @@ -54,7 +54,6 @@ <span>{{ $t('latest-request-received-at') }}</span> <template #prefix><fa :icon="faInbox"/></template> </ui-input> - <ui-switch v-model="instance.isBlocked" @change="updateInstance()">{{ $t('block') }}</ui-switch> <ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()">{{ $t('marked-as-closed') }}</ui-switch> <details> <summary>{{ $t('charts') }}</summary> @@ -80,6 +79,10 @@ <div ref="chart"></div> </details> <details> + <summary>{{ $t('delete-all-files') }}</summary> + <ui-button @click="deleteAllFiles()" style="margin-top: 16px;"><fa :icon="faTrashAlt"/> {{ $t('delete-all-files') }}</ui-button> + </details> + <details> <summary>{{ $t('remove-all-following') }}</summary> <ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button> <ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info> @@ -130,7 +133,7 @@ <span>{{ $t('status') }}</span> </header> <div v-for="instance in instances" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }"> - <a @click.prevent="showInstance(instance.host)" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a> + <a @click.prevent="showInstance(instance.host)" rel="nofollow noopener" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a> <span>{{ instance.notesCount | number }}</span> <span>{{ instance.usersCount | number }}</span> <span>{{ instance.followingCount | number }}</span> @@ -142,6 +145,16 @@ <ui-info v-if="instances.length == limit">{{ $t('result-is-truncated', { n: limit }) }}</ui-info> </section> </ui-card> + + <ui-card> + <template #title><fa :icon="faBan"/> {{ $t('blocked-hosts') }}</template> + <section class="fit-top"> + <ui-textarea v-model="blockedHosts"> + <template #desc>{{ $t('blocked-hosts-info') }}</template> + </ui-textarea> + <ui-button @click="saveBlockedHosts">{{ $t('save') }}</ui-button> + </section> + </ui-card> </div> </template> @@ -149,7 +162,7 @@ import Vue from 'vue'; import i18n from '../../i18n'; import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'; -import { faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faTrafficLight, faInbox } from '@fortawesome/free-solid-svg-icons'; +import { faTrashAlt, faBan, faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faTrafficLight, faInbox } from '@fortawesome/free-solid-svg-icons'; import ApexCharts from 'apexcharts'; import * as tinycolor from 'tinycolor2'; @@ -176,7 +189,8 @@ export default Vue.extend({ chartSrc: 'requests', chartSpan: 'hour', chartInstance: null, - faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faPaperPlane, faTrafficLight, faInbox + blockedHosts: '', + faTrashAlt, faBan, faGlobe, faTerminal, faSearch, faMinusCircle, faServer, faCrosshairs, faEnvelopeOpenText, faUsers, faCaretDown, faCaretUp, faPaperPlane, faTrafficLight, faInbox }; }, @@ -246,6 +260,10 @@ export default Vue.extend({ mounted() { this.fetchInstances(); + + this.$root.getMeta().then(meta => { + this.blockedHosts = meta.blockedHosts.join('\n'); + }); }, beforeDestroy() { @@ -293,6 +311,17 @@ export default Vue.extend({ }); }, + deleteAllFiles() { + this.$root.api('admin/federation/delete-all-files', { + host: this.instance.host + }).then(() => { + this.$root.dialog({ + type: 'success', + splash: true + }); + }); + }, + updateInstance() { this.$root.api('admin/federation/update-instance', { host: this.instance.host, @@ -477,6 +506,22 @@ export default Vue.extend({ }] }; }, + + saveBlockedHosts() { + this.$root.api('admin/update-meta', { + blockedHosts: this.blockedHosts.split('\n') + }).then(() => { + this.$root.dialog({ + type: 'success', + text: this.$t('saved') + }); + }).catch(e => { + this.$root.dialog({ + type: 'error', + text: e + }); + }); + } } }); </script> diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index d81edc8fe6..5cdd22296f 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -10,6 +10,9 @@ <ui-input v-model="mascotImageUrl"><template #icon><fa icon="link"/></template>{{ $t('logo-url') }}</ui-input> <ui-input v-model="bannerUrl"><template #icon><fa icon="link"/></template>{{ $t('banner-url') }}</ui-input> <ui-input v-model="errorImageUrl"><template #icon><fa icon="link"/></template>{{ $t('error-image-url') }}</ui-input> + <ui-input v-model="ToSUrl"><template #icon><fa icon="link"/></template>{{ $t('tos-url') }}</ui-input> + <ui-input v-model="repositoryUrl"><template #icon><fa icon="link"/></template>{{ $t('repository-url') }}</ui-input> + <ui-input v-model="feedbackUrl"><template #icon><fa icon="link"/></template>{{ $t('feedback-url') }}</ui-input> <ui-input v-model="languages"><template #icon><fa icon="language"/></template>{{ $t('languages') }}<template #desc>{{ $t('languages-desc') }}</template></ui-input> </section> <section class="fit-bottom"> @@ -83,9 +86,11 @@ </ui-card> <ui-card> - <template #title>{{ $t('pinned-users') }}</template> - <section> - <ui-textarea v-model="pinnedUsers"></ui-textarea> + <template #title><fa :icon="faThumbtack"/> {{ $t('pinned-users') }}</template> + <section class="fit-top"> + <ui-textarea v-model="pinnedUsers"> + <template #desc>{{ $t('pinned-users-info') }}</template> + </ui-textarea> <ui-button @click="updateMeta">{{ $t('save') }}</ui-button> </section> </ui-card> @@ -144,7 +149,7 @@ import Vue from 'vue'; import i18n from '../../i18n'; import { url, host } from '../../config'; import { toUnicode } from 'punycode'; -import { faHeadset, faShieldAlt, faGhost, faUserPlus, faBolt } from '@fortawesome/free-solid-svg-icons'; +import { faHeadset, faShieldAlt, faGhost, faUserPlus, faBolt, faThumbtack } from '@fortawesome/free-solid-svg-icons'; import { faEnvelope as farEnvelope } from '@fortawesome/free-regular-svg-icons'; export default Vue.extend({ @@ -156,6 +161,9 @@ export default Vue.extend({ host: toUnicode(host), maintainerName: null, maintainerEmail: null, + ToSUrl: null, + repositoryUrl: "https://github.com/syuilo/misskey", + feedbackUrl: null, disableRegistration: false, disableLocalTimeline: false, disableGlobalTimeline: false, @@ -198,8 +206,8 @@ export default Vue.extend({ enableServiceWorker: false, swPublicKey: null, swPrivateKey: null, - pinnedUsers: [], - faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt + pinnedUsers: '', + faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope, faBolt, faThumbtack }; }, @@ -207,6 +215,9 @@ export default Vue.extend({ this.$root.getMeta(true).then(meta => { this.maintainerName = meta.maintainerName; this.maintainerEmail = meta.maintainerEmail; + this.ToSUrl = meta.ToSUrl; + this.repositoryUrl = meta.repositoryUrl; + this.feedbackUrl = meta.feedbackUrl; this.disableRegistration = meta.disableRegistration; this.disableLocalTimeline = meta.disableLocalTimeline; this.disableGlobalTimeline = meta.disableGlobalTimeline; @@ -268,6 +279,9 @@ export default Vue.extend({ this.$root.api('admin/update-meta', { maintainerName: this.maintainerName, maintainerEmail: this.maintainerEmail, + ToSUrl: this.ToSUrl, + repositoryUrl: this.repositoryUrl, + feedbackUrl: this.feedbackUrl, disableRegistration: this.disableRegistration, disableLocalTimeline: this.disableLocalTimeline, disableGlobalTimeline: this.disableGlobalTimeline, diff --git a/src/client/app/common/views/components/forkit.vue b/src/client/app/common/views/components/forkit.vue index 5629c5ac24..d652b846a4 100644 --- a/src/client/app/common/views/components/forkit.vue +++ b/src/client/app/common/views/components/forkit.vue @@ -1,5 +1,5 @@ <template> -<a class="a" :href="repositoryUrl" target="_blank" title="View source on GitHub"> +<a class="a" :href="repositoryUrl" rel="noopener" target="_blank" title="View source on GitHub"> <svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden"> <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> <path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path> diff --git a/src/client/app/common/views/components/integrations.integration.vue b/src/client/app/common/views/components/integrations.integration.vue index 8be9d99554..51995843b1 100644 --- a/src/client/app/common/views/components/integrations.integration.vue +++ b/src/client/app/common/views/components/integrations.integration.vue @@ -1,5 +1,5 @@ <template> -<a class="zxrjzpcj" :href="url" :class="service" target="_blank"> +<a class="zxrjzpcj" :href="url" :class="service" rel="noopener" target="_blank"> <fa :icon="icon" size="lg" fixed-width /><span>{{ text }}</span> </a> </template> diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue index 256ea760b3..908533e0cc 100644 --- a/src/client/app/common/views/components/messaging-room.message.vue +++ b/src/client/app/common/views/components/messaging-room.message.vue @@ -9,7 +9,7 @@ <div class="content" v-if="!message.isDeleted"> <mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> <div class="file" v-if="message.file"> - <a :href="message.file.url" target="_blank" :title="message.file.name"> + <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name"> <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name" :style="{ backgroundColor: message.file.properties.avgColor || 'transparent' }"/> <p v-else>{{ message.file.name }}</p> diff --git a/src/client/app/common/views/components/mfm.ts b/src/client/app/common/views/components/mfm.ts index 78734200a7..9459172881 100644 --- a/src/client/app/common/views/components/mfm.ts +++ b/src/client/app/common/views/components/mfm.ts @@ -174,6 +174,7 @@ export default Vue.component('misskey-flavored-markdown', { key: Math.random(), props: { url: token.node.props.url, + rel: 'nofollow noopener', target: '_blank' }, attrs: { @@ -187,6 +188,7 @@ export default Vue.component('misskey-flavored-markdown', { attrs: { class: 'link', href: token.node.props.url, + rel: 'nofollow noopener', target: '_blank', title: token.node.props.url, style: 'color:var(--mfmLink);' diff --git a/src/client/app/common/views/components/nav.vue b/src/client/app/common/views/components/nav.vue index a8f9bb9289..da26fd1b8e 100644 --- a/src/client/app/common/views/components/nav.vue +++ b/src/client/app/common/views/components/nav.vue @@ -1,12 +1,16 @@ <template> <span class="mk-nav"> <a :href="aboutUrl">{{ $t('about') }}</a> + <template v-if="ToSUrl !== null"> + <i>・</i> + <a :href="ToSUrl" target="_blank">{{ $t('tos') }}</a> + </template> <i>・</i> - <a :href="repositoryUrl">{{ $t('repository') }}</a> + <a :href="repositoryUrl" rel="noopener" target="_blank">{{ $t('repository') }}</a> <i>・</i> - <a :href="feedbackUrl" target="_blank">{{ $t('feedback') }}</a> + <a :href="feedbackUrl" rel="noopener" target="_blank">{{ $t('feedback') }}</a> <i>・</i> - <a href="/dev">{{ $t('develop') }}</a> + <a href="/dev" target="_blank">{{ $t('develop') }}</a> </span> </template> @@ -21,8 +25,17 @@ export default Vue.extend({ return { aboutUrl: `/docs/${lang}/about`, repositoryUrl: 'https://github.com/syuilo/misskey', - feedbackUrl: 'https://github.com/syuilo/misskey/issues/new' + feedbackUrl: 'https://github.com/syuilo/misskey/issues/new', + ToSUrl: null } + }, + + mounted() { + this.$root.getMeta(true).then(meta => { + this.repositoryUrl = meta.repositoryUrl; + this.feedbackUrl = meta.feedbackUrl; + this.ToSUrl = meta.ToSUrl; + }) } }); </script> diff --git a/src/client/app/common/views/components/settings/2fa.vue b/src/client/app/common/views/components/settings/2fa.vue index 07a1493151..6e8d19d83a 100644 --- a/src/client/app/common/views/components/settings/2fa.vue +++ b/src/client/app/common/views/components/settings/2fa.vue @@ -9,7 +9,7 @@ </template> <div v-if="data && !$store.state.i.twoFactorEnabled"> <ol> - <li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" target="_blank">{{ $t('howtoinstall') }}</a></li> + <li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank">{{ $t('howtoinstall') }}</a></li> <li>{{ $t('scan') }}<br><img :src="data.qr"></li> <li>{{ $t('done') }}<br> <ui-input v-model="token">{{ $t('token') }}</ui-input> diff --git a/src/client/app/common/views/components/settings/integration.vue b/src/client/app/common/views/components/settings/integration.vue index 8ac0c134c0..71ad8b4509 100644 --- a/src/client/app/common/views/components/settings/integration.vue +++ b/src/client/app/common/views/components/settings/integration.vue @@ -4,21 +4,21 @@ <section v-if="enableTwitterIntegration"> <header><fa :icon="['fab', 'twitter']"/> Twitter</header> - <p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> + <p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> <ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button> <ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button> </section> <section v-if="enableDiscordIntegration"> <header><fa :icon="['fab', 'discord']"/> Discord</header> - <p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p> + <p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p> <ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button> <ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button> </section> <section v-if="enableGithubIntegration"> <header><fa :icon="['fab', 'github']"/> GitHub</header> - <p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> + <p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.github.login }}</a></p> <ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button> <ui-button v-else @click="connectGithub">{{ $t('connect') }}</ui-button> </section> diff --git a/src/client/app/common/views/components/settings/settings.vue b/src/client/app/common/views/components/settings/settings.vue index be2d7fce85..4d4cac0976 100644 --- a/src/client/app/common/views/components/settings/settings.vue +++ b/src/client/app/common/views/components/settings/settings.vue @@ -543,8 +543,8 @@ export default Vue.extend({ }); } else { this.$root.dialog({ - title: this.$t('update-available'), - text: this.$t('update-available-desc') + title: this.$t('@._settings.update-available'), + text: this.$t('@._settings.update-available-desc') }); } }); diff --git a/src/client/app/common/views/components/settings/theme.vue b/src/client/app/common/views/components/settings/theme.vue index b1f7b2bc49..3e6b9133cd 100644 --- a/src/client/app/common/views/components/settings/theme.vue +++ b/src/client/app/common/views/components/settings/theme.vue @@ -45,7 +45,7 @@ </ui-select> </label> - <a href="https://assets.msky.cafe/theme/list" target="_blank">{{ $t('find-more-theme') }}</a> + <a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank">{{ $t('find-more-theme') }}</a> <details class="creator"> <summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary> diff --git a/src/client/app/common/views/components/signup.vue b/src/client/app/common/views/components/signup.vue index 9b1df79771..bf8e5d25b8 100644 --- a/src/client/app/common/views/components/signup.vue +++ b/src/client/app/common/views/components/signup.vue @@ -37,8 +37,13 @@ <p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> {{ $t('password-not-matched') }}</p> </template> </ui-input> + <ui-switch v-model="ToSAgreement" v-if="meta.ToSUrl"> + <i18n path="agree-to"> + <a :href="meta.ToSUrl" target="_blank">{{ $t('tos') }}</a> + </i18n> + </ui-switch> <div v-if="meta.enableRecaptcha" class="g-recaptcha" :data-sitekey="meta.recaptchaSiteKey" style="margin: 16px 0;"></div> - <ui-button type="submit">{{ $t('create') }}</ui-button> + <ui-button type="submit" :disabled="!(meta.ToSUrl ? ToSAgreement : true)">{{ $t('create') }}</ui-button> </template> </form> </template> @@ -64,7 +69,8 @@ export default Vue.extend({ usernameState: null, passwordStrength: '', passwordRetypeState: null, - meta: null + meta: {}, + ToSAgreement: false } }, diff --git a/src/client/app/common/views/components/tag-cloud.vue b/src/client/app/common/views/components/tag-cloud.vue index b5eef964b6..3fa5e3b9d4 100644 --- a/src/client/app/common/views/components/tag-cloud.vue +++ b/src/client/app/common/views/components/tag-cloud.vue @@ -4,7 +4,7 @@ <p class="empty" v-else-if="tags.length == 0"><fa icon="exclamation-circle"/>{{ $t('empty') }}</p> <div v-else> <vue-word-cloud - :words="tags.slice(0, 20).map(x => [x.name, x.count])" + :words="tags.slice(0, 20).map(x => [x.tag, x.count])" :color="color" :spacing="1"> <template slot-scope="{word, text, weight}"> @@ -43,7 +43,7 @@ export default Vue.extend({ }, methods: { fetch() { - this.$root.api('aggregation/hashtags').then(tags => { + this.$root.api('hashtags/trend').then(tags => { this.tags = tags; this.fetching = false; }); diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue index af5f3e4ebc..9ca4497ad8 100644 --- a/src/client/app/common/views/components/url-preview.vue +++ b/src/client/app/common/views/components/url-preview.vue @@ -9,7 +9,7 @@ </blockquote> </div> <div v-else class="mk-url-preview"> - <a :class="{ mini: narrow, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> + <a :class="{ mini: narrow, compact }" :href="url" rel="nofollow noopener" target="_blank" :title="url" v-if="!fetching"> <div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> <button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button> </div> diff --git a/src/client/app/common/views/components/url.vue b/src/client/app/common/views/components/url.vue index 2daf79cba6..faf439814d 100644 --- a/src/client/app/common/views/components/url.vue +++ b/src/client/app/common/views/components/url.vue @@ -1,5 +1,5 @@ <template> -<a class="mk-url" :href="url" :target="target"> +<a class="mk-url" :href="url" :rel="rel" :target="target"> <span class="schema">{{ schema }}//</span> <span class="hostname">{{ hostname }}</span> <span class="port" v-if="port != ''">:{{ port }}</span> @@ -15,7 +15,7 @@ import Vue from 'vue'; import { toUnicode as decodePunycode } from 'punycode'; export default Vue.extend({ - props: ['url', 'target'], + props: ['url', 'rel', 'target'], data() { return { schema: null, diff --git a/src/client/app/common/views/components/user-menu.vue b/src/client/app/common/views/components/user-menu.vue index 0af0fdb7e4..7cbffa9f9a 100644 --- a/src/client/app/common/views/components/user-menu.vue +++ b/src/client/app/common/views/components/user-menu.vue @@ -7,7 +7,6 @@ <script lang="ts"> import Vue from 'vue'; import i18n from '../../../i18n'; -import copyToClipboard from '../../../common/scripts/copy-to-clipboard'; import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; @@ -27,19 +26,23 @@ export default Vue.extend({ icon: ['fas', 'list'], text: this.$t('push-to-list'), action: this.pushList - }, null, { - icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'], - text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), - action: this.toggleMute - }, { - icon: 'ban', - text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'), - action: this.toggleBlock - }, null, { - icon: faExclamationCircle, - text: this.$t('report-abuse'), - action: this.reportAbuse - }]; + }] as any; + + if (this.$store.getters.isSignedIn && this.$store.state.i.id != this.user.id) { + menu = menu.concat([null, { + icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'], + text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), + action: this.toggleMute + }, { + icon: 'ban', + text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'), + action: this.toggleBlock + }, null, { + icon: faExclamationCircle, + text: this.$t('report-abuse'), + action: this.reportAbuse + }]); + } if (this.$store.getters.isSignedIn && (this.$store.state.i.isAdmin || this.$store.state.i.isModerator)) { menu = menu.concat([null, { diff --git a/src/client/app/common/views/deck/deck.note-column.vue b/src/client/app/common/views/deck/deck.note-column.vue index ddc3860d3d..bcc887e2fd 100644 --- a/src/client/app/common/views/deck/deck.note-column.vue +++ b/src/client/app/common/views/deck/deck.note-column.vue @@ -8,7 +8,7 @@ <div class="is-remote" v-if="note.user.host != null"> <details> <summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-post') }}</summary> - <a :href="note.url || note.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> + <a :href="note.url || note.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> </details> </div> <mk-note :note="note" :detail="true" :key="note.id"/> diff --git a/src/client/app/common/views/deck/deck.user-column.vue b/src/client/app/common/views/deck/deck.user-column.vue index 8d1b458a84..c508f3f69f 100644 --- a/src/client/app/common/views/deck/deck.user-column.vue +++ b/src/client/app/common/views/deck/deck.user-column.vue @@ -8,7 +8,7 @@ <div class="is-remote" v-if="user.host != null"> <details> <summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary> - <a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a> + <a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> </details> </div> <header :style="bannerStyle"> diff --git a/src/client/app/common/views/widgets/rss.vue b/src/client/app/common/views/widgets/rss.vue index e979197670..c1a66bfebb 100644 --- a/src/client/app/common/views/widgets/rss.vue +++ b/src/client/app/common/views/widgets/rss.vue @@ -7,7 +7,7 @@ <div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <div class="feed" v-else> - <a v-for="item in items" :href="item.link" target="_blank" :title="item.title">{{ item.title }}</a> + <a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a> </div> </div> </ui-container> diff --git a/src/client/app/desktop/views/components/note-detail.vue b/src/client/app/desktop/views/components/note-detail.vue index bcff8c8774..e0ce5ce1c6 100644 --- a/src/client/app/desktop/views/components/note-detail.vue +++ b/src/client/app/desktop/views/components/note-detail.vue @@ -54,7 +54,7 @@ </div> <mk-poll v-if="appearNote.poll" :note="appearNote"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> - <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> + <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <div class="map" v-if="appearNote.geo" ref="map"></div> <div class="renote" v-if="appearNote.renote"> <mk-note-preview :note="appearNote.renote"/> diff --git a/src/client/app/desktop/views/components/note.vue b/src/client/app/desktop/views/components/note.vue index 585294fc89..1c00faed39 100644 --- a/src/client/app/desktop/views/components/note.vue +++ b/src/client/app/desktop/views/components/note.vue @@ -32,7 +32,7 @@ <mk-media-list :media-list="appearNote.files"/> </div> <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> + <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> </div> diff --git a/src/client/app/desktop/views/home/user/index.vue b/src/client/app/desktop/views/home/user/index.vue index 338cd1c59d..98ad165d93 100644 --- a/src/client/app/desktop/views/home/user/index.vue +++ b/src/client/app/desktop/views/home/user/index.vue @@ -4,7 +4,7 @@ <fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }} </div> <div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> - <fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a> + <fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a> </div> <div class="main"> <x-header class="header" :user="user"/> diff --git a/src/client/app/desktop/views/home/user/user.header.vue b/src/client/app/desktop/views/home/user/user.header.vue index e21757ccf9..52a5165c3f 100644 --- a/src/client/app/desktop/views/home/user/user.header.vue +++ b/src/client/app/desktop/views/home/user/user.header.vue @@ -1,7 +1,7 @@ <template> <div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <div class="banner-container" :style="style"> - <div class="banner" ref="banner" :style="style" @click="onBannerClick"></div> + <div class="banner" ref="banner" :style="style"></div> <div class="fade"></div> <div class="title"> <p class="name"> @@ -105,14 +105,6 @@ export default Vue.extend({ if (blur <= 10) banner.style.filter = `blur(${blur}px)`; }, - onBannerClick() { - if (!this.$store.getters.isSignedIn || this.$store.state.i.id != this.user.id) return; - - this.$updateBanner().then(i => { - this.user.bannerUrl = i.bannerUrl; - }); - }, - menu() { this.$root.new(XUserMenu, { source: this.$refs.menu, @@ -171,9 +163,6 @@ export default Vue.extend({ > .menu height 100% - display block - position absolute - left -42px padding 0 14px color #fff text-shadow 0 0 8px #000 diff --git a/src/client/app/mobile/views/components/media-video.vue b/src/client/app/mobile/views/components/media-video.vue index 1bf282b4ae..044bb4c106 100644 --- a/src/client/app/mobile/views/components/media-video.vue +++ b/src/client/app/mobile/views/components/media-video.vue @@ -7,6 +7,7 @@ </div> <a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else :href="video.url" + rel="nofollow noopener" target="_blank" :style="imageStyle" :title="video.name" diff --git a/src/client/app/mobile/views/components/note-detail.vue b/src/client/app/mobile/views/components/note-detail.vue index 4b343491b8..358b827a5c 100644 --- a/src/client/app/mobile/views/components/note-detail.vue +++ b/src/client/app/mobile/views/components/note-detail.vue @@ -40,7 +40,7 @@ </div> <mk-poll v-if="appearNote.poll" :note="appearNote"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> - <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> + <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <div class="map" v-if="appearNote.geo" ref="map"></div> <div class="renote" v-if="appearNote.renote"> <mk-note-preview :note="appearNote.renote"/> diff --git a/src/client/app/mobile/views/components/note.vue b/src/client/app/mobile/views/components/note.vue index c207eb10d7..01514f05fc 100644 --- a/src/client/app/mobile/views/components/note.vue +++ b/src/client/app/mobile/views/components/note.vue @@ -32,7 +32,7 @@ </div> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/> - <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> + <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" 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> <span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> diff --git a/src/client/app/mobile/views/pages/user/index.vue b/src/client/app/mobile/views/pages/user/index.vue index 7afa06d4a4..f8f4719a68 100644 --- a/src/client/app/mobile/views/pages/user/index.vue +++ b/src/client/app/mobile/views/pages/user/index.vue @@ -5,7 +5,7 @@ </template> <div class="wwtwuxyh" v-if="!fetching"> <div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div> - <div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div> + <div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div> <header> <div class="banner" :style="style"></div> <div class="body"> diff --git a/src/docs/article.pug b/src/docs/article.pug index 38494fec6c..48f501d406 100644 --- a/src/docs/article.pug +++ b/src/docs/article.pug @@ -6,4 +6,4 @@ block main block footer p = i18n('docs.edit-this-page-on-github') - a(href=src target="_blank")= i18n('docs.edit-this-page-on-github-link') + a(href=src rel="noopener" target="_blank")= i18n('docs.edit-this-page-on-github-link') diff --git a/src/models/entities/log.ts b/src/models/entities/log.ts index 99e1e8947e..182a9fbed8 100644 --- a/src/models/entities/log.ts +++ b/src/models/entities/log.ts @@ -35,7 +35,7 @@ export class Log { public machine: string; @Column('varchar', { - length: 1024 + length: 2048 }) public message: string; diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts index 2c36b8333f..c3797a9ed6 100644 --- a/src/models/entities/meta.ts +++ b/src/models/entities/meta.ts @@ -268,4 +268,24 @@ export class Meta { nullable: true }) public discordClientSecret: string | null; + + @Column('varchar', { + length: 512, + nullable: true + }) + public ToSUrl: string | null; + + @Column('varchar', { + length: 512, + default: 'https://github.com/syuilo/misskey', + nullable: false + }) + public repositoryUrl: string; + + @Column('varchar', { + length: 512, + default: 'https://github.com/syuilo/misskey/issues/new', + nullable: true + }) + public feedbackUrl: string | null; } diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index b71c022341..33a6d2bc27 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -43,8 +43,8 @@ export class Note { @JoinColumn() public renote: Note | null; - @Column({ - type: 'text', nullable: true + @Column('varchar', { + length: 8192, nullable: true }) public text: string | null; diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 33eb53612a..330220fb72 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -72,7 +72,10 @@ export class UserRepository extends Repository<User> { const meId = me ? typeof me === 'string' ? me : me.id : null; const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; - const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; + const pins = opts.detail ? await UserNotePinings.find({ + where: { userId: user.id }, + order: { id: 'DESC' } + }) : []; const profile = opts.detail ? await UserProfiles.findOne(user.id).then(ensure) : null; const falsy = opts.detail ? false : undefined; diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index a40677dfc3..654d36403e 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -474,9 +474,15 @@ export async function updateFeatured(userId: User['id']) { .slice(0, 5) .map(item => limit(() => resolveNote(item, resolver)))); + // delete + await UserNotePinings.delete({ userId: user.id }); + + // とりあえずidを別の時間で生成して順番を維持 + let td = 0; for (const note of featuredNotes.filter(note => note != null)) { + td -= 1000; UserNotePinings.save({ - id: genId(), + id: genId(new Date(Date.now() + td)), createdAt: new Date(), userId: user.id, noteId: note!.id diff --git a/src/server/activitypub/featured.ts b/src/server/activitypub/featured.ts index 86ec1000c7..dae72f16b6 100644 --- a/src/server/activitypub/featured.ts +++ b/src/server/activitypub/featured.ts @@ -21,7 +21,10 @@ export default async (ctx: Router.IRouterContext) => { return; } - const pinings = await UserNotePinings.find({ userId: user.id }); + const pinings = await UserNotePinings.find({ + where: { userId: user.id }, + order: { id: 'DESC' } + }); const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId).then(ensure))); diff --git a/src/server/api/endpoints/admin/federation/delete-all-files.ts b/src/server/api/endpoints/admin/federation/delete-all-files.ts new file mode 100644 index 0000000000..befb362264 --- /dev/null +++ b/src/server/api/endpoints/admin/federation/delete-all-files.ts @@ -0,0 +1,27 @@ +import $ from 'cafy'; +import define from '../../../define'; +import del from '../../../../../services/drive/delete-file'; +import { DriveFiles } from '../../../../../models'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + + params: { + host: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, me) => { + const files = await DriveFiles.find({ + userHost: ps.host + }); + + for (const file of files) { + del(file); + } +}); diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 8a2019fcc1..e4f2e86aaa 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -70,6 +70,13 @@ export const meta = { } }, + blockedHosts: { + validator: $.optional.nullable.arr($.str), + desc: { + 'ja-JP': 'ブロックするホスト' + } + }, + mascotImageUrl: { validator: $.optional.nullable.str, desc: { @@ -330,6 +337,27 @@ export const meta = { 'ja-JP': 'ServiceWorkerのVAPIDキーペアの秘密鍵' } }, + + ToSUrl: { + validator: $.optional.nullable.str, + desc: { + 'ja-JP': '利用規約のURL' + } + }, + + repositoryUrl: { + validator: $.optional.str, + desc: { + 'ja-JP': 'リポジトリのURL' + } + }, + + feedbackUrl: { + validator: $.optional.str, + desc: { + 'ja-JP': 'フィードバックのURL' + } + } } }; @@ -368,6 +396,10 @@ export default define(meta, async (ps) => { set.hiddenTags = ps.hiddenTags; } + if (Array.isArray(ps.blockedHosts)) { + set.blockedHosts = ps.blockedHosts; + } + if (ps.mascotImageUrl !== undefined) { set.mascotImageUrl = ps.mascotImageUrl; } @@ -516,6 +548,18 @@ export default define(meta, async (ps) => { set.swPrivateKey = ps.swPrivateKey; } + if (ps.ToSUrl !== undefined) { + set.ToSUrl = ps.ToSUrl; + } + + if (ps.repositoryUrl !== undefined) { + set.repositoryUrl = ps.repositoryUrl; + } + + if (ps.feedbackUrl !== undefined) { + set.feedbackUrl = ps.feedbackUrl; + } + await getConnection().transaction(async transactionalEntityManager => { const meta = await transactionalEntityManager.findOne(Meta, { order: { diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 5667e7fbb4..1bd88a1e6d 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -106,6 +106,9 @@ export default define(meta, async (ps, me) => { uri: config.url, description: instance.description, langs: instance.langs, + ToSUrl: instance.ToSUrl, + repositoryUrl: instance.repositoryUrl, + feedbackUrl: instance.feedbackUrl, secure: config.https != null, machine: os.hostname(), @@ -162,6 +165,7 @@ export default define(meta, async (ps, me) => { response.useStarForReactionFallback = instance.useStarForReactionFallback; response.pinnedUsers = instance.pinnedUsers; response.hiddenTags = instance.hiddenTags; + response.blockedHosts = instance.blockedHosts; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; response.twitterConsumerKey = instance.twitterConsumerKey; diff --git a/src/server/nodeinfo.ts b/src/server/nodeinfo.ts index d3ad90fab5..da1e4e7b5e 100644 --- a/src/server/nodeinfo.ts +++ b/src/server/nodeinfo.ts @@ -26,6 +26,9 @@ const nodeinfo2 = async () => { maintainerName, maintainerEmail, langs, + ToSUrl, + repositoryUrl, + feedbackUrl, announcements, disableRegistration, disableLocalTimeline, @@ -77,6 +80,9 @@ const nodeinfo2 = async () => { email: maintainerEmail }, langs, + ToSUrl, + repositoryUrl, + feedbackUrl, announcements, disableRegistration, disableLocalTimeline, diff --git a/src/server/web/views/note.pug b/src/server/web/views/note.pug index 983c731a04..0580e959f7 100644 --- a/src/server/web/views/note.pug +++ b/src/server/web/views/note.pug @@ -26,6 +26,9 @@ block meta meta(name='twitter:card' content='summary') // todo + if user.host + meta(name='robots' content='noindex') + if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug index bff98ba80f..9b257afb7b 100644 --- a/src/server/web/views/user.pug +++ b/src/server/web/views/user.pug @@ -24,6 +24,9 @@ block meta meta(name='twitter:card' content='summary') + if user.host + meta(name='robots' content='noindex') + if profile.twitter meta(name='twitter:creator' content=`@${profile.twitter.screenName}`) |