summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2020-11-17 14:59:15 +0900
committerGitHub <noreply@github.com>2020-11-17 14:59:15 +0900
commit0044d83801fd261e586143b0442102fabf9106cb (patch)
treedd7c18374e1afaced3669a38c2e20359f6f3949f
parent12.57.4 (diff)
downloadmisskey-0044d83801fd261e586143b0442102fabf9106cb.tar.gz
misskey-0044d83801fd261e586143b0442102fabf9106cb.tar.bz2
misskey-0044d83801fd261e586143b0442102fabf9106cb.zip
nanka iroiro (#6847)
* wip * wip * wip * wip * Update ja-JP.yml * wip * wip * wip
-rw-r--r--locales/ja-JP.yml3
-rw-r--r--migration/1605585339718-instance-pinned-pages.ts14
-rw-r--r--src/client/components/notes.vue2
-rw-r--r--src/client/components/tab.vue30
-rw-r--r--src/client/components/taskmanager.api-window.vue5
-rw-r--r--src/client/components/taskmanager.vue7
-rw-r--r--src/client/pages/channel.vue2
-rw-r--r--src/client/pages/channels.vue40
-rw-r--r--src/client/pages/instance/emojis.vue5
-rw-r--r--src/client/pages/instance/settings.vue54
-rw-r--r--src/client/pages/my-groups/index.vue6
-rw-r--r--src/client/pages/note.vue75
-rw-r--r--src/client/pages/page-editor/page-editor.vue4
-rw-r--r--src/client/pages/page.vue2
-rw-r--r--src/client/pages/pages.vue24
-rw-r--r--src/client/pages/settings/mute-block.vue5
-rw-r--r--src/client/pages/settings/word-mute.vue5
-rw-r--r--src/client/pages/welcome.entrance.block.vue141
-rw-r--r--src/client/pages/welcome.entrance.vue85
-rw-r--r--src/client/pages/welcome.vue14
-rw-r--r--src/client/router.ts6
-rw-r--r--src/client/sidebar.ts3
-rw-r--r--src/client/ui/visitor.vue20
-rw-r--r--src/models/entities/meta.ts5
-rw-r--r--src/models/repositories/page.ts3
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts8
-rw-r--r--src/server/api/endpoints/meta.ts89
-rw-r--r--src/server/api/endpoints/notes/clips.ts54
-rw-r--r--src/server/api/endpoints/pages/featured.ts29
-rw-r--r--src/server/web/index.ts1
30 files changed, 558 insertions, 183 deletions
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index ab540c6a4d..a3f2a82ed3 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -316,6 +316,8 @@ bannerUrl: "バナー画像のURL"
basicInfo: "基本情報"
pinnedUsers: "ピン留めユーザー"
pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。"
+pinnedPages: "ピン留めページ"
+pinnedPagesDescription: "インスタンスのトップページにピン留めしたいページのパスを改行で区切って記述します。"
hcaptcha: "hCaptcha"
enableHcaptcha: "hCaptchaを有効にする"
hcaptchaSiteKey: "サイトキー"
@@ -1117,6 +1119,7 @@ _pages:
unlike: "いいね解除"
my: "自分のページ"
liked: "いいねしたページ"
+ featured: "人気"
inspector: "インスペクター"
contents: "コンテンツ"
content: "ページブロック"
diff --git a/migration/1605585339718-instance-pinned-pages.ts b/migration/1605585339718-instance-pinned-pages.ts
new file mode 100644
index 0000000000..2f0ebab235
--- /dev/null
+++ b/migration/1605585339718-instance-pinned-pages.ts
@@ -0,0 +1,14 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class instancePinnedPages1605585339718 implements MigrationInterface {
+ name = 'instancePinnedPages1605585339718'
+
+ public async up(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`ALTER TABLE "meta" ADD "pinnedPages" character varying(512) array NOT NULL DEFAULT '{"/announcements", "/featured", "/channels", "/pages", "/explore", "/games/reversi", "/about-misskey"}'::varchar[]`);
+ }
+
+ public async down(queryRunner: QueryRunner): Promise<void> {
+ await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "pinnedPages"`);
+ }
+
+}
diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue
index f2ea7e929b..649e7c4cf6 100644
--- a/src/client/components/notes.vue
+++ b/src/client/components/notes.vue
@@ -8,7 +8,7 @@
<MkError v-if="error" @retry="init()"/>
<div v-show="more && reversed" style="margin-bottom: var(--margin);">
- <button class="_loadMore" v-appear="$store.state.device.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
+ <button class="_loadMore" @click="fetchMore" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $t('loadMore') }}</template>
<template v-if="moreFetching"><MkLoading inline/></template>
</button>
diff --git a/src/client/components/tab.vue b/src/client/components/tab.vue
index 7278c74d9a..aca4d32a22 100644
--- a/src/client/components/tab.vue
+++ b/src/client/components/tab.vue
@@ -1,26 +1,32 @@
-<template>
-<div class="pxhvhrfw" v-size="{ max: [500] }">
- <button v-for="item in items" class="_button" @click="$emit('update:value', item.value)" :class="{ active: value === item.value }" :disabled="value === item.value" :key="item.value"><Fa v-if="item.icon" :icon="item.icon" class="icon"/>{{ item.label }}</button>
-</div>
-</template>
-
<script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, h, resolveDirective, withDirectives } from 'vue';
export default defineComponent({
props: {
- items: {
- type: Array,
- required: true,
- },
value: {
required: true,
},
},
+ render() {
+ const options = this.$slots.default();
+
+ return withDirectives(h('div', {
+ class: 'pxhvhrfw',
+ }, options.map(option => h('button', {
+ class: ['_button', { active: this.value === option.props.value }],
+ key: option.props.value,
+ disabled: this.value === option.props.value,
+ onClick: () => {
+ this.$emit('update:value', option.props.value);
+ }
+ }, option.children))), [
+ [resolveDirective('size'), { max: [500] }]
+ ]);
+ }
});
</script>
-<style lang="scss" scoped>
+<style lang="scss">
.pxhvhrfw {
display: flex;
diff --git a/src/client/components/taskmanager.api-window.vue b/src/client/components/taskmanager.api-window.vue
index 8a6f200a29..0df3f75fa2 100644
--- a/src/client/components/taskmanager.api-window.vue
+++ b/src/client/components/taskmanager.api-window.vue
@@ -9,7 +9,10 @@
<template #header>Req Viewer</template>
<div class="rlkneywz">
- <MkTab v-model:value="tab" :items="[{ label: 'Request', value: 'req', }, { label: 'Response', value: 'res', }]" style="border-bottom: solid 1px var(--divider);"/>
+ <MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
+ <option value="req">Request</option>
+ <option value="res">Response</option>
+ </MkTab>
<code v-if="tab === 'req'">{{ reqStr }}</code>
<code v-if="tab === 'res'">{{ resStr }}</code>
diff --git a/src/client/components/taskmanager.vue b/src/client/components/taskmanager.vue
index ab8d4a80dd..92c56442c3 100644
--- a/src/client/components/taskmanager.vue
+++ b/src/client/components/taskmanager.vue
@@ -4,7 +4,12 @@
<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager
</template>
<div class="qljqmnzj">
- <MkTab v-model:value="tab" :items="[{ label: 'Windows', value: 'windows', }, { label: 'Stream', value: 'stream', }, { label: 'Stream (Pool)', value: 'streamPool', }, { label: 'API', value: 'api', }]" style="border-bottom: solid 1px var(--divider);"/>
+ <MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
+ <option value="windows">Windows</option>
+ <option value="stream">Stream</option>
+ <option value="streamPool">Stream (Pool)</option>
+ <option value="api">API</option>
+ </MkTab>
<div class="content">
<div v-if="tab === 'windows'" class="windows" v-follow>
diff --git a/src/client/pages/channel.vue b/src/client/pages/channel.vue
index 33339bbc95..ef41308541 100644
--- a/src/client/pages/channel.vue
+++ b/src/client/pages/channel.vue
@@ -20,7 +20,7 @@
</div>
</div>
- <XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed/>
+ <XPostForm :channel="channel" class="post-form _content _panel _vMargin" fixed v-if="this.$store.getters.isSignedIn"/>
<XTimeline class="_content _vMargin" src="channel" :channel="channelId" @before="before" @after="after"/>
</div>
diff --git a/src/client/pages/channels.vue b/src/client/pages/channels.vue
index a57f974c4d..e428051284 100644
--- a/src/client/pages/channels.vue
+++ b/src/client/pages/channels.vue
@@ -1,26 +1,30 @@
<template>
<div>
- <div class="_section" style="padding: 0;">
- <MkTab class="_content" v-model:value="tab" :items="[{ label: $t('_channel.featured'), value: 'featured', icon: faFireAlt }, { label: $t('_channel.following'), value: 'following', icon: faHeart }, { label: $t('_channel.owned'), value: 'owned', icon: faEdit }]"/>
+ <div class="_section" style="padding: 0;" v-if="this.$store.getters.isSignedIn">
+ <MkTab class="_content" v-model:value="tab">
+ <option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_channel.featured') }}</option>
+ <option value="following"><Fa :icon="faHeart"/> {{ $t('_channel.following') }}</option>
+ <option value="owned"><Fa :icon="faEdit"/> {{ $t('_channel.owned') }}</option>
+ </MkTab>
</div>
<div class="_section">
<div class="_content grwlizim featured" v-if="tab === 'featured'">
<MkPagination :pagination="featuredPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
+ <MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
</MkPagination>
</div>
<div class="_content grwlizim following" v-if="tab === 'following'">
<MkPagination :pagination="followingPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
+ <MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
</MkPagination>
</div>
<div class="_content grwlizim owned" v-if="tab === 'owned'">
<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton>
<MkPagination :pagination="ownedPagination" #default="{items}">
- <MkChannelPreview v-for="channel in items" class="uveselbe" :channel="channel" :key="channel.id"/>
+ <MkChannelPreview v-for="channel in items" class="_vMargin" :channel="channel" :key="channel.id"/>
</MkPagination>
</div>
</div>
@@ -44,7 +48,11 @@ export default defineComponent({
return {
INFO: {
title: this.$t('channel'),
- icon: faSatelliteDish
+ icon: faSatelliteDish,
+ action: {
+ icon: faPlus,
+ handler: this.create
+ }
},
tab: 'featured',
featuredPagination: {
@@ -69,23 +77,3 @@ export default defineComponent({
}
});
</script>
-
-<style lang="scss" scoped>
-.grwlizim {
- padding: 16px 0;
-
- &.my .uveselbe:first-child {
- margin-top: 16px;
- }
-
- .uveselbe:not(:last-child) {
- margin-bottom: 8px;
- }
-
- @media (min-width: 500px) {
- .uveselbe:not(:last-child) {
- margin-bottom: 16px;
- }
- }
-}
-</style>
diff --git a/src/client/pages/instance/emojis.vue b/src/client/pages/instance/emojis.vue
index dcd12edc91..01ea0d7f82 100644
--- a/src/client/pages/instance/emojis.vue
+++ b/src/client/pages/instance/emojis.vue
@@ -1,7 +1,10 @@
<template>
<div class="mk-instance-emojis">
<div class="_section" style="padding: 0;">
- <MkTab v-model:value="tab" :items="[{ label: $t('local'), value: 'local' }, { label: $t('remote'), value: 'remote' }]"/>
+ <MkTab v-model:value="tab">
+ <option value="local">{{ $t('local') }}</option>
+ <option value="remote">{{ $t('remote') }}</option>
+ </MkTab>
</div>
<div class="_section">
diff --git a/src/client/pages/instance/settings.vue b/src/client/pages/instance/settings.vue
index 1015b5f98d..32a6a9595f 100644
--- a/src/client/pages/instance/settings.vue
+++ b/src/client/pages/instance/settings.vue
@@ -1,6 +1,6 @@
<template>
-<div v-if="meta">
- <section class="_section info">
+<div v-if="meta" class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faInfoCircle"/> {{ $t('basicInfo') }}</div>
<div class="_content">
<MkInput v-model:value="name">{{ $t('instanceName') }}</MkInput>
@@ -16,7 +16,7 @@
</div>
</section>
- <section class="_section info">
+ <section class="_card _vMargin">
<div class="_content">
<MkInput v-model:value="maxNoteTextLength" type="number" :save="() => save()"><template #icon><Fa :icon="faPencilAlt"/></template>{{ $t('maxNoteTextLength') }}</MkInput>
</div>
@@ -30,7 +30,7 @@
</div>
</section>
- <section class="_section info">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faUser"/> {{ $t('registration') }}</div>
<div class="_content">
<MkSwitch v-model:value="enableRegistration" @update:value="save()">{{ $t('enableRegistration') }}</MkSwitch>
@@ -38,7 +38,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('hcaptcha') }}</div>
<div class="_content">
<MkSwitch v-model:value="enableHcaptcha">{{ $t('enableHcaptcha') }}</MkSwitch>
@@ -56,7 +56,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faShieldAlt"/> {{ $t('recaptcha') }}</div>
<div class="_content">
<MkSwitch v-model:value="enableRecaptcha" ref="enableRecaptcha">{{ $t('enableRecaptcha') }}</MkSwitch>
@@ -74,7 +74,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faEnvelope" /> {{ $t('emailConfig') }}</div>
<div class="_content">
<MkSwitch v-model:value="enableEmail" @update:value="save()">{{ $t('enableEmail') }}<template #desc>{{ $t('emailConfigInfo') }}</template></MkSwitch>
@@ -97,7 +97,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faBolt"/> {{ $t('serviceworker') }}</div>
<div class="_content">
<MkSwitch v-model:value="enableServiceWorker">{{ $t('enableServiceworker') }}<template #desc>{{ $t('serviceworkerInfo') }}</template></MkSwitch>
@@ -113,7 +113,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedUsers') }}</div>
<div class="_content">
<MkTextarea v-model:value="pinnedUsers">
@@ -125,7 +125,19 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
+ <div class="_title"><Fa :icon="faThumbtack"/> {{ $t('pinnedPages') }}</div>
+ <div class="_content">
+ <MkTextarea v-model:value="pinnedPages">
+ <template #desc>{{ $t('pinnedPagesDescription') }}</template>
+ </MkTextarea>
+ </div>
+ <div class="_footer">
+ <MkButton primary @click="save(true)"><Fa :icon="faSave"/> {{ $t('save') }}</MkButton>
+ </div>
+ </section>
+
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faCloud"/> {{ $t('files') }}</div>
<div class="_content">
<MkSwitch v-model:value="cacheRemoteFiles">{{ $t('cacheRemoteFiles') }}<template #desc>{{ $t('cacheRemoteFilesDescription') }}</template></MkSwitch>
@@ -138,7 +150,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faCloud"/> {{ $t('objectStorage') }}</div>
<div class="_content">
<MkSwitch v-model:value="useObjectStorage">{{ $t('useObjectStorage') }}</MkSwitch>
@@ -166,7 +178,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faGhost"/> {{ $t('proxyAccount') }}</div>
<div class="_content">
<MkInput :value="proxyAccount ? proxyAccount.username : null" disabled><template #prefix>@</template>{{ $t('proxyAccount') }}<template #desc>{{ $t('proxyAccountDescription') }}</template></MkInput>
@@ -174,7 +186,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faBan"/> {{ $t('blockedInstances') }}</div>
<div class="_content">
<MkTextarea v-model:value="blockedHosts">
@@ -186,7 +198,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faShareAlt"/> {{ $t('integration') }}</div>
<div class="_content">
<header><Fa :icon="faTwitter"/> Twitter</header>
@@ -220,7 +232,7 @@
</div>
</section>
- <section class="_section">
+ <section class="_card _vMargin">
<div class="_title"><Fa :icon="faArchway" /> Summaly Proxy</div>
<div class="_content">
<MkInput v-model:value="summalyProxy">URL</MkInput>
@@ -260,6 +272,7 @@ export default defineComponent({
title: this.$t('instance'),
icon: faCog,
},
+ meta: null,
url,
proxyAccount: null,
proxyAccountId: null,
@@ -269,6 +282,7 @@ export default defineComponent({
remoteDriveCapacityMb: 0,
blockedHosts: '',
pinnedUsers: '',
+ pinnedPages: '',
maintainerName: null,
maintainerEmail: null,
name: null,
@@ -323,13 +337,9 @@ export default defineComponent({
}
},
- computed: {
- meta() {
- return this.$store.state.instance.meta;
- },
- },
+ async created() {
+ this.meta = await os.api('meta', { detail: true });
- created() {
this.name = this.meta.name;
this.description = this.meta.description;
this.tosUrl = this.meta.tosUrl;
@@ -356,6 +366,7 @@ export default defineComponent({
this.remoteDriveCapacityMb = this.meta.driveCapacityPerRemoteUserMb;
this.blockedHosts = this.meta.blockedHosts.join('\n');
this.pinnedUsers = this.meta.pinnedUsers.join('\n');
+ this.pinnedPages = this.meta.pinnedPages.join('\n');
this.enableServiceWorker = this.meta.enableServiceWorker;
this.swPublicKey = this.meta.swPublickey;
this.swPrivateKey = this.meta.swPrivateKey;
@@ -506,6 +517,7 @@ export default defineComponent({
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
blockedHosts: this.blockedHosts.split('\n') || [],
pinnedUsers: this.pinnedUsers ? this.pinnedUsers.split('\n') : [],
+ pinnedPages: this.pinnedPages ? this.pinnedPages.split('\n') : [],
enableServiceWorker: this.enableServiceWorker,
swPublicKey: this.swPublicKey,
swPrivateKey: this.swPrivateKey,
diff --git a/src/client/pages/my-groups/index.vue b/src/client/pages/my-groups/index.vue
index d81165b2dc..fb3d9ccb34 100644
--- a/src/client/pages/my-groups/index.vue
+++ b/src/client/pages/my-groups/index.vue
@@ -1,7 +1,11 @@
<template>
<div class="">
<div class="_section" style="padding: 0;">
- <MkTab v-model:value="tab" :items="[{ label: $t('ownedGroups'), value: 'owned' }, { label: $t('joinedGroups'), value: 'joined' }, { label: $t('invites'), icon: faEnvelopeOpenText, value: 'invites' }]"/>
+ <MkTab v-model:value="tab">
+ <option value="owned">{{ $t('ownedGroups') }}</option>
+ <option value="joined">{{ $t('joinedGroups') }}</option>
+ <option value="invites"><Fa :icon="faEnvelopeOpenText"/> {{ $t('invites') }}</option>
+ </MkTab>
</div>
<div class="_section">
diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue
index 7f416c7558..6ad6f2ba1b 100644
--- a/src/client/pages/note.vue
+++ b/src/client/pages/note.vue
@@ -1,21 +1,31 @@
<template>
<div class="fcuexfpr">
<div v-if="note" class="note">
- <div class="_section">
- <XNotes v-if="showNext" class="_content" :pagination="next"/>
- <MkButton v-else-if="hasNext" class="load _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
+ <div class="_section" v-if="showNext">
+ <XNotes class="_content" :pagination="next"/>
</div>
- <div class="_section">
- <div class="_content">
+ <div class="_section main">
+ <MkButton v-if="!showNext && hasNext" class="load next _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
+ <div class="_content _vMargin">
<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/>
<XNote v-model:note="note" :key="note.id" :detail="true" class="_vMargin"/>
</div>
+ <div class="_content clips _vMargin" v-if="clips && clips.length > 0">
+ <div class="title">{{ $t('clip') }}</div>
+ <MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _vMargin">
+ <b>{{ item.name }}</b>
+ <div v-if="item.description" class="description">{{ item.description }}</div>
+ <div class="user">
+ <MkAvatar :user="item.user" class="avatar"/> <MkUserName :user="item.user" :nowrap="false"/>
+ </div>
+ </MkA>
+ </div>
+ <MkButton v-if="!showPrev && hasPrev" class="load prev _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
</div>
- <div class="_section">
- <XNotes v-if="showPrev" class="_content" :pagination="prev"/>
- <MkButton v-else-if="hasPrev" class="load _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
+ <div class="_section" v-if="showPrev">
+ <XNotes class="_content" :pagination="prev"/>
</div>
</div>
@@ -28,7 +38,6 @@
<script lang="ts">
import { computed, defineComponent } from 'vue';
import { faChevronUp, faChevronDown } from '@fortawesome/free-solid-svg-icons';
-import Progress from '@/scripts/loading';
import XNote from '@/components/note.vue';
import XNotes from '@/components/notes.vue';
import MkRemoteCaution from '@/components/remote-caution.vue';
@@ -55,6 +64,7 @@ export default defineComponent({
avatar: this.note.user,
} : null),
note: null,
+ clips: null,
hasPrev: false,
hasNext: false,
showPrev: false,
@@ -88,11 +98,13 @@ export default defineComponent({
},
methods: {
fetch() {
- Progress.start();
os.api('notes/show', {
noteId: this.noteId
}).then(note => {
Promise.all([
+ os.api('notes/clips', {
+ noteId: note.id,
+ }),
os.api('users/notes', {
userId: note.userId,
untilId: note.id,
@@ -103,15 +115,14 @@ export default defineComponent({
sinceId: note.id,
limit: 1,
}),
- ]).then(([prev, next]) => {
+ ]).then(([clips, prev, next]) => {
+ this.clips = clips;
this.hasPrev = prev.length !== 0;
this.hasNext = next.length !== 0;
this.note = note;
});
}).catch(e => {
this.error = e;
- }).finally(() => {
- Progress.done();
});
}
}
@@ -121,10 +132,46 @@ export default defineComponent({
<style lang="scss" scoped>
.fcuexfpr {
> .note {
- > ._section {
+ > .main {
> .load {
min-width: 0;
border-radius: 999px;
+
+ &.next {
+ margin-bottom: var(--margin);
+ }
+
+ &.prev {
+ margin-top: var(--margin);
+ }
+ }
+
+ > .clips {
+ > .title {
+ font-weight: bold;
+ padding: 12px;
+ }
+
+ > .item {
+ display: block;
+ padding: 16px;
+
+ > .description {
+ padding: 8px 0;
+ }
+
+ > .user {
+ $height: 32px;
+ padding-top: 16px;
+ border-top: solid 1px var(--divider);
+ line-height: $height;
+
+ > .avatar {
+ width: $height;
+ height: $height;
+ }
+ }
+ }
}
}
}
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
index 30146b56ed..eab48c766e 100644
--- a/src/client/pages/page-editor/page-editor.vue
+++ b/src/client/pages/page-editor/page-editor.vue
@@ -277,7 +277,7 @@ export default defineComponent({
type: 'success',
text: this.$t('_pages.created')
});
- this.$router.push(`/my/pages/edit/${this.pageId}`);
+ this.$router.push(`/pages/edit/${this.pageId}`);
}).catch(onError);
}
},
@@ -296,7 +296,7 @@ export default defineComponent({
type: 'success',
text: this.$t('_pages.deleted')
});
- this.$router.push(`/my/pages`);
+ this.$router.push(`/pages`);
});
});
},
diff --git a/src/client/pages/page.vue b/src/client/pages/page.vue
index d1df8f796f..43c1688824 100644
--- a/src/client/pages/page.vue
+++ b/src/client/pages/page.vue
@@ -22,7 +22,7 @@
<div class="_content">
<MkA :to="`./${page.name}/view-source`" class="link">{{ $t('_pages.viewSource') }}</MkA>
<template v-if="$store.getters.isSignedIn && $store.state.i.id === page.userId">
- <MkA :to="`/my/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
+ <MkA :to="`/pages/edit/${page.id}`" class="link">{{ $t('_pages.editThisPage') }}</MkA>
<button v-if="$store.state.i.pinnedPageId === page.id" @click="pin(false)" class="link _textButton">{{ $t('unpin') }}</button>
<button v-else @click="pin(true)" class="link _textButton">{{ $t('pin') }}</button>
</template>
diff --git a/src/client/pages/pages.vue b/src/client/pages/pages.vue
index 4e0ff5dd3c..140bbcb873 100644
--- a/src/client/pages/pages.vue
+++ b/src/client/pages/pages.vue
@@ -1,8 +1,18 @@
<template>
<div>
- <MkTab v-model:value="tab" :items="[{ label: $t('_pages.my'), value: 'my', icon: faEdit }, { label: $t('_pages.liked'), value: 'liked', icon: faHeart }]"/>
+ <MkTab v-model:value="tab" v-if="this.$store.getters.isSignedIn">
+ <option value="featured"><Fa :icon="faFireAlt"/> {{ $t('_pages.featured') }}</option>
+ <option value="my"><Fa :icon="faEdit"/> {{ $t('_pages.my') }}</option>
+ <option value="liked"><Fa :icon="faHeart"/> {{ $t('_pages.liked') }}</option>
+ </MkTab>
<div class="_section">
+ <div class="rknalgpo _content" v-if="tab === 'featured'">
+ <MkPagination :pagination="featuredPagesPagination" #default="{items}">
+ <MkPagePreview v-for="page in items" class="ckltabjg" :page="page" :key="page.id"/>
+ </MkPagination>
+ </div>
+
<div class="rknalgpo _content my" v-if="tab === 'my'">
<MkButton class="new" @click="create()"><Fa :icon="faPlus"/></MkButton>
<MkPagination :pagination="myPagesPagination" #default="{items}">
@@ -21,7 +31,7 @@
<script lang="ts">
import { defineComponent } from 'vue';
-import { faPlus, faEdit } from '@fortawesome/free-solid-svg-icons';
+import { faPlus, faEdit, faFireAlt } from '@fortawesome/free-solid-svg-icons';
import { faStickyNote, faHeart } from '@fortawesome/free-regular-svg-icons';
import MkPagePreview from '@/components/page-preview.vue';
import MkPagination from '@/components/ui/pagination.vue';
@@ -42,7 +52,11 @@ export default defineComponent({
handler: this.create
}
},
- tab: 'my',
+ tab: 'featured',
+ featuredPagesPagination: {
+ endpoint: 'pages/featured',
+ noPaging: true,
+ },
myPagesPagination: {
endpoint: 'i/pages',
limit: 5,
@@ -51,12 +65,12 @@ export default defineComponent({
endpoint: 'i/page-likes',
limit: 5,
},
- faStickyNote, faPlus, faEdit, faHeart
+ faStickyNote, faPlus, faEdit, faHeart, faFireAlt
};
},
methods: {
create() {
- this.$router.push(`/my/pages/new`);
+ this.$router.push(`/pages/new`);
}
}
});
diff --git a/src/client/pages/settings/mute-block.vue b/src/client/pages/settings/mute-block.vue
index 2143d108b5..43e2c396b9 100644
--- a/src/client/pages/settings/mute-block.vue
+++ b/src/client/pages/settings/mute-block.vue
@@ -1,6 +1,9 @@
<template>
<section class="rrfwjxfl _section">
- <MkTab v-model:value="tab" :items="[{ label: $t('mutedUsers'), value: 'mute' }, { label: $t('blockedUsers'), value: 'block' }]" style="margin-bottom: var(--margin);"/>
+ <MkTab v-model:value="tab" style="margin-bottom: var(--margin);">
+ <option value="mute">{{ $t('mutedUsers') }}</option>
+ <option value="block">{{ $t('blockedUsers') }}</option>
+ </MkTab>
<div class="_content" v-if="tab === 'mute'">
<MkPagination :pagination="mutingPagination" class="muting">
<template #empty><MkInfo>{{ $t('noUsers') }}</MkInfo></template>
diff --git a/src/client/pages/settings/word-mute.vue b/src/client/pages/settings/word-mute.vue
index aeae031830..444b2e598c 100644
--- a/src/client/pages/settings/word-mute.vue
+++ b/src/client/pages/settings/word-mute.vue
@@ -1,7 +1,10 @@
<template>
<div class="_section">
<div class="_card">
- <MkTab v-model:value="tab" :items="[{ label: $t('_wordMute.soft'), value: 'soft' }, { label: $t('_wordMute.hard'), value: 'hard' }]"/>
+ <MkTab v-model:value="tab">
+ <option value="soft">{{ $t('_wordMute.soft') }}</option>
+ <option value="hard">{{ $t('_wordMute.hard') }}</option>
+ </MkTab>
<div class="_content">
<div v-show="tab === 'soft'">
<MkInfo>{{ $t('_wordMute.softDescription') }}</MkInfo>
diff --git a/src/client/pages/welcome.entrance.block.vue b/src/client/pages/welcome.entrance.block.vue
new file mode 100644
index 0000000000..0e4aefa4b0
--- /dev/null
+++ b/src/client/pages/welcome.entrance.block.vue
@@ -0,0 +1,141 @@
+<template>
+<div class="xyeqzsjl _panel">
+ <header>
+ <button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
+ <XHeader class="title" :info="pageInfo" :with-back="false"/>
+ </header>
+ <div>
+ <component :is="component" v-bind="props" :ref="changePage"/>
+ </div>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
+import XWindow from '@/components/ui/window.vue';
+import XHeader from '@/ui/_common_/header.vue';
+import { popout } from '@/scripts/popout';
+import { resolve } from '@/router';
+import { url } from '@/config';
+
+export default defineComponent({
+ components: {
+ XWindow,
+ XHeader,
+ },
+
+ provide() {
+ return {
+ navHook: (path) => {
+ this.navigate(path);
+ }
+ };
+ },
+
+ props: {
+ initialPath: {
+ type: String,
+ required: true,
+ },
+ },
+
+ data() {
+ return {
+ pageInfo: null,
+ path: this.initialPath,
+ component: null,
+ props: null,
+ history: [],
+ faChevronLeft,
+ };
+ },
+
+ computed: {
+ url(): string {
+ return url + this.path;
+ },
+ },
+
+ created() {
+ const { component, props } = resolve(this.initialPath);
+ this.component = component;
+ this.props = props;
+ },
+
+ methods: {
+ changePage(page) {
+ if (page == null) return;
+ if (page.INFO) {
+ this.pageInfo = page.INFO;
+ }
+ },
+
+ navigate(path, record = true) {
+ if (record) this.history.push(this.path);
+ this.path = path;
+ const { component, props } = resolve(path);
+ this.component = component;
+ this.props = props;
+ },
+
+ back() {
+ this.navigate(this.history.pop(), false);
+ },
+
+ expand() {
+ this.$router.push(this.path);
+ this.$refs.window.close();
+ },
+
+ popout() {
+ popout(this.path, this.$el);
+ this.$refs.window.close();
+ },
+ },
+});
+</script>
+
+<style lang="scss" scoped>
+.xyeqzsjl {
+ --section-padding: 16px;
+
+ display: flex;
+ flex-direction: column;
+ contain: content;
+
+ > header {
+ $height: 50px;
+ display: flex;
+ position: relative;
+ z-index: 1;
+ height: $height;
+ line-height: $height;
+ box-shadow: 0px 1px var(--divider);
+
+ > button {
+ height: $height;
+ width: $height;
+
+ &:hover {
+ color: var(--fgHighlighted);
+ }
+ }
+
+ > .title {
+ flex: 1;
+ position: relative;
+ line-height: $height;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+ }
+ }
+
+ > div {
+ flex: 1;
+ overflow: auto;
+ }
+}
+</style>
diff --git a/src/client/pages/welcome.entrance.vue b/src/client/pages/welcome.entrance.vue
index ff946f7452..b1cd6d50c6 100644
--- a/src/client/pages/welcome.entrance.vue
+++ b/src/client/pages/welcome.entrance.vue
@@ -1,18 +1,13 @@
<template>
-<div class="rsqzvsbo">
- <div class="_section">
- <div class="_content _panel about" v-if="meta">
- <div class="body">
- <div class="desc" v-html="meta.description || $t('introMisskey')"></div>
- <MkButton @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton>
- <MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton>
- </div>
- </div>
+<div class="rsqzvsbo _section" v-if="meta">
+ <div class="about">
+ <h1>{{ instanceName }}</h1>
+ <div class="desc" v-html="meta.description || $t('introMisskey')"></div>
+ <MkButton @click="signup()" style="display: inline-block; margin-right: 16px;" primary>{{ $t('signup') }}</MkButton>
+ <MkButton @click="signin()" style="display: inline-block;">{{ $t('login') }}</MkButton>
</div>
- <div class="_section">
- <div class="_content">
- <XNotes :pagination="featuredPagination"/>
- </div>
+ <div class="blocks">
+ <XBlock class="block" v-for="path in meta.pinnedPages" :initial-path="path" :key="path"/>
</div>
</div>
</template>
@@ -24,33 +19,30 @@ import XSigninDialog from '@/components/signin-dialog.vue';
import XSignupDialog from '@/components/signup-dialog.vue';
import MkButton from '@/components/ui/button.vue';
import XNotes from '@/components/notes.vue';
-import { host } from '@/config';
+import XBlock from './welcome.entrance.block.vue';
+import { host, instanceName } from '@/config';
import * as os from '@/os';
export default defineComponent({
components: {
MkButton,
XNotes,
+ XBlock,
},
data() {
return {
- featuredPagination: {
- endpoint: 'notes/featured',
- limit: 10,
- noPaging: true,
- },
host: toUnicode(host),
+ instanceName,
+ meta: null,
};
},
- computed: {
- meta() {
- return this.$store.state.instance.meta;
- },
- },
-
created() {
+ os.api('meta', { detail: true }).then(meta => {
+ this.meta = meta;
+ });
+
os.api('stats').then(stats => {
this.stats = stats;
});
@@ -74,15 +66,42 @@ export default defineComponent({
<style lang="scss" scoped>
.rsqzvsbo {
- > ._section {
- > .about {
- > .body {
- padding: 32px;
+ text-align: center;
+
+ > .about {
+ display: inline-block;
+ padding: 24px;
+ margin-bottom: var(--margin);
+ -webkit-backdrop-filter: blur(8px);
+ backdrop-filter: blur(8px);
+ background: rgba(0, 0, 0, 0.5);
+ border-radius: var(--radius);
+ text-align: center;
+ box-sizing: border-box;
+ min-width: 300px;
+ max-width: 800px;
+
+ &, * {
+ color: #fff !important;
+ }
+
+ > h1 {
+ margin: 0 0 16px 0;
+ }
+ }
+
+ > .blocks {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
+ grid-gap: var(--margin);
+ text-align: left;
+
+ > .block {
+ height: 600px;
+ }
- @media (max-width: 500px) {
- padding: 16px;
- }
- }
+ @media (max-width: 800px) {
+ grid-template-columns: 1fr;
}
}
}
diff --git a/src/client/pages/welcome.vue b/src/client/pages/welcome.vue
index 32ac43eb9d..cc57629c8a 100644
--- a/src/client/pages/welcome.vue
+++ b/src/client/pages/welcome.vue
@@ -10,6 +10,7 @@ import { defineComponent } from 'vue';
import XSetup from './welcome.setup.vue';
import XEntrance from './welcome.entrance.vue';
import { instanceName } from '@/config';
+import * as os from '@/os';
export default defineComponent({
components: {
@@ -20,16 +21,17 @@ export default defineComponent({
data() {
return {
INFO: {
- title: instanceName || 'Misskey',
+ title: instanceName,
icon: null
},
+ meta: null
}
},
- computed: {
- meta() {
- return this.$store.state.instance.meta;
- },
- },
+ created() {
+ os.api('meta', { detail: true }).then(meta => {
+ this.meta = meta;
+ });
+ }
});
</script>
diff --git a/src/client/router.ts b/src/client/router.ts
index 413e72c320..5ad3345d55 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -33,6 +33,9 @@ export const router = createRouter({
{ path: '/explore', component: page('explore') },
{ path: '/explore/tags/:tag', props: true, component: page('explore') },
{ path: '/search', component: page('search') },
+ { path: '/pages', name: 'pages', component: page('pages') },
+ { path: '/pages/new', component: page('page-editor/page-editor') },
+ { path: '/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/channels', component: page('channels') },
{ path: '/channels/new', component: page('channel-editor') },
{ path: '/channels/:channelId/edit', component: page('channel-editor'), props: true },
@@ -47,9 +50,6 @@ export const router = createRouter({
{ path: '/my/messaging/group/:group', component: page('messaging/messaging-room'), props: route => ({ groupId: route.params.group }) },
{ path: '/my/drive', name: 'drive', component: page('drive') },
{ path: '/my/drive/folder/:folder', component: page('drive') },
- { path: '/my/pages', name: 'pages', component: page('pages') },
- { path: '/my/pages/new', component: page('page-editor/page-editor') },
- { path: '/my/pages/edit/:pageId', component: page('page-editor/page-editor'), props: route => ({ initPageId: route.params.pageId }) },
{ path: '/my/follow-requests', component: page('follow-requests') },
{ path: '/my/lists', component: page('my-lists/index') },
{ path: '/my/lists/:list', component: page('my-lists/list') },
diff --git a/src/client/sidebar.ts b/src/client/sidebar.ts
index a541670df1..a3a32d7875 100644
--- a/src/client/sidebar.ts
+++ b/src/client/sidebar.ts
@@ -96,8 +96,7 @@ export const sidebarDef = {
pages: {
title: 'pages',
icon: faFileAlt,
- show: computed(() => store.getters.isSignedIn),
- to: '/my/pages',
+ to: '/pages',
},
clips: {
title: 'clip',
diff --git a/src/client/ui/visitor.vue b/src/client/ui/visitor.vue
index 8a3c19b631..56cc270be7 100644
--- a/src/client/ui/visitor.vue
+++ b/src/client/ui/visitor.vue
@@ -7,12 +7,12 @@
<MkA class="link" to="/about">{{ $t('aboutX', { x: instanceName }) }}</MkA>
</header>
- <div class="banner" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }">
- <h1>{{ instanceName }}</h1>
+ <div class="banner" :class="{ asBg: $route.path === '/' }" :style="{ backgroundImage: `url(${ $store.state.instance.meta.bannerUrl })` }">
+ <h1 v-if="$route.path !== '/'">{{ instanceName }}</h1>
</div>
<div class="contents" ref="contents" :class="{ wallpaper }">
- <header class="header" ref="header">
+ <header class="header" ref="header" v-show="$route.path !== '/'">
<XHeader :info="pageInfo"/>
</header>
<main ref="main">
@@ -116,11 +116,10 @@ export default defineComponent({
<style lang="scss" scoped>
.mk-app {
min-height: 100vh;
- max-width: 1300px;
- margin: 0 auto;
- box-shadow: 1px 0 var(--divider), -1px 0 var(--divider);
> header {
+ position: relative;
+ z-index: 1;
background: var(--panel);
padding: 0 16px;
text-align: center;
@@ -145,6 +144,12 @@ export default defineComponent({
background-size: cover;
background-position: center;
+ &.asBg {
+ position: absolute;
+ left: 0;
+ height: 320px;
+ }
+
&:after {
content: "";
display: block;
@@ -166,6 +171,9 @@ export default defineComponent({
}
> .contents {
+ position: relative;
+ z-index: 1;
+
> .header {
position: sticky;
top: 0;
diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts
index d1eecf6277..b7fe8b18ad 100644
--- a/src/models/entities/meta.ts
+++ b/src/models/entities/meta.ts
@@ -77,6 +77,11 @@ export class Meta {
public blockedHosts: string[];
@Column('varchar', {
+ length: 512, array: true, default: '{"/announcements", "/featured", "/channels", "/explore", "/games/reversi", "/about-misskey"}'
+ })
+ public pinnedPages: string[];
+
+ @Column('varchar', {
length: 512,
nullable: true,
default: '/assets/ai.png'
diff --git a/src/models/repositories/page.ts b/src/models/repositories/page.ts
index 662c41905f..3889bf59a7 100644
--- a/src/models/repositories/page.ts
+++ b/src/models/repositories/page.ts
@@ -85,8 +85,9 @@ export class PageRepository extends Repository<Page> {
public packMany(
pages: Page[],
+ me?: User['id'] | User | null | undefined,
) {
- return Promise.all(pages.map(x => this.pack(x)));
+ return Promise.all(pages.map(x => this.pack(x, me)));
}
}
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index fea6cb539f..ae6d2a4163 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -208,6 +208,10 @@ export const meta = {
}
},
+ pinnedPages: {
+ validator: $.optional.arr($.str),
+ },
+
langs: {
validator: $.optional.arr($.str),
desc: {
@@ -537,6 +541,10 @@ export default define(meta, async (ps, me) => {
set.langs = ps.langs.filter(Boolean);
}
+ if (Array.isArray(ps.pinnedPages)) {
+ set.pinnedPages = ps.pinnedPages.filter(Boolean);
+ }
+
if (ps.summalyProxy !== undefined) {
set.summalyProxy = ps.summalyProxy;
}
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index f46139aa23..97376a9d73 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -99,8 +99,6 @@ export default define(meta, async (ps, me) => {
}
});
- const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null;
-
const response: any = {
maintainerName: instance.maintainerName,
maintainerEmail: instance.maintainerEmail,
@@ -122,8 +120,6 @@ export default define(meta, async (ps, me) => {
disableGlobalTimeline: instance.disableGlobalTimeline,
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
- cacheRemoteFiles: instance.cacheRemoteFiles,
- proxyRemoteFiles: instance.proxyRemoteFiles,
enableHcaptcha: instance.enableHcaptcha,
hcaptchaSiteKey: instance.hcaptchaSiteKey,
enableRecaptcha: instance.enableRecaptcha,
@@ -135,9 +131,6 @@ export default define(meta, async (ps, me) => {
iconUrl: instance.iconUrl,
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
emojis: await Emojis.packMany(emojis),
- requireSetup: (await Users.count({
- host: null,
- })) === 0,
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
@@ -146,10 +139,20 @@ export default define(meta, async (ps, me) => {
enableServiceWorker: instance.enableServiceWorker,
- proxyAccountName: proxyAccount ? proxyAccount.username : null,
+ ...(ps.detail ? {
+ pinnedPages: instance.pinnedPages,
+ cacheRemoteFiles: instance.cacheRemoteFiles,
+ proxyRemoteFiles: instance.proxyRemoteFiles,
+ requireSetup: (await Users.count({
+ host: null,
+ })) === 0,
+ } : {})
};
if (ps.detail) {
+ const proxyAccount = instance.proxyAccountId ? await Users.pack(instance.proxyAccountId).catch(() => null) : null;
+
+ response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
response.features = {
registration: !instance.disableRegistration,
localTimeLine: !instance.disableLocalTimeline,
@@ -164,42 +167,42 @@ export default define(meta, async (ps, me) => {
serviceWorker: instance.enableServiceWorker,
miauth: true,
};
- }
- if (me && me.isAdmin) {
- response.useStarForReactionFallback = instance.useStarForReactionFallback;
- response.pinnedUsers = instance.pinnedUsers;
- response.hiddenTags = instance.hiddenTags;
- response.blockedHosts = instance.blockedHosts;
- response.hcaptchaSecretKey = instance.hcaptchaSecretKey;
- response.recaptchaSecretKey = instance.recaptchaSecretKey;
- response.proxyAccountId = instance.proxyAccountId;
- response.twitterConsumerKey = instance.twitterConsumerKey;
- response.twitterConsumerSecret = instance.twitterConsumerSecret;
- response.githubClientId = instance.githubClientId;
- response.githubClientSecret = instance.githubClientSecret;
- response.discordClientId = instance.discordClientId;
- response.discordClientSecret = instance.discordClientSecret;
- response.summalyProxy = instance.summalyProxy;
- response.email = instance.email;
- response.smtpSecure = instance.smtpSecure;
- response.smtpHost = instance.smtpHost;
- response.smtpPort = instance.smtpPort;
- response.smtpUser = instance.smtpUser;
- response.smtpPass = instance.smtpPass;
- response.swPrivateKey = instance.swPrivateKey;
- response.useObjectStorage = instance.useObjectStorage;
- response.objectStorageBaseUrl = instance.objectStorageBaseUrl;
- response.objectStorageBucket = instance.objectStorageBucket;
- response.objectStoragePrefix = instance.objectStoragePrefix;
- response.objectStorageEndpoint = instance.objectStorageEndpoint;
- response.objectStorageRegion = instance.objectStorageRegion;
- response.objectStoragePort = instance.objectStoragePort;
- response.objectStorageAccessKey = instance.objectStorageAccessKey;
- response.objectStorageSecretKey = instance.objectStorageSecretKey;
- response.objectStorageUseSSL = instance.objectStorageUseSSL;
- response.objectStorageUseProxy = instance.objectStorageUseProxy;
- response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead;
+ if (me && me.isAdmin) {
+ response.useStarForReactionFallback = instance.useStarForReactionFallback;
+ response.pinnedUsers = instance.pinnedUsers;
+ response.hiddenTags = instance.hiddenTags;
+ response.blockedHosts = instance.blockedHosts;
+ response.hcaptchaSecretKey = instance.hcaptchaSecretKey;
+ response.recaptchaSecretKey = instance.recaptchaSecretKey;
+ response.proxyAccountId = instance.proxyAccountId;
+ response.twitterConsumerKey = instance.twitterConsumerKey;
+ response.twitterConsumerSecret = instance.twitterConsumerSecret;
+ response.githubClientId = instance.githubClientId;
+ response.githubClientSecret = instance.githubClientSecret;
+ response.discordClientId = instance.discordClientId;
+ response.discordClientSecret = instance.discordClientSecret;
+ response.summalyProxy = instance.summalyProxy;
+ response.email = instance.email;
+ response.smtpSecure = instance.smtpSecure;
+ response.smtpHost = instance.smtpHost;
+ response.smtpPort = instance.smtpPort;
+ response.smtpUser = instance.smtpUser;
+ response.smtpPass = instance.smtpPass;
+ response.swPrivateKey = instance.swPrivateKey;
+ response.useObjectStorage = instance.useObjectStorage;
+ response.objectStorageBaseUrl = instance.objectStorageBaseUrl;
+ response.objectStorageBucket = instance.objectStorageBucket;
+ response.objectStoragePrefix = instance.objectStoragePrefix;
+ response.objectStorageEndpoint = instance.objectStorageEndpoint;
+ response.objectStorageRegion = instance.objectStorageRegion;
+ response.objectStoragePort = instance.objectStoragePort;
+ response.objectStorageAccessKey = instance.objectStorageAccessKey;
+ response.objectStorageSecretKey = instance.objectStorageSecretKey;
+ response.objectStorageUseSSL = instance.objectStorageUseSSL;
+ response.objectStorageUseProxy = instance.objectStorageUseProxy;
+ response.objectStorageSetPublicRead = instance.objectStorageSetPublicRead;
+ }
}
return response;
diff --git a/src/server/api/endpoints/notes/clips.ts b/src/server/api/endpoints/notes/clips.ts
new file mode 100644
index 0000000000..6126f12c66
--- /dev/null
+++ b/src/server/api/endpoints/notes/clips.ts
@@ -0,0 +1,54 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ClipNotes, Clips } from '../../../../models';
+import { getNote } from '../../common/getters';
+import { ApiError } from '../../error';
+import { In } from 'typeorm';
+
+export const meta = {
+ tags: ['clips', 'notes'],
+
+ requireCredential: false as const,
+
+ params: {
+ noteId: {
+ validator: $.type(ID),
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'Note',
+ }
+ },
+
+ errors: {
+ noSuchNote: {
+ message: 'No such note.',
+ code: 'NO_SUCH_NOTE',
+ id: '47db1a1c-b0af-458d-8fb4-986e4efafe1e'
+ }
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ const note = await getNote(ps.noteId).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw e;
+ });
+
+ const clipNotes = await ClipNotes.find({
+ noteId: note.id,
+ });
+
+ const clips = await Clips.find({
+ id: In(clipNotes.map(x => x.clipId)),
+ });
+
+ return await Promise.all(clips.map(x => Clips.pack(x)));
+});
diff --git a/src/server/api/endpoints/pages/featured.ts b/src/server/api/endpoints/pages/featured.ts
new file mode 100644
index 0000000000..19802d0448
--- /dev/null
+++ b/src/server/api/endpoints/pages/featured.ts
@@ -0,0 +1,29 @@
+import define from '../../define';
+import { Pages } from '../../../../models';
+
+export const meta = {
+ tags: ['pages'],
+
+ requireCredential: false as const,
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'Page',
+ }
+ },
+};
+
+export default define(meta, async (ps, me) => {
+ const query = Pages.createQueryBuilder('page')
+ .where('page.visibility = \'public\'')
+ .andWhere('page.likedCount > 0')
+ .orderBy('page.likedCount', 'DESC');
+
+ const pages = await query.take(10).getMany();
+
+ return await Pages.packMany(pages, me);
+});
diff --git a/src/server/web/index.ts b/src/server/web/index.ts
index f889374139..0bc9f242ad 100644
--- a/src/server/web/index.ts
+++ b/src/server/web/index.ts
@@ -299,6 +299,7 @@ router.get('/@:user/pages/:page', async ctx => {
});
// Clip
+// TODO: 非publicなclipのハンドリング
router.get('/clips/:clip', async ctx => {
const clip = await Clips.findOne({
id: ctx.params.clip,