forked from kaverti/website
0.172
This commit is contained in:
parent
951fe5b493
commit
0b209c1b21
|
@ -8,4 +8,4 @@
|
|||
* Copyright (c) 2019 Daniel Eden
|
||||
*/
|
||||
|
||||
@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.delay-1s{-webkit-animation-delay:1s;animation-delay:1s}.animated.delay-2s{-webkit-animation-delay:2s;animation-delay:2s}.animated.delay-3s{-webkit-animation-delay:3s;animation-delay:3s}.animated.delay-4s{-webkit-animation-delay:4s;animation-delay:4s}.animated.delay-5s{-webkit-animation-delay:5s;animation-delay:5s}.animated.fast{-webkit-animation-duration:.8s;animation-duration:.8s}.animated.faster{-webkit-animation-duration:.5s;animation-duration:.5s}.animated.slow{-webkit-animation-duration:2s;animation-duration:2s}.animated.slower{-webkit-animation-duration:3s;animation-duration:3s}@media (prefers-reduced-motion:reduce),(print){.animated{-webkit-animation-duration:1ms!important;animation-duration:1ms!important;-webkit-transition-duration:1ms!important;transition-duration:1ms!important;-webkit-animation-iteration-count:1!important;animation-iteration-count:1!important}}
|
||||
@-webkit-keyframes fadeIn{0%{opacity:0}to{opacity:1}}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.fadeIn{-webkit-animation-name:fadeIn;animation-name:fadeIn}@-webkit-keyframes fadeOut{0%{opacity:1}to{opacity:0}}@keyframes fadeOut{0%{opacity:1}to{opacity:0}}.fadeOut{-webkit-animation-name:fadeOut;animation-name:fadeOut}@-webkit-keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInDown{0%{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInDown{-webkit-animation-name:slideInDown;animation-name:slideInDown}@-webkit-keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInLeft{0%{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInLeft{-webkit-animation-name:slideInLeft;animation-name:slideInLeft}@-webkit-keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInRight{0%{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInRight{-webkit-animation-name:slideInRight;animation-name:slideInRight}@-webkit-keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}@keyframes slideInUp{0%{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);visibility:visible}to{-webkit-transform:translateZ(0);transform:translateZ(0)}}.slideInUp{-webkit-animation-name:slideInUp;animation-name:slideInUp}@-webkit-keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}@keyframes slideOutDown{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}}.slideOutDown{-webkit-animation-name:slideOutDown;animation-name:slideOutDown}@-webkit-keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}@keyframes slideOutLeft{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}}.slideOutLeft{-webkit-animation-name:slideOutLeft;animation-name:slideOutLeft}@-webkit-keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}@keyframes slideOutRight{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}}.slideOutRight{-webkit-animation-name:slideOutRight;animation-name:slideOutRight}@-webkit-keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}@keyframes slideOutUp{0%{-webkit-transform:translateZ(0);transform:translateZ(0)}to{visibility:hidden;-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}}.slideOutUp{-webkit-animation-name:slideOutUp;animation-name:slideOutUp}.animated{-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-fill-mode:both;animation-fill-mode:both}.animated.infinite{-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.animated.delay-1s{-webkit-animation-delay:1s;animation-delay:1s}.animated.delay-2s{-webkit-animation-delay:2s;animation-delay:2s}.animated.delay-3s{-webkit-animation-delay:3s;animation-delay:3s}.animated.delay-4s{-webkit-animation-delay:4s;animation-delay:4s}.animated.delay-5s{-webkit-animation-delay:5s;animation-delay:5s}.animated.fast{-webkit-animation-duration:.8s;animation-duration:.8s}.animated.faster{-webkit-animation-duration:.5s;animation-duration:.5s}.animated.slow{-webkit-animation-duration:2s;animation-duration:2s}.animated.slower{-webkit-animation-duration:3s;animation-duration:3s}@media (prefers-reduced-motion:reduce),(print){.animated{-webkit-animation-duration:1ms;animation-duration:1ms;-webkit-transition-duration:1ms;transition-duration:1ms;-webkit-animation-iteration-count:1;animation-iteration-count:1!important}}
|
|
@ -20,7 +20,7 @@
|
|||
box-shadow: none;
|
||||
|
||||
&:focus, &:active {
|
||||
box-shadow: none!important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@
|
|||
.switch, .b-checkbox.checkbox {
|
||||
input[type=checkbox] {
|
||||
&:focus + .check, &:focus:checked + .check {
|
||||
box-shadow: none!important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ table.table {
|
|||
.table-wrapper.has-mobile-cards {
|
||||
tr {
|
||||
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1);
|
||||
margin-bottom: 3px!important;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
td {
|
||||
&.is-progress-col {
|
||||
|
@ -119,7 +119,7 @@ table.table {
|
|||
}
|
||||
|
||||
&.checkbox-cell, &.is-image-cell {
|
||||
border-bottom: 0!important;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&.checkbox-cell, &.is-actions-cell {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
"@sentry/browser": "^5.22.3",
|
||||
"@sentry/integrations": "^5.22.3",
|
||||
"@sentry/tracing": "^5.22.3",
|
||||
"@vue-a11y/skip-to": "^2.1.1",
|
||||
"@vuejs-community/vue-filter-date-format": "^1.6.1",
|
||||
"axios": "^0.19.0",
|
||||
"babel": "^6.23.0",
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
<style>
|
||||
trpl-title {
|
||||
font-size: 32px !important;
|
||||
background: -webkit-linear-gradient(#00b3ff, #007aff) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
font-size: 32px ;
|
||||
background: -webkit-linear-gradient(#00b3ff, #007aff) ;
|
||||
-webkit-background-clip: text ;
|
||||
-webkit-text-fill-color: transparent ;
|
||||
}
|
||||
trpl-para {
|
||||
background: #007aff !important;
|
||||
-webkit-background-clip: text !important;
|
||||
-webkit-text-fill-color: transparent !important;
|
||||
font-weight: bold !important;
|
||||
background: #007aff ;
|
||||
-webkit-background-clip: text ;
|
||||
-webkit-text-fill-color: transparent ;
|
||||
font-weight: bold ;
|
||||
}
|
||||
h1 {
|
||||
font-size: 32px !important;
|
||||
font-size: 32px ;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px !important;
|
||||
font-size: 24px ;
|
||||
}
|
||||
mini-br {
|
||||
border-width: 0.1em;
|
||||
|
@ -227,9 +227,10 @@
|
|||
</tab-view>
|
||||
</modal-window>
|
||||
<template>
|
||||
<b-navbar v-bind:fixed-top="true">
|
||||
<b-navbar v-bind:fixed-top="true">
|
||||
<template slot="brand">
|
||||
<b-navbar-item tag="router-link" :to="{ path: '/' }">
|
||||
<VueSkipTo to="#main" label="Skip to main content" />
|
||||
<img
|
||||
:src = $store.state.meta.logo
|
||||
>
|
||||
|
@ -298,7 +299,7 @@
|
|||
<div class="navbar-dropdown is-boxed">
|
||||
<router-link class="navbar-item" :to='"/u/" + this.$store.state.username'>My Profile</router-link>
|
||||
<router-link class="navbar-item" to='/settings'>Settings</router-link>
|
||||
<router-link class="navbar-item is-hidden-desktop " to='/notifications'>Notifications</router-link>
|
||||
<router-link class="navbar-item is-hidden-desktop" to='/notifications'>Notifications</router-link>
|
||||
<router-link to='/admin' class="navbar-item" v-if='$store.state.admin'>Admin Panel</router-link>
|
||||
<router-link class="navbar-item is-active" to='/premium'>Upgrade</router-link>
|
||||
<a class="navbar-item" @click='logout'>
|
||||
|
@ -329,7 +330,7 @@
|
|||
You are using an outdated client, refresh to update.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if='$store.state.currentStableVersion && $store.state.meta.bannerText && !$store.state.username' class="container is-fullhd" style="padding-left: 5px; padding-right: 5px; padding-top: 20px; padding-bottom: 5px;">
|
||||
<div v-if='$store.state.meta.bannerText && !$store.state.username' class="container is-fullhd" style="padding-left: 5px; padding-right: 5px; padding-top: 20px; padding-bottom: 5px;">
|
||||
<div class="notification is-info">
|
||||
{{$store.state.meta.bannerText}}
|
||||
</div>
|
||||
|
@ -341,7 +342,7 @@
|
|||
</div>
|
||||
<not-found v-show='$store.state.show404Page'></not-found>
|
||||
<transition name='fade'>
|
||||
<router-view v-show='!$store.state.show404Page'></router-view>
|
||||
<router-view id="main" v-show='!$store.state.show404Page'></router-view>
|
||||
</transition>
|
||||
<footer class="footer">
|
||||
<div class="content has-text-centered">
|
||||
|
@ -359,8 +360,8 @@
|
|||
@import 'https://kit-pro.fontawesome.com/releases/v5.14.0/css/pro.min.css';
|
||||
</style>
|
||||
<style lang="scss">
|
||||
$primary: #007aff !important;
|
||||
$navbar-dropdown-arrow: #007aff !important;
|
||||
$primary: #007aff ;
|
||||
$navbar-dropdown-arrow: #007aff ;
|
||||
</style>
|
||||
<script>
|
||||
import ModalWindow from './components/ModalWindow'
|
||||
|
@ -373,7 +374,6 @@
|
|||
import NotFound from './components/routes/NotFound'
|
||||
|
||||
import AjaxErrorHandler from './assets/js/errorHandler'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
components: {
|
||||
|
@ -440,6 +440,9 @@
|
|||
}
|
||||
return "https://cdn.kaverti.com/logo.png";
|
||||
},
|
||||
theme () {
|
||||
return this.$store.state.theme
|
||||
},
|
||||
showAccountModal: {
|
||||
get () { return this.$store.state.accountModal },
|
||||
set (val) {
|
||||
|
@ -469,6 +472,21 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
darkTheme() {
|
||||
let darkThemeLinkEl = document.createElement("link");
|
||||
darkThemeLinkEl.setAttribute("rel", "stylesheet");
|
||||
darkThemeLinkEl.setAttribute("href", "https://cdn.kaverti.com/css/dark.css");
|
||||
darkThemeLinkEl.setAttribute("id", "dark-theme");
|
||||
|
||||
let docHead = document.querySelector("head");
|
||||
docHead.append(darkThemeLinkEl);
|
||||
},
|
||||
disableDarkTheme() {
|
||||
let darkThemeLinkEl = document.removeElement("dark-theme");
|
||||
|
||||
let docHead = document.querySelector("head");
|
||||
docHead.append(darkThemeLinkEl);
|
||||
},
|
||||
recaptcha() {
|
||||
this.$recaptcha('login').then((token) => {
|
||||
console.log(token) // Will print the token
|
||||
|
@ -574,6 +592,7 @@
|
|||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.axios.get('/api/v1/kaverti/state')
|
||||
.then(res => {
|
||||
this.$store.commit('setSettings', res.data)
|
||||
|
@ -603,6 +622,7 @@
|
|||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.closeConn()
|
||||
}).catch(err => {
|
||||
this.showConn()
|
||||
|
@ -704,6 +724,7 @@
|
|||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.closeConn()
|
||||
}).catch(err => {
|
||||
this.showConn()
|
||||
|
@ -753,6 +774,7 @@
|
|||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.closeConn()
|
||||
}).catch(err => {
|
||||
this.showConn()
|
||||
|
@ -786,6 +808,8 @@
|
|||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
this.closeConn()
|
||||
}).catch(err => {
|
||||
this.showConn()
|
||||
|
@ -798,7 +822,6 @@
|
|||
this.pollConn()
|
||||
}
|
||||
})
|
||||
|
||||
this.axios.get('/api/v1/kaverti/state')
|
||||
.then(res => {
|
||||
this.$store.commit('setSettings', res.data)
|
||||
|
@ -813,6 +836,10 @@
|
|||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setKoins', res.data.koins)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
if(res.data.theme === "dark") {
|
||||
this.darkTheme()
|
||||
}
|
||||
this.closeConn()
|
||||
}).catch(err => {
|
||||
this.showConn()
|
||||
|
@ -863,7 +890,7 @@
|
|||
if(val) {
|
||||
this.$refs.ajaxErrorsModalButton.focus()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -871,9 +898,9 @@
|
|||
@import url('https://fonts.googleapis.com/css?family=Lato:400,400i,500,500i,700');
|
||||
@import './assets/scss/variables.scss';
|
||||
@import './assets/sass/primary';
|
||||
$primary: #0ba8e6 !important;
|
||||
$primary: #0ba8e6 ;
|
||||
$colors: (
|
||||
"primary": #0ba8e6 !important
|
||||
"primary": #0ba8e6
|
||||
);
|
||||
html, body {
|
||||
width: 100%;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -68,7 +68,7 @@ b, strong {
|
|||
letter-spacing: 0.25px;
|
||||
background: none;
|
||||
background-color: #fff;
|
||||
color: lighten($color__text--primary, 30%) !important;
|
||||
color: lighten($color__text--primary, 30%) ;
|
||||
transition: background-color 0.2s, border-color 0.2s, filter 0.2s;
|
||||
outline: none;
|
||||
|
||||
|
@ -130,17 +130,17 @@ b, strong {
|
|||
@mixin filled_button($background, $border, $text: #fff) {
|
||||
background-color: $background;
|
||||
border-color: $border;
|
||||
color: $text !important;
|
||||
|
||||
color: $text ;
|
||||
|
||||
&:hover {
|
||||
background-color: darken($background, 5%);
|
||||
border-color: rgba($border, 0.6);
|
||||
color: darken(#fff, 5%) !important;
|
||||
color: darken(#fff, 5%) ;
|
||||
}
|
||||
&:active {
|
||||
background-color: darken($background, 10%);
|
||||
border-color: rgba($border, 0.6);
|
||||
color: darken(#fff, 10%) !important;
|
||||
color: darken(#fff, 10%) ;
|
||||
}
|
||||
}
|
||||
@at-root #{&}--blue {
|
||||
|
@ -312,4 +312,4 @@ blockquote.twitter-tweet {
|
|||
}
|
||||
.slide-enter-to, .slide-leave {
|
||||
max-height: 20rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,12 @@
|
|||
<div class='avatar_icon__header_info'>
|
||||
<span class='avatar_icon__username' @click.stop='goToUser'>
|
||||
{{proxyUser.username}}
|
||||
<b-tag rounded class="is-success" v-if='proxyUser.system'>SYSTEM</b-tag>
|
||||
<b-tag rounded class="is-info" v-if='proxyUser.bot'>BOT</b-tag>
|
||||
<b-tag rounded class="is-danger" v-if='proxyUser.admin'>ADMIN</b-tag>
|
||||
</span>
|
||||
<span class='avatar_icon__date'>{{proxyUser.createdAt | formatDate('date') }}</span>
|
||||
<span class='avatar_icon__date'>Created: {{proxyUser.createdAt | formatDate('date') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class='avatar_icon__description' v-if='proxyUser.description'>
|
||||
{{proxyUser.description}}
|
||||
{{proxyUser.description | stripTags | truncate(30)}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -53,42 +53,42 @@
|
|||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.fancy_input {
|
||||
display: inline-flex !important;
|
||||
flex-direction: column !important;
|
||||
position: relative !important;
|
||||
margin-top: 0.25rem !important;
|
||||
margin-bottom: 0.5rem !important;
|
||||
display: inline-flex ;
|
||||
flex-direction: column ;
|
||||
position: relative ;
|
||||
margin-top: 0.25rem ;
|
||||
margin-bottom: 0.5rem ;
|
||||
|
||||
@at-root #{&}__input {
|
||||
transition: border-color 0.2s !important;
|
||||
width: 100% !important;
|
||||
transition: border-color 0.2s ;
|
||||
width: 100% ;
|
||||
|
||||
@at-root #{&}--large {
|
||||
padding: 0.5rem !important;
|
||||
padding: 0.5rem ;
|
||||
}
|
||||
@at-root #{&}--error {
|
||||
border-color: $color__red--primary !important;
|
||||
border-color: $color__red--primary ;
|
||||
}
|
||||
}
|
||||
|
||||
@at-root #{&}__placeholder {
|
||||
position: absolute !important;
|
||||
top: 0.35rem !important !important;
|
||||
position: absolute ;
|
||||
top: 0.35rem ;
|
||||
background-color: #fff;
|
||||
left: 0.35rem !important;
|
||||
color: $color__gray--darkest !important;
|
||||
pointer-events: none !important;
|
||||
transition: top 0.2s, font-size 0.2s !important;
|
||||
left: 0.35rem ;
|
||||
color: $color__gray--darkest ;
|
||||
pointer-events: none ;
|
||||
transition: top 0.2s, font-size 0.2s ;
|
||||
|
||||
@at-root #{&}--large {
|
||||
top: 0.55rem !important;
|
||||
left: 0.6rem !important;
|
||||
top: 0.55rem ;
|
||||
left: 0.6rem ;
|
||||
}
|
||||
|
||||
@at-root #{&}--active {
|
||||
top: -0.5rem !important;
|
||||
font-size: 0.75rem !important;
|
||||
transition: top 0.2s, font-size 0.2s !important;
|
||||
top: -0.5rem ;
|
||||
font-size: 0.75rem ;
|
||||
transition: top 0.2s, font-size 0.2s ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
padding: 0.1rem 0.25rem;
|
||||
top: -1.75rem;
|
||||
right: 0;
|
||||
|
||||
|
||||
&:first-letter{ text-transform: capitalize; }
|
||||
|
||||
opacity: 0;
|
||||
|
@ -122,8 +122,8 @@
|
|||
@media (max-width: 420px) {
|
||||
.fancy_textarea {
|
||||
@at-root #{&}__container {
|
||||
width: 100% !important;
|
||||
width: 100% ;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -322,7 +322,6 @@
|
|||
@include user-select(none);
|
||||
@include text($font--role-default, 1rem, 600);
|
||||
color: $color__darkgray--primary;
|
||||
border: thin solid $color__gray--primary;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
@at-root #{&}--dark {
|
||||
span {
|
||||
background-color: $color__darkgray--primary !important;
|
||||
background-color: $color__darkgray--primary ;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,12 +55,10 @@
|
|||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.more_threads {
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
width: 80%;
|
||||
padding: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
|
||||
@at-root #{&}__header {
|
||||
font-size: 1.5rem;
|
||||
|
@ -78,28 +76,17 @@
|
|||
border-bottom: thin solid $color__gray--darker;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: $color__lightgray--primary;
|
||||
}
|
||||
&:active {
|
||||
background-color: $color__lightgray--darker;
|
||||
}
|
||||
|
||||
@at-root #{&}--header {
|
||||
cursor: default;
|
||||
font-size: 1rem;
|
||||
font-weight: 500;
|
||||
border-bottom: 0.125rem solid $color__gray--darker;
|
||||
|
||||
&:hover { background-color: #fff; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@at-root #{&}__name {
|
||||
word-break: break-all;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
@at-root #{&}__date_created {
|
||||
color: $color__text--secondary;
|
||||
font-size: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
|
@ -424,7 +424,7 @@
|
|||
.notification_button__menu {
|
||||
width: 100%;
|
||||
left: unset;
|
||||
right: unset !important;
|
||||
right: unset ;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class='post_scrubber__line' ref='line' @click='lineClick'></div>
|
||||
<div
|
||||
class='post_scrubber__dragger'
|
||||
|
||||
|
||||
:class='{ "post_scrubber--no_top_transition": dragging }'
|
||||
:style='{
|
||||
"top": draggerYCoord + "px"
|
||||
|
@ -16,7 +16,7 @@
|
|||
<div
|
||||
class='post_scrubber__dragger_info'
|
||||
:class='{ "post_scrubber--no_top_transition": dragging }'
|
||||
|
||||
|
||||
:style='{
|
||||
"top": draggerYCoord + "px"
|
||||
}'
|
||||
|
@ -122,7 +122,7 @@
|
|||
this.clientY = e.clientY
|
||||
}
|
||||
})
|
||||
window.addEventListener('mouseup', () => {
|
||||
window.addEventListener('mouseup', () => {
|
||||
if(this.dragging) {
|
||||
this.dragging = false
|
||||
this.$emit('input', this.currentPost-1)
|
||||
|
@ -143,12 +143,11 @@
|
|||
margin-left: 0.25rem;
|
||||
|
||||
@at-root #{&}--no_top_transition {
|
||||
transition: background-color 0.2s !important;
|
||||
transition: background-color 0.2s ;
|
||||
}
|
||||
|
||||
@at-root #{&}__line {
|
||||
height: 100%;
|
||||
background-color: $color__gray--darker;
|
||||
border-radius: 1rem;
|
||||
width: 0.125rem;
|
||||
}
|
||||
|
@ -193,14 +192,11 @@
|
|||
width: 10rem;
|
||||
margin-top: calc(-1.5rem / 2 - 0.125rem);
|
||||
pointer-events: none;
|
||||
background-color: #fff;
|
||||
left: 1rem;
|
||||
font-size: 0.9rem;
|
||||
border-radius: 0.125rem;
|
||||
padding: 0.25rem;
|
||||
transition: top 0.3s;
|
||||
|
||||
@extend .shadow_border;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
}
|
||||
|
||||
@at-root #{&}__button--selected {
|
||||
color: $color__blue--darker !important;
|
||||
color: $color__blue--darker ;
|
||||
}
|
||||
|
||||
@at-root #{&}__option {
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
transition: transform 0.2s;
|
||||
}
|
||||
@at-root #{&}--selected {
|
||||
color: $color__blue--darker !important;
|
||||
color: $color__blue--darker ;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,6 @@
|
|||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.thread_display {
|
||||
background-color: #fff;
|
||||
border: thin solid $color__gray--darker;
|
||||
border-radius: 0.25rem;
|
||||
cursor: pointer;
|
||||
|
|
|
@ -262,7 +262,6 @@
|
|||
border-radius: 0.25rem;
|
||||
|
||||
@at-root #{&}--highlighted {
|
||||
background-color: $color__lightgray--darkest;
|
||||
animation-name: shake;
|
||||
animation-iteration-count: 5;
|
||||
animation-timing-function: linear;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
@import '../assets/scss/variables.scss';
|
||||
|
||||
.slide-fade-enter-active, .slide-fade-leave-active {
|
||||
transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28) !important;
|
||||
transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28) ;
|
||||
}
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
transform: translateX(25rem);
|
||||
|
@ -65,7 +65,7 @@
|
|||
transition: background-color 0.2s;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
|
||||
@include user-select(none);
|
||||
|
||||
&:hover {
|
||||
|
@ -87,4 +87,4 @@
|
|||
name: 'ThreadPostNotification',
|
||||
props: ['post']
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
|
|
@ -6,6 +6,28 @@
|
|||
<avatar-icon :user='user' size='small'></avatar-icon>
|
||||
<div class='user_display__username'>
|
||||
{{user.username}}
|
||||
<tooltips style="padding-left: 5px;">
|
||||
<b-tooltip v-if='user && user.system' class="is-success" label="This user is a system user operated by administrators that mainly run API operations.">
|
||||
<b-tag rounded class="is-success"> SYSTEM <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip v-if='user && user.bot' class="is-info" label="This user is a bot account that can run automated API operations.">
|
||||
<b-tag rounded class="is-info"> BOT <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip v-if='user && user.admin' class="is-danger" label="User is an official Kaverti administrator.">
|
||||
<b-tag class="is-danger" rounded> ADMIN <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip v-if='user && user.hidden' class="is-info" label="User is not discoverable in the user list.">
|
||||
<b-tag rounded> HIDDEN <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip v-if='user && user.booster' class="is-light" label="User is boosting the Kaverti Discord server.">
|
||||
<b-tag class="is-light" rounded> BOOSTER <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
</tooltips>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -34,7 +56,7 @@
|
|||
margin-bottom: 1rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
transition: box-shadow 0.2s;
|
||||
|
||||
|
||||
&:hover {
|
||||
@extend .shadow_border--hover;
|
||||
}
|
||||
|
@ -45,4 +67,4 @@
|
|||
margin-left: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
@ -55,6 +55,12 @@
|
|||
<th>
|
||||
<sort-menu v-model='tableSort' column='createdAt' display='Account created at'></sort-menu>
|
||||
</th>
|
||||
<th>
|
||||
<sort-menu v-model='tableSort' column='postCount' display='Posts count'></sort-menu>
|
||||
</th>
|
||||
<th>
|
||||
<sort-menu v-model='tableSort' column='threadCount' display='Threads count'></sort-menu>
|
||||
</th>
|
||||
</tr>
|
||||
<tr v-for='user in users' :key='"user-row" + user.username' v-show="user && !user.hidden">
|
||||
<td class='admin_users__user_column'>
|
||||
|
@ -78,8 +84,8 @@
|
|||
<b-tag rounded> HIDDEN <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-tooltip v-if='user && user.booster' class="is-success" label="User is boosting the Kaverti Discord server.">
|
||||
<b-tag class="is-success" rounded> BOOSTER <i class="fas fa-info-circle"></i></b-tag>
|
||||
<b-tooltip v-if='user && user.booster' class="is-light" label="User is boosting the Kaverti Discord server.">
|
||||
<b-tag class="is-light" rounded> BOOSTER <i class="fas fa-info-circle"></i></b-tag>
|
||||
</b-tooltip>
|
||||
|
||||
<b-button v-if="!user.admin" class="is-info is-small" rounded @click='toggleBadgeModal(user)'>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<template>
|
||||
<h1>Unused</h1>
|
||||
<main>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
|
|
@ -43,8 +43,27 @@
|
|||
<select-options
|
||||
:options='filterOptions'
|
||||
v-model='selectedFilterOption'
|
||||
class='thread_sorting__filter'
|
||||
class=''
|
||||
></select-options>
|
||||
<br/>
|
||||
<div class='threads_main__side_bar__title'>
|
||||
Leaderboard:
|
||||
</div>
|
||||
<div style="padding-right: 10px">
|
||||
<div class="box" v-for='user in users' :key='"user-row" + user.username' v-show="user && !user.hidden">
|
||||
<header class="content">
|
||||
{{user.postCount}} <leaderpara v-if="user.postCount === 1">post</leaderpara> <leaderpara v-else>posts</leaderpara>
|
||||
<p>
|
||||
<router-link :to='"/user/" + user.username'>{{user.username}}</router-link>
|
||||
</p>
|
||||
</header>
|
||||
</div>
|
||||
<p name='fade' mode='out-in'>
|
||||
<center><loading-message key='loading' v-if='loading'></loading-message></center>
|
||||
<center><div class='overlay_message' v-if='!loading && !users.length'>
|
||||
Something went wrong while loading the users, check your internet connection, or check the <a href="https://status.troplo.com">Service Status</a>
|
||||
</div></center></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -110,8 +129,9 @@
|
|||
SelectOptions
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
filterOptions: [
|
||||
return {offset: 0,
|
||||
users: [],
|
||||
filterOptions: [
|
||||
{name: 'New', value: 'NEW'},
|
||||
{name: 'Most active', value: 'MOST_ACTIVE'},
|
||||
{name: 'No replies', value: 'NO_REPLIES'}
|
||||
|
@ -167,6 +187,33 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
fetchLeaderboardData () {
|
||||
if(this.offset === null) return;
|
||||
|
||||
let url = `/api/v1/users/leaderboard?
|
||||
sort=postCount
|
||||
&order=desc
|
||||
&offset=0
|
||||
`;
|
||||
|
||||
this.loading = true;
|
||||
this.axios
|
||||
.get(url)
|
||||
.then(res => {
|
||||
this.users.push(...res.data);
|
||||
this.loading = /*loading =*/ false;
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e);
|
||||
this.loading = /*loading =*/ false;
|
||||
});
|
||||
},
|
||||
resetLeaderboard () {
|
||||
this.offset = 0;
|
||||
this.users = [];
|
||||
|
||||
this.fetchLeaderboardData();
|
||||
},
|
||||
navigateToThread (slug, id) {
|
||||
this.$router.push('/thread/' + slug + '/' + id);
|
||||
},
|
||||
|
@ -248,7 +295,7 @@
|
|||
this.$store.commit('setAccountTabs', 0)
|
||||
this.$store.commit('setAccountModalState', true)
|
||||
}
|
||||
|
||||
this.fetchLeaderboardData()
|
||||
logger('index')
|
||||
},
|
||||
destroyed () {
|
||||
|
@ -269,7 +316,6 @@
|
|||
.forum_description {
|
||||
padding: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
}
|
||||
|
@ -285,7 +331,6 @@
|
|||
|
||||
@at-root #{&}__display {
|
||||
padding-right: 0.5rem;
|
||||
border-right: thin solid $color__gray--primary;
|
||||
margin-right: 1.25rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
@ -300,16 +345,13 @@
|
|||
.threads_main__side_bar {
|
||||
width: 12rem;
|
||||
height: 0%;
|
||||
background: #fff;
|
||||
margin-right: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
padding: 0.5rem 0 1rem 1rem;
|
||||
position: sticky;
|
||||
top: 4.5rem;
|
||||
|
||||
@at-root #{&}__title {
|
||||
color: $color__darkgray--darker;
|
||||
cursor: default;
|
||||
font-weight: 500;
|
||||
font-size: 1.125rem;
|
||||
|
@ -333,15 +375,9 @@
|
|||
width: 0.9rem;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.25rem;
|
||||
background-color: $color__gray--darkest;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
@at-root #{&}__text {
|
||||
filter: saturate(0.75), brightness(0.75);
|
||||
}
|
||||
|
||||
|
||||
@at-root #{&}--selected {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -357,37 +393,30 @@
|
|||
|
||||
font-size: 1.25rem;
|
||||
margin: 0 0 1rem 0;
|
||||
background-color: $color__lightgray--primary;
|
||||
border-color: $color__gray--darker;
|
||||
width: 100%;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.thread {
|
||||
background-color: #fff;
|
||||
padding: 0.5rem 0;
|
||||
cursor: default;
|
||||
text-align: left;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: $color__lightgray--primary;
|
||||
}
|
||||
|
||||
td, th {
|
||||
padding: 0.3rem 0.5rem;
|
||||
border-bottom: solid thin $color__lightgray--primary;
|
||||
}
|
||||
|
||||
@at-root #{&}--header {
|
||||
&:hover {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
th {
|
||||
font-weight: 400;
|
||||
padding-bottom: 0.25rem;
|
||||
border-bottom: thin solid $color__lightgray--darkest;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,7 +428,6 @@
|
|||
display: inline-block;
|
||||
}
|
||||
@at-root #{&}__date {
|
||||
color: $color__text--secondary;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -407,7 +407,7 @@
|
|||
.notification_button__menu {
|
||||
width: 100%;
|
||||
left: unset;
|
||||
right: unset !important;
|
||||
right: unset ;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,15 +6,15 @@
|
|||
<b-menu-item
|
||||
:label=item.name
|
||||
:icon=item.icon
|
||||
:key='"menu-item-" + index'
|
||||
:key='index'
|
||||
v-for='(item, index) in menuItems'
|
||||
:class="{'settings_menu__item--selected': index === selected}"
|
||||
:class="{'': index === selected}"
|
||||
@click='$router.push("/settings/" + item.route)'
|
||||
></b-menu-item>
|
||||
</b-menu-list>
|
||||
</b-menu>
|
||||
</div>
|
||||
<div class='settings_page'>
|
||||
<div class='column box'>
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -93,8 +93,6 @@
|
|||
|
||||
.settings_menu {
|
||||
width: 15rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
background-color: #fff;
|
||||
padding: 1rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="box">
|
||||
<div>
|
||||
<center>
|
||||
<img src="https://cdn.kaverti.com/icon.png" width="10%">
|
||||
<h1>Kaverti v{{this.$store.state.clientVersion}}</h1>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<style>
|
||||
h1 {
|
||||
font-size: 32px !important;
|
||||
font-size: 32px ;
|
||||
}
|
||||
h2 {
|
||||
font-size: 24px !important;
|
||||
font-size: 24px ;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
|
|
|
@ -1,9 +1,135 @@
|
|||
<template>
|
||||
<main>
|
||||
<span>
|
||||
<div v-if="$store.state.developerMode">
|
||||
<center>
|
||||
<h1>Only available on <a href="https://canary.kaverti.com">Kaverti Canary</a>.</h1>
|
||||
<h1>Experiments</h1>
|
||||
<div class="column">
|
||||
<h2>Dark Theme</h2>
|
||||
<b-switch
|
||||
passive-type='is-warning'
|
||||
type='is-dark'
|
||||
false-value="light"
|
||||
true-value="dark"
|
||||
v-model="experiments.theme"
|
||||
v-if="$store.state.developerMode">
|
||||
Theme
|
||||
</b-switch>
|
||||
<br/>
|
||||
<br/>
|
||||
<b-button
|
||||
class='button is-info'
|
||||
:loading='experiments.loading'
|
||||
@click='savePreferences'
|
||||
>
|
||||
Save experiment preferences
|
||||
</b-button>
|
||||
</div>
|
||||
</center>
|
||||
</span>
|
||||
</div>
|
||||
<div v-else>
|
||||
<center>
|
||||
<h2>Please enable Integration Developer Mode to gain access to experiments.</h2>
|
||||
<p>It's easy! Just go to Settings > General, flip the switch and save! </p>
|
||||
</center>
|
||||
</div>
|
||||
</main>
|
||||
</template>
|
||||
<script>
|
||||
import FancyTextarea from '../FancyTextarea'
|
||||
|
||||
import AjaxErrorHandler from '../../assets/js/errorHandler'
|
||||
import logger from '../../assets/js/logger'
|
||||
|
||||
export default {
|
||||
name: 'settingsGeneral',
|
||||
components: {
|
||||
// eslint-disable-next-line vue/no-unused-components
|
||||
FancyTextarea
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
experiments: {
|
||||
theme: '',
|
||||
loading: false,
|
||||
error: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
capitalizeFirstLetter(value) {
|
||||
return value.toUpperCase()
|
||||
},
|
||||
saveDescription() {
|
||||
this.description.error = ''
|
||||
this.description.loading = true
|
||||
|
||||
this.axios
|
||||
.put('/api/v1/users/preferences', {
|
||||
description: this.description.value
|
||||
})
|
||||
.then(() => {
|
||||
this.description.loading = false
|
||||
})
|
||||
.catch(e => {
|
||||
this.description.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
savePreferences() {
|
||||
this.experiments.error = ''
|
||||
this.experiments.loading = true
|
||||
|
||||
this.axios
|
||||
.put('/api/v1/users/experiments', {
|
||||
theme: this.experiments.theme
|
||||
})
|
||||
.then(() => {
|
||||
this.experiments.loading = false
|
||||
this.axios.get('/api/v1/userinfo')
|
||||
.then(res => {
|
||||
this.$store.commit('setUsername', res.data.username)
|
||||
this.$store.commit('setEmail', res.data.email)
|
||||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
this.$store.commit('setTheme', res.data.theme)
|
||||
if(res.data.theme === "dark") {
|
||||
alert("Please refresh to enable this experiment. (theme)")
|
||||
} else {
|
||||
alert("Please refresh to disable this experiment. (theme)")
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.experiments.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.axios.get('/api/v1/userinfo')
|
||||
.then(res => {
|
||||
this.$store.commit('setUsername', res.data.username)
|
||||
this.$store.commit('setEmail', res.data.email)
|
||||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
})
|
||||
this.$store.dispatch('setTitle', 'Experiments')
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.axios
|
||||
.get('/api/v1/userinfo')
|
||||
.then(res => {
|
||||
this.experiments.theme = res.data.theme || ''
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
})
|
||||
|
||||
logger('settingsGeneral')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -25,6 +25,17 @@
|
|||
Save description
|
||||
</b-button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Preferences</h2>
|
||||
<b-switch class="is-info" v-model="preferences.developerMode">Developer Mode and get beta features first</b-switch><br>
|
||||
<b-button
|
||||
class='button is-info'
|
||||
:loading='preferences.loading'
|
||||
@click='savePreferences'
|
||||
>
|
||||
Save preferences
|
||||
</b-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -47,16 +58,25 @@
|
|||
loading: false,
|
||||
error: ''
|
||||
},
|
||||
preferences: {
|
||||
theme: '',
|
||||
developerMode: '',
|
||||
loading: false,
|
||||
error: ''
|
||||
},
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
capitalizeFirstLetter(value) {
|
||||
return value.toUpperCase()
|
||||
},
|
||||
saveDescription() {
|
||||
this.description.error = ''
|
||||
this.description.loading = true
|
||||
|
||||
this.axios
|
||||
.put('/api/v1/user/' + this.$store.state.username, {
|
||||
.put('/api/v1/users/preferences', {
|
||||
description: this.description.value
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -66,6 +86,30 @@
|
|||
this.description.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
},
|
||||
savePreferences() {
|
||||
this.preferences.error = ''
|
||||
this.preferences.loading = true
|
||||
|
||||
this.axios
|
||||
.put('/api/v1/users/preferences', {
|
||||
developerMode: this.preferences.developerMode
|
||||
})
|
||||
.then(() => {
|
||||
this.preferences.loading = false
|
||||
this.axios.get('/api/v1/userinfo')
|
||||
.then(res => {
|
||||
this.$store.commit('setUsername', res.data.username)
|
||||
this.$store.commit('setEmail', res.data.email)
|
||||
this.$store.commit('setEmailVerified', res.data.emailVerified)
|
||||
this.$store.commit('setAdmin', res.data.admin)
|
||||
this.$store.commit('setDevMode', res.data.developerMode)
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
this.preferences.loading = false
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
})
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@ -84,6 +128,8 @@
|
|||
.get('/api/v1/userinfo')
|
||||
.then(res => {
|
||||
this.description.value = res.data.description || ''
|
||||
this.preferences.developerMode = res.data.developerMode
|
||||
this.preferences.theme = res.data.theme || ''
|
||||
})
|
||||
.catch(e => {
|
||||
AjaxErrorHandler(this.$store)(e)
|
||||
|
@ -94,66 +140,3 @@
|
|||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '../../assets/scss/variables.scss';
|
||||
|
||||
.profile_picture_preview {
|
||||
height: 5rem;
|
||||
width: 5rem;
|
||||
}
|
||||
|
||||
.profile_picture_modal {
|
||||
padding-top: 1rem;
|
||||
transition: all 0.2s;
|
||||
|
||||
@at-root #{&}--picture .dragging {
|
||||
background-color: $color__lightgray--primary;
|
||||
}
|
||||
|
||||
@at-root #{&}__overlay {
|
||||
@include loading-overlay(rgba(0, 0, 0, 0.5), 0.125rem);
|
||||
}
|
||||
|
||||
@at-root #{&}__upload_button input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@at-root #{&}__drag_area {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
|
||||
@at-root #{&}__image {
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
display: inline-block;
|
||||
margin-top: -1rem;
|
||||
}
|
||||
|
||||
@at-root #{&}__icon {
|
||||
font-size: 6rem;
|
||||
color: $color__gray--darker;
|
||||
transition: all 0.2s;
|
||||
|
||||
@at-root #{&}--picture.dragging {
|
||||
transform: translateY(-0.5rem) scale(1.1);
|
||||
color: $color__gray--darkest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint--tablet) {
|
||||
.h1 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.visuallyhidden {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px; width: 1px;
|
||||
margin: -1px; padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -422,20 +422,16 @@
|
|||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
background-color: #fff;
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
width: 80%;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
}
|
||||
|
||||
.posts {
|
||||
width: 80%;
|
||||
background-color: #fff;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
}
|
||||
|
||||
|
||||
|
@ -497,7 +493,7 @@
|
|||
|
||||
@media (max-width: $breakpoint--tablet-thread) {
|
||||
.route_container {
|
||||
padding-bottom: 2rem !important;
|
||||
padding-bottom: 2rem ;
|
||||
}
|
||||
|
||||
.thread_side_bar {
|
||||
|
|
|
@ -284,8 +284,6 @@
|
|||
}
|
||||
|
||||
.thread_meta_info {
|
||||
background-color: #fff;
|
||||
border: thin solid $color__gray--darker;
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
|
@ -350,9 +348,7 @@
|
|||
|
||||
.editor {
|
||||
display: flex;
|
||||
background-color: #fff;
|
||||
border-radius: 0.25rem;
|
||||
border: thin solid $color__gray--darker;
|
||||
|
||||
transition: all 0.2s;
|
||||
|
||||
|
@ -430,7 +426,7 @@
|
|||
width: 100%;
|
||||
|
||||
> div, input {
|
||||
width: 100% !important;
|
||||
width: 100% ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,10 +56,10 @@
|
|||
Manage user
|
||||
</button>
|
||||
</menu-button><br>
|
||||
<b-button v-if="user && relationship && user.username !== $store.state.username && $store.state.username" class='is-danger button' icon-left="minus">
|
||||
<b-button v-if="$store.state.developerMode && relationship && user.username !== $store.state.username && $store.state.username" class='is-danger button' icon-left="minus">
|
||||
Remove Friend
|
||||
</b-button>
|
||||
<b-button v-model="relationship.type" :value="1" @click="doRelationship" v-if="user && !relationship && user.username !== $store.state.username && $store.state.username" class='is-info button' icon-left="plus">
|
||||
<b-button :value="1" @click="doRelationship" v-if="$store.state.developerMode && user && !relationship && user.username !== $store.state.username && $store.state.username" class='is-info button' icon-left="plus">
|
||||
Send friend request
|
||||
</b-button>
|
||||
</div>
|
||||
|
|
|
@ -407,7 +407,7 @@ export default {
|
|||
width: 100%;
|
||||
|
||||
> div, input {
|
||||
width: 100% !important;
|
||||
width: 100% ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,11 +43,13 @@
|
|||
</header>
|
||||
<div class="card-content">
|
||||
<div class="content limit" v-if="user.description">
|
||||
{{user.description | truncate(200)}}<br/>
|
||||
<time v-bind:datetime="user.createdAt">Created at {{user.createdAt | formatDate}}</time>
|
||||
{{user.description | truncate(200)}}
|
||||
</div>
|
||||
<div class="content" v-if="!user.description">
|
||||
No description set.<br><br>
|
||||
No description set.
|
||||
</div>
|
||||
<div class="content">
|
||||
Post count: {{user.postCount}}<br>
|
||||
<time v-bind:datetime="user.createdAt">Created at {{user.createdAt | formatDate}}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -31,7 +31,7 @@ import { faBell, faComment } from '@fortawesome/free-regular-svg-icons'
|
|||
import VueMatomo from 'vue-matomo'
|
||||
import { VueSpinners } from '@saeris/vue-spinners'
|
||||
import moment from 'moment'
|
||||
|
||||
import VueSkipTo from '@vue-a11y/skip-to'
|
||||
import {
|
||||
faBars, faPlus, faGrin, faLink, faCode,
|
||||
faTimes, faUnlockAlt, faReply, faHome, faTh,
|
||||
|
@ -57,6 +57,7 @@ Vue.component('font-awesome-icon', FontAwesomeIcon);
|
|||
const Index = () => import('./components/routes/Index')
|
||||
const CategorySelect = () => import('./components/routes/CategorySelect')
|
||||
const HomeUnauthenticated = () => import('./components/routes/HomeUnauthenticated')
|
||||
const HomeAuthenticated = () => import('./components/routes/HomeAuthenticated')
|
||||
|
||||
const P = () => import('./components/routes/P')
|
||||
const Thread = () => import('./components/routes/Thread')
|
||||
|
@ -124,6 +125,7 @@ Vue.use(linkExpander)
|
|||
Vue.use(Buefy, { defaultIconPack: 'fas' })
|
||||
Vue.use(VPaginator)
|
||||
Vue.use(VueSpinners)
|
||||
Vue.use(VueSkipTo)
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://287b35aa71b54ffea9e66b5ce03d2e29@o444992.ingest.sentry.io/5420646",
|
||||
|
@ -145,6 +147,7 @@ Vue.use({
|
|||
const router = new VueRouter({
|
||||
routes: [
|
||||
{ path: '/', component: HomeUnauthenticated },
|
||||
{ path: '/dashboard', component: HomeAuthenticated },
|
||||
{ path: '/category/select', component: CategorySelect },
|
||||
{ path: '/category/:category', component: Index },
|
||||
{ path: '/p/:id', component: P },
|
||||
|
|
|
@ -25,9 +25,10 @@ export default new Vuex.Store({
|
|||
koins: '',
|
||||
email: '',
|
||||
emailVerified: true,
|
||||
clientVersion: '0.171-stable',
|
||||
clientVersion: '0.172-stable',
|
||||
latestClientVersion: '',
|
||||
developerMode: false,
|
||||
theme: '',
|
||||
|
||||
token: null,
|
||||
passkey: "register",
|
||||
|
@ -94,6 +95,9 @@ export default new Vuex.Store({
|
|||
setPassKey(state, passkey) {
|
||||
state.passkey = passkey
|
||||
},
|
||||
setTheme(state, theme) {
|
||||
state.theme = theme
|
||||
},
|
||||
set404Page(state, value) {
|
||||
state.show404Page = value
|
||||
},
|
||||
|
|
|
@ -1520,6 +1520,11 @@
|
|||
resolved "https://npm.open-registry.dev/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
|
||||
integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
|
||||
|
||||
"@vue-a11y/skip-to@^2.1.1":
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@vue-a11y/skip-to/-/skip-to-2.1.1.tgz#122fe02a589947f5c4afef66957ffd844bcbda39"
|
||||
integrity sha512-/N7NVd9hy/oxmvaae8AUZo2IWcgZWjtNgkt/A+rwNqDeCFOREbCLEyzPWqcWvHO1ubzpGVoShGWuICGDqJr9xA==
|
||||
|
||||
"@vue/babel-helper-vue-jsx-merge-props@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://npm.open-registry.dev/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.0.0.tgz#048fe579958da408fb7a8b2a3ec050b50a661040"
|
||||
|
|
|
@ -19,6 +19,10 @@ let Errors = {
|
|||
'Due to the nature of this action, a login is required.',
|
||||
401
|
||||
],
|
||||
deniedExperiments: [
|
||||
'Please enable integration developer mode to access experiments.',
|
||||
401
|
||||
],
|
||||
reCAPTCHA: [
|
||||
'Oh uh! You failed the reCAPTCHA score, please try again or contact support! This could be caused because you are operating the API through an unofficial client, CLI application, or reCAPTCHA is blocked by your network administrator or ISP.',
|
||||
400
|
||||
|
|
|
@ -78,7 +78,12 @@ module.exports = (sequelize, DataTypes) => {
|
|||
},
|
||||
theme: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: 'light'
|
||||
defaultValue: 'light',
|
||||
values: ['light', 'dark'],
|
||||
isIn: {
|
||||
args: [['light', 'dark']],
|
||||
msg: "Theme can only be one of the pre-defined options"
|
||||
},
|
||||
},
|
||||
lastRewardDate: {
|
||||
type: DataTypes.DATE
|
||||
|
@ -108,7 +113,12 @@ module.exports = (sequelize, DataTypes) => {
|
|||
},
|
||||
developerMode: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
defaultValue: false,
|
||||
validate: {
|
||||
isBoolean: {
|
||||
msg: 'Developer mode can only be true or false.'
|
||||
}
|
||||
}
|
||||
},
|
||||
experimentMode: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
|
|
40
preview.html
40
preview.html
|
@ -23,16 +23,16 @@
|
|||
<style type="text/css">
|
||||
@media screen and (max-width: 525px) {
|
||||
table[class="wrapper"] {
|
||||
width: 100% !important;
|
||||
width: 100% ;
|
||||
}
|
||||
|
||||
td[class="logo"] {
|
||||
text-align: left;
|
||||
padding: 20px 0 20px 0 !important;
|
||||
padding: 20px 0 20px 0 ;
|
||||
}
|
||||
|
||||
td[class="logo"] img {
|
||||
margin: 0 auto!important;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
td[class="mobile-hide"] {
|
||||
|
@ -40,62 +40,62 @@
|
|||
}
|
||||
|
||||
img[class="mobile-hide"] {
|
||||
display: none !important;
|
||||
display: none ;
|
||||
}
|
||||
|
||||
img[class="img-max"] {
|
||||
max-width: 100% !important;
|
||||
height: auto !important;
|
||||
max-width: 100% ;
|
||||
height: auto ;
|
||||
}
|
||||
|
||||
table[class="responsive-table"] {
|
||||
width: 100%!important;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
td[class="padding"] {
|
||||
padding: 10px 5% 15px 5% !important;
|
||||
padding: 10px 5% 15px 5% ;
|
||||
}
|
||||
|
||||
td[class="padding-copy"] {
|
||||
padding: 10px 5% 10px 5% !important;
|
||||
padding: 10px 5% 10px 5% ;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td[class="padding-meta"] {
|
||||
padding: 30px 5% 0px 5% !important;
|
||||
padding: 30px 5% 0px 5% ;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
td[class="no-pad"] {
|
||||
padding: 0 0 20px 0 !important;
|
||||
padding: 0 0 20px 0 ;
|
||||
}
|
||||
|
||||
td[class="no-padding"] {
|
||||
padding: 0 !important;
|
||||
padding: 0 ;
|
||||
}
|
||||
|
||||
td[class="section-padding"] {
|
||||
padding: 50px 15px 50px 15px !important;
|
||||
padding: 50px 15px 50px 15px ;
|
||||
}
|
||||
|
||||
td[class="section-padding-bottom-image"] {
|
||||
padding: 50px 15px 0 15px !important;
|
||||
padding: 50px 15px 0 15px ;
|
||||
}
|
||||
|
||||
td[class="mobile-wrapper"] {
|
||||
padding: 10px 5% 15px 5% !important;
|
||||
padding: 10px 5% 15px 5% ;
|
||||
}
|
||||
|
||||
table[class="mobile-button-container"] {
|
||||
margin: 0 auto;
|
||||
width: 100% !important;
|
||||
width: 100% ;
|
||||
}
|
||||
|
||||
a[class="mobile-button"] {
|
||||
width: 80% !important;
|
||||
padding: 15px !important;
|
||||
border: 0 !important;
|
||||
font-size: 16px !important;
|
||||
width: 80% ;
|
||||
padding: 15px ;
|
||||
border: 0 ;
|
||||
font-size: 16px ;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -37,7 +37,7 @@ router.put('/user/scrub', async (req, res, next) => {
|
|||
if(user.admin) {
|
||||
throw Errors.modifyAdminUser
|
||||
}
|
||||
let userUpdate = await User.update({ description: "Scrambled by Admin: " + Math.random().toString(36).substring(2)}, { where: {
|
||||
let userUpdate = await User.update({ description: "Description was removed by an administrator"}, { where: {
|
||||
username: req.autosan.body.user
|
||||
}})
|
||||
res.status(200)
|
||||
|
@ -74,6 +74,7 @@ router.put('/user/modify', async (req, res, next) => {
|
|||
let user = await User.findOne({ where: {
|
||||
username: req.body.username
|
||||
}})
|
||||
if(!user) throw Errors.accountDoesNotExist
|
||||
if(user.admin) {
|
||||
throw Errors.modifyAdminUser
|
||||
}
|
||||
|
|
111
routes/user.js
111
routes/user.js
|
@ -271,118 +271,55 @@ router.get('/:username/picture', async (req, res, next) => {
|
|||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
router.get('/', async function(req, res) {
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
let sortFields = {
|
||||
createdAt: 'X.id',
|
||||
username: 'X.username',
|
||||
threadCount: 'threadCount',
|
||||
postCount: 'postCount',
|
||||
description: 'description'
|
||||
postCount: 'postCount'
|
||||
};
|
||||
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
|
||||
let havingClause = '';
|
||||
|
||||
if (req.query.role === 'admin') {
|
||||
if(req.query.role === 'admin') {
|
||||
havingClause = 'HAVING Users.admin = true';
|
||||
} else if (req.query.role === 'user') {
|
||||
} else if(req.query.role === 'user') {
|
||||
havingClause = 'HAVING Users.admin = false';
|
||||
} else {
|
||||
havingClause = '';
|
||||
}
|
||||
|
||||
if (req.query.search) {
|
||||
if(req.query.search) {
|
||||
//I.e. if there is not already a HAVING clause
|
||||
if (!havingClause.length) {
|
||||
if(!havingClause.length) {
|
||||
havingClause = 'HAVING ';
|
||||
} else {
|
||||
havingClause += ' AND ';
|
||||
}
|
||||
|
||||
havingClause += 'Users.username LIKE $search';
|
||||
}
|
||||
|
||||
let sql = `
|
||||
SELECT X.username, X.admin, X.level, X.levelProgress, X.bot, X.booster, X.description, X.koins, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount
|
||||
FROM (
|
||||
SELECT Users.*, COUNT(Posts.id) as postCount
|
||||
FROM Users
|
||||
LEFT OUTER JOIN Posts
|
||||
ON Users.id = Posts.UserId
|
||||
GROUP BY Users.id
|
||||
${havingClause}
|
||||
) as X
|
||||
LEFT OUTER JOIN Threads
|
||||
ON X.id = Threads.UserId
|
||||
GROUP BY X.id
|
||||
ORDER BY ${sortFields[req.query.sort] || 'X.id'}
|
||||
LIMIT 9999
|
||||
OFFSET ${offset}
|
||||
let sql = `
|
||||
SELECT X.username, X.admin, X.level, X.levelProgress, X.bot, X.booster, X.description, X.bodyColor, X.headColor, X.leftLegColor, X.rightLegColor, X.leftArmColor, X.rightArmColor, X.hidden, X.system, X.createdAt, X.contributor, X.postCount, COUNT(Threads.id) as threadCount
|
||||
FROM (
|
||||
SELECT Users.*, COUNT(Posts.id) as postCount
|
||||
FROM Users
|
||||
LEFT OUTER JOIN Posts
|
||||
ON Users.id = Posts.UserId
|
||||
GROUP BY Users.id
|
||||
${havingClause}
|
||||
) as X
|
||||
LEFT OUTER JOIN threads
|
||||
ON X.id = Threads.UserId
|
||||
GROUP BY X.id
|
||||
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}
|
||||
LIMIT 2000
|
||||
OFFSET ${offset}
|
||||
`;
|
||||
|
||||
let users = await sequelize.query(sql, {
|
||||
model: User,
|
||||
attributes: {exclude: ['hash', 'email', 'emailVerified', 'koins', 'currency2', 'emailToken', 'passwordResetExpiry', 'passwordResetToken', 'experimentMode', 'developerMode']},
|
||||
bind: {search: req.query.search + '%'}
|
||||
bind: { search: req.query.search + '%' }
|
||||
});
|
||||
|
||||
res.json(users)
|
||||
} catch (e) {
|
||||
}
|
||||
})
|
||||
|
||||
router.all('*', (req, res, next) => {
|
||||
if(req.session.username) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.put('/:username', async (req, res, next) => {
|
||||
try {
|
||||
if(req.session.username !== req.params.username) {
|
||||
throw Errors.requestNotAuthorized
|
||||
}
|
||||
await Ban.ReadOnlyMode(req.session.username)
|
||||
|
||||
if(req.autosan.body.description !== undefined) {
|
||||
let user = await User.update({ description: req.autosan.body.description }, { where: {
|
||||
username: req.session.username
|
||||
}})
|
||||
|
||||
res.json({ success: true })
|
||||
|
||||
} else if(
|
||||
req.body.currentPassword !== undefined &&
|
||||
req.body.newPassword !== undefined
|
||||
) {
|
||||
let user = await User.findOne({
|
||||
where: {
|
||||
username: req.session.username
|
||||
}
|
||||
})
|
||||
|
||||
await user.updatePassword(req.body.currentPassword, req.body.newPassword)
|
||||
res.json({success: true})
|
||||
} else if(
|
||||
req.body.emailCurrentPassword !== undefined &&
|
||||
req.body.newEmail !== undefined
|
||||
) {
|
||||
let user = await User.findOne({where: {
|
||||
username: req.session.username
|
||||
}})
|
||||
|
||||
await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
|
||||
res.json({ success: true })
|
||||
|
||||
} else {
|
||||
res.json({ success: false })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -97,6 +97,58 @@ router.post('/', async (req, res, next) => {
|
|||
res.json(user.toJSON())
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.get('/leaderboard', async (req, res, next) => {
|
||||
try {
|
||||
let sortFields = {
|
||||
createdAt: 'X.id',
|
||||
username: 'X.username',
|
||||
threadCount: 'threadCount',
|
||||
postCount: 'postCount'
|
||||
};
|
||||
let offset = Number.isInteger(+req.query.offset) ? +req.query.offset : 0;
|
||||
let havingClause = '';
|
||||
if(req.query.role === 'admin') {
|
||||
havingClause = 'HAVING Users.admin = true';
|
||||
} else if(req.query.role === 'user') {
|
||||
havingClause = 'HAVING Users.admin = false';
|
||||
} else {
|
||||
havingClause = '';
|
||||
}
|
||||
if(req.query.search) {
|
||||
//I.e. if there is not already a HAVING clause
|
||||
if(!havingClause.length) {
|
||||
havingClause = 'HAVING ';
|
||||
} else {
|
||||
havingClause += ' AND ';
|
||||
}
|
||||
havingClause += 'Users.username LIKE $search';
|
||||
}
|
||||
let sql = `
|
||||
SELECT X.username, X.postCount, COUNT(Threads.id) as threadCount
|
||||
FROM (
|
||||
SELECT Users.*, COUNT(Posts.id) as postCount
|
||||
FROM Users
|
||||
LEFT OUTER JOIN Posts
|
||||
ON Users.id = Posts.UserId
|
||||
GROUP BY Users.id
|
||||
${havingClause}
|
||||
) as X
|
||||
LEFT OUTER JOIN threads
|
||||
ON X.id = Threads.UserId
|
||||
GROUP BY X.id
|
||||
ORDER BY ${sortFields[req.query.sort] || 'X.id'} ${req.query.order === 'asc' ? 'ASC' : 'DESC'}
|
||||
LIMIT 5
|
||||
OFFSET ${offset}
|
||||
`;
|
||||
let users = await sequelize.query(sql, {
|
||||
model: User,
|
||||
bind: { search: req.query.search + '%' }
|
||||
});
|
||||
res.json(users)
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.post('/job-application', async (req, res, next) => {
|
||||
try {
|
||||
let userParams = {
|
||||
|
@ -405,4 +457,84 @@ router.get('/email-verify/:token', async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
router.put('/preferences', async (req, res, next) => {
|
||||
try {
|
||||
if(!req.session.username) {
|
||||
throw Errors.requestNotAuthorized
|
||||
}
|
||||
await Ban.ReadOnlyMode(req.session.username)
|
||||
|
||||
if(req.autosan.body.description !== undefined) {
|
||||
let user = await User.update({ description: req.autosan.body.description }, { where: {
|
||||
username: req.session.username
|
||||
}})
|
||||
|
||||
res.json({ success: true })
|
||||
|
||||
} else if(
|
||||
req.body.currentPassword !== undefined &&
|
||||
req.body.newPassword !== undefined
|
||||
) {
|
||||
let user = await User.findOne({
|
||||
where: {
|
||||
username: req.session.username
|
||||
}
|
||||
})
|
||||
|
||||
await user.updatePassword(req.body.currentPassword, req.body.newPassword)
|
||||
res.json({success: true})
|
||||
} else if(
|
||||
req.body.emailCurrentPassword !== undefined &&
|
||||
req.body.newEmail !== undefined
|
||||
) {
|
||||
let user = await User.findOne({where: {
|
||||
username: req.session.username
|
||||
}})
|
||||
|
||||
await user.updateEmail(req.body.emailCurrentPassword, req.body.newEmail)
|
||||
res.json({ success: true })
|
||||
} else if(
|
||||
req.body.developerMode !== undefined) {
|
||||
let user = await User.update({developerMode: req.autosan.body.developerMode}, {
|
||||
where: {
|
||||
username: req.session.username
|
||||
}
|
||||
})
|
||||
|
||||
res.json({success: true})
|
||||
} else {
|
||||
res.json({ success: false })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.put('/experiments', async (req, res, next) => {
|
||||
try {
|
||||
if(!req.session.username) {
|
||||
throw Errors.requestNotAuthorized
|
||||
}
|
||||
let queryObj = {
|
||||
attributes: {include: ['developerMode']},
|
||||
where: { username: req.session.username }
|
||||
}
|
||||
let user = await User.findOne(queryObj)
|
||||
if(!user.developerMode) {
|
||||
throw Errors.deniedExperiments
|
||||
}
|
||||
if(req.body.theme !== undefined) {
|
||||
{
|
||||
let user = await User.update({theme: req.autosan.body.theme}, {
|
||||
where: {
|
||||
username: req.session.username
|
||||
}
|
||||
})
|
||||
|
||||
res.json({success: true})
|
||||
}
|
||||
} else {
|
||||
res.json({ success: false })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
|
|
Loading…
Reference in New Issue