This commit is contained in:
Troplo 2020-10-08 22:17:15 +11:00
parent 951fe5b493
commit 0b209c1b21
45 changed files with 8984 additions and 645 deletions

View File

@ -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}}

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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",

View File

@ -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

View File

@ -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;
}
}

View File

@ -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>&nbsp;
<b-tag rounded class="is-info" v-if='proxyUser.bot'>BOT</b-tag>&nbsp;
<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>

View File

@ -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 ;
}
}
}

View File

@ -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>

View File

@ -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 {

View File

@ -41,7 +41,7 @@
@at-root #{&}--dark {
span {
background-color: $color__darkgray--primary !important;
background-color: $color__darkgray--primary ;
}
}

View File

@ -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;
}

View File

@ -424,7 +424,7 @@
.notification_button__menu {
width: 100%;
left: unset;
right: unset !important;
right: unset ;
}
}

View File

@ -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>

View File

@ -98,7 +98,7 @@
}
@at-root #{&}__button--selected {
color: $color__blue--darker !important;
color: $color__blue--darker ;
}
@at-root #{&}__option {

View File

@ -95,7 +95,7 @@
transition: transform 0.2s;
}
@at-root #{&}--selected {
color: $color__blue--darker !important;
color: $color__blue--darker ;
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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">&nbsp;SYSTEM&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<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">&nbsp;BOT&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.admin' class="is-danger" label="User is an official Kaverti administrator.">
<b-tag class="is-danger" rounded>&nbsp;ADMIN&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.hidden' class="is-info" label="User is not discoverable in the user list.">
<b-tag rounded>&nbsp;HIDDEN&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.booster' class="is-light" label="User is boosting the Kaverti Discord server.">
<b-tag class="is-light" rounded>&nbsp;BOOSTER&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
</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>

View File

@ -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>&nbsp;HIDDEN&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-tooltip v-if='user && user.booster' class="is-success" label="User is boosting the Kaverti Discord server.">
<b-tag class="is-success" rounded>&nbsp;BOOSTER&nbsp;<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>&nbsp;BOOSTER&nbsp;<i class="fas fa-info-circle"></i></b-tag>
</b-tooltip>
&nbsp;
<b-button v-if="!user.admin" class="is-info is-small" rounded @click='toggleBadgeModal(user)'>

View File

@ -1,3 +1,5 @@
<template>
<h1>Unused</h1>
<main>
</main>
</template>

View File

@ -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;
}
}

View File

@ -407,7 +407,7 @@
.notification_button__menu {
width: 100%;
left: unset;
right: unset !important;
right: unset ;
}
}

View File

@ -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;

View File

@ -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>

View File

@ -1,9 +1,9 @@
<style>
h1 {
font-size: 32px !important;
font-size: 32px ;
}
h2 {
font-size: 24px !important;
font-size: 24px ;
}
</style>
<template>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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% ;
}
}
}

View File

@ -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>

View File

@ -407,7 +407,7 @@ export default {
width: 100%;
> div, input {
width: 100% !important;
width: 100% ;
}
}
}

View File

@ -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>

View File

@ -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 },

View File

@ -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
},

View File

@ -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"

View File

@ -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

View File

@ -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,

View File

@ -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>

View File

@ -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
}

View File

@ -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;

View File

@ -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;