diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue index 5230ac371a..b1c6782e93 100644 --- a/src/client/app/common/views/components/signin.vue +++ b/src/client/app/common/views/components/signin.vue @@ -78,7 +78,7 @@ export default Vue.extend({ cursor wait !important > .avatar - margin 16px auto 0 auto + margin 0 auto 0 auto width 64px height 64px background #ddd diff --git a/src/client/app/common/views/widgets/broadcast.vue b/src/client/app/common/views/widgets/broadcast.vue index 69b2a54fe9..d3a39bd9cc 100644 --- a/src/client/app/common/views/widgets/broadcast.vue +++ b/src/client/app/common/views/widgets/broadcast.vue @@ -42,15 +42,7 @@ export default define({ }, mounted() { (this as any).os.getMeta().then(meta => { - let broadcasts = []; - if (meta.broadcasts) { - meta.broadcasts.forEach(broadcast => { - if (broadcast[lang]) { - broadcasts.push(broadcast[lang]); - } - }); - } - this.broadcasts = broadcasts; + this.broadcasts = meta.broadcasts; this.fetching = false; }); }, diff --git a/src/client/app/desktop/views/pages/admin/admin.announcements.vue b/src/client/app/desktop/views/pages/admin/admin.announcements.vue new file mode 100644 index 0000000000..532400deb2 --- /dev/null +++ b/src/client/app/desktop/views/pages/admin/admin.announcements.vue @@ -0,0 +1,41 @@ +<template> +<div class="qldxjjsrseehkusjuoooapmsprvfrxyl mk-admin-card"> + <header>%i18n:@announcements%</header> + <textarea v-model="broadcasts"></textarea> + <button class="ui" @click="save">%i18n:@save%</button> +</div> +</template> + +<script lang="ts"> +import Vue from "vue"; + +export default Vue.extend({ + data() { + return { + broadcasts: '', + }; + }, + created() { + (this as any).os.getMeta().then(meta => { + this.broadcasts = JSON.stringify(meta.broadcasts, null, ' '); + }); + }, + methods: { + save() { + (this as any).api('admin/update-meta', { + broadcasts: JSON.parse(this.broadcasts) + }); + } + } +}); +</script> + +<style lang="stylus" scoped> +@import '~const.styl' + +.qldxjjsrseehkusjuoooapmsprvfrxyl + textarea + width 100% + min-height 300px + +</style> diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue index 3438462cd6..a71059c378 100644 --- a/src/client/app/desktop/views/pages/admin/admin.vue +++ b/src/client/app/desktop/views/pages/admin/admin.vue @@ -4,6 +4,7 @@ <ul> <li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li> <li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li> + <li @click="nav('announcements')" :class="{ active: page == 'announcements' }">%fa:broadcast-tower .fw%%i18n:@announcements%</li> <!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> --> <!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> --> </ul> @@ -13,6 +14,9 @@ <x-dashboard/> <x-charts/> </div> + <div v-show="page == 'announcements'"> + <x-announcements/> + </div> <div v-if="page == 'users'"> <x-suspend-user/> <x-unsuspend-user/> @@ -28,6 +32,7 @@ <script lang="ts"> import Vue from "vue"; import XDashboard from "./admin.dashboard.vue"; +import XAnnouncements from "./admin.announcements.vue"; import XSuspendUser from "./admin.suspend-user.vue"; import XUnsuspendUser from "./admin.unsuspend-user.vue"; import XVerifyUser from "./admin.verify-user.vue"; @@ -37,6 +42,7 @@ import XCharts from "../../components/charts.vue"; export default Vue.extend({ components: { XDashboard, + XAnnouncements, XSuspendUser, XUnsuspendUser, XVerifyUser, diff --git a/src/client/app/desktop/views/pages/welcome.vue b/src/client/app/desktop/views/pages/welcome.vue index ae9bf7e678..e67ef16136 100644 --- a/src/client/app/desktop/views/pages/welcome.vue +++ b/src/client/app/desktop/views/pages/welcome.vue @@ -1,46 +1,60 @@ <template> <div class="mk-welcome"> - <img ref="pointer" class="pointer" src="/assets/pointer.png" alt=""> <button @click="dark"> <template v-if="$store.state.device.darkmode">%fa:moon%</template> <template v-else>%fa:R moon%</template> </button> + + <mk-forkit class="forkit"/> + <div class="body"> - <div class="container"> + <div class="main"> + <h1 v-if="name != 'Misskey'">{{ name }}</h1> + <h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1> + <div class="info"> - <span><b>{{ host }}</b></span> + <span><b>{{ host }}</b> - <span v-html="'%i18n:@powered-by-misskey%'"></span></span> <span class="stats" v-if="stats"> <span>%fa:user% {{ stats.originalUsersCount | number }}</span> <span>%fa:pencil-alt% {{ stats.originalNotesCount | number }}</span> </span> </div> - <main> - <div class="about"> - <h1 v-if="name != 'Misskey'">{{ name }}</h1> - <h1 v-else><img :src="$store.state.device.darkmode ? 'assets/title.dark.svg' : 'assets/title.light.svg'" :alt="name"></h1> - <p class="powerd-by" v-if="name != 'Misskey'" v-html="'%i18n:@powered-by-misskey%'"></p> - <p class="desc" v-html="description || '%i18n:common.about%'"></p> - <a ref="signup" @click="signup">π¦ %i18n:@signup%</a> - </div> - <div class="login"> - <mk-signin/> - </div> - </main> + + <p class="desc" v-html="description || '%i18n:common.about%'"></p> + + <p class="sign"> + <span class="signup" @click="signup">%i18n:@signup%</span> + <span class="divider">|</span> + <span class="signin" @click="signin">%i18n:@signin%</span> + </p> + <div class="hashtags"> <router-link v-for="tag in tags" :key="tag" :to="`/tags/${ tag }`" :title="tag">#{{ tag }}</router-link> </div> + </div> + + <div class="broadcasts"> + <div v-for="broadcast in broadcasts"> + <h1 v-html="broadcast.title"></h1> + <div v-html="broadcast.text"></div> + </div> + </div> + + <div class="nav"> <mk-nav class="nav"/> </div> - <mk-forkit class="forkit"/> - <img src="assets/title.dark.svg" :alt="name"> - </div> - <div class="tl"> - <mk-welcome-timeline :max="20"/> + + <mk-welcome-timeline class="tl" :max="20"/> </div> - <modal name="signup" width="500px" height="auto" scrollable> - <header :class="$style.signupFormHeader">%i18n:@signup%</header> - <mk-signup :class="$style.signupForm"/> + <modal name="signup" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable> + <header class="formHeader">%i18n:@signup%</header> + <mk-signup class="form"/> + </modal> + + <modal name="signin" :class="$store.state.device.darkmode ? 'modal-dark' : 'modal-light'" width="450px" height="auto" scrollable> + <header class="formHeader">%i18n:@signin%</header> + <mk-signin class="form"/> </modal> </div> </template> @@ -57,7 +71,7 @@ export default Vue.extend({ host, name: 'Misskey', description: '', - pointerInterval: null, + broadcasts: [], tags: [] }; }, @@ -65,6 +79,7 @@ export default Vue.extend({ (this as any).os.getMeta().then(meta => { this.name = meta.name; this.description = meta.description; + this.broadcasts = meta.broadcasts; }); (this as any).api('stats').then(stats => { @@ -75,19 +90,7 @@ export default Vue.extend({ this.tags = stats.map(x => x.tag); }); }, - mounted() { - this.point(); - this.pointerInterval = setInterval(this.point, 100); - }, - beforeDestroy() { - clearInterval(this.pointerInterval); - }, methods: { - point() { - const x = this.$refs.signup.getBoundingClientRect(); - this.$refs.pointer.style.top = x.top + x.height + 'px'; - this.$refs.pointer.style.left = x.left + 'px'; - }, signup() { this.$modal.show('signup'); }, @@ -104,11 +107,40 @@ export default Vue.extend({ }); </script> -<style> -#wait { - right: auto; - left: 15px; -} +<style lang="stylus"> +#wait + right auto + left 15px + +.v--modal-overlay + background rgba(0, 0, 0, 0.4) + +.modal-light + .v--modal-box + color #777 + + .formHeader + border-bottom solid 1px #eee + +.modal-dark + .v--modal-box + background #313543 + color #fff + + .formHeader + border-bottom solid 1px rgba(#000, 0.2) + +.modal-light +.modal-dark + .form + padding 24px 48px 48px 48px + + .formHeader + text-align center + padding 48px 0 12px 0 + margin 0 48px + font-size 1.5em + </style> <style lang="stylus" scoped> @@ -117,122 +149,85 @@ export default Vue.extend({ root(isDark) display flex min-height 100vh + //background-color #00070F + //background-image url('/assets/bg.jpg') + //background-position center + //background-size cover - > .pointer - display block + > .forkit position absolute - z-index 1 top 0 right 0 - width 180px - margin 0 0 0 -180px - transform rotateY(180deg) translateX(-10px) translateY(-48px) - pointer-events none > button position fixed z-index 1 - top 0 - left 0 + bottom 64px + left 64px padding 16px font-size 18px - color #fff - - display none // TODO + color isDark ? #fff : #444 > .body - flex 1 - padding 64px 0 0 0 - text-align center - background #578394 - background-position center - background-size cover + display grid + grid-template-rows 0.5fr 0.5fr 64px + grid-template-columns 1fr 350px + gap 16px + width 100% + max-width 1200px + height 100vh + margin 0 auto + padding 64px - &:before - content '' - display block - position absolute - top 0 - left 0 - right 0 - bottom 0 - background rgba(#000, 0.5) + > * + color isDark ? #fff : #444 + background isDark ? #313543 : #fff + box-shadow 0 3px 8px rgba(0, 0, 0, 0.2) + //border-radius 8px + overflow auto - > .forkit - position absolute - top 0 - right 0 + > .main + grid-row 1 + grid-column 1 + padding 32px - > img - position absolute - bottom 16px - right 16px - width 150px + > h1 + margin 0 - > .container - $aboutWidth = 380px - $loginWidth = 340px - $width = $aboutWidth + $loginWidth + > img + margin -8px 0 0 -16px + max-width 280px > .info margin 0 auto 16px auto width $width font-size 14px - color #fff > .stats margin-left 16px padding-left 16px - border-left solid 1px #fff + border-left solid 1px isDark ? #fff : #444 > * margin-right 16px - > main - display flex - margin auto - width $width - border-radius 8px - overflow hidden - box-shadow 0 2px 8px rgba(#000, 0.3) + > .sign + font-size 120% - > .about - width $aboutWidth - color #444 - background #fff + > .divider + margin 0 16px - > h1 - margin 0 0 16px 0 - padding 32px 32px 0 32px - color #444 + > .signin + > .signup + cursor pointer - > img - width 170px - vertical-align bottom - - > .powerd-by - margin 16px - opacity 0.7 - - > .desc - margin 0 - padding 0 32px 16px 32px - - > a - display inline-block - margin 0 0 32px 0 - font-weight bold - - > .login - width $loginWidth - padding 16px 32px 32px 32px - background isDark ? #2e3440 : #f5f5f5 + &:hover + color $theme-color > .hashtags margin 16px auto width $width font-size 14px - color #fff background rgba(#000, 0.3) border-radius 8px @@ -240,20 +235,32 @@ root(isDark) display inline-block margin 14px - > .nav - display block - margin 16px 0 - font-size 14px - color #fff + > .broadcasts + grid-row 2 + grid-column 1 + padding 32px - > .tl - margin 0 - width 410px - height 100vh - text-align left - background isDark ? #313543 : #fff + > div + padding 0 0 16px 0 + margin 0 0 16px 0 + border-bottom 1px solid isDark ? rgba(#000, 0.2) : rgba(#000, 0.05) - > * + > h1 + margin 0 + font-size 1.5em + + > .nav + display flex + justify-content center + align-items center + grid-row 3 + grid-column 1 + font-size 14px + + > .tl + grid-row 1 / 4 + grid-column 2 + text-align left max-height 100% overflow auto @@ -264,29 +271,3 @@ root(isDark) root(false) </style> - -<style lang="stylus" module> -.signupForm - padding 24px 48px 48px 48px - -.signupFormHeader - padding 48px 0 12px 0 - margin: 0 48px - font-size 1.5em - color #777 - border-bottom solid 1px #eee - -.signinForm - padding 24px 48px 48px 48px - -.signinFormHeader - padding 48px 0 12px 0 - margin: 0 48px - font-size 1.5em - color #777 - border-bottom solid 1px #eee - -.nav - a - color #666 -</style> diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 9737a281ed..10ca15d329 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -11,11 +11,17 @@ export const meta = { requireAdmin: true, params: { + broadcasts: $.arr($.obj()).optional.nullable.note({ + desc: { + 'ja-JP': 'γγγΌγγγ£γΉγ' + } + }), + disableRegistration: $.bool.optional.nullable.note({ desc: { 'ja-JP': 'ζεΎ εΆγε¦γ' } - }), + }) } }; @@ -25,6 +31,10 @@ export default (params: any) => new Promise(async (res, rej) => { const set = {} as any; + if (ps.broadcasts) { + set.broadcasts = ps.broadcasts; + } + if (typeof ps.disableRegistration === 'boolean') { set.disableRegistration = ps.disableRegistration; }