Compare commits
10 Commits
7c7b38e869
...
6fef87b0f1
| Author | SHA1 | Date |
|---|---|---|
|
|
6fef87b0f1 | |
|
|
0d7a03f3e5 | |
|
|
1703e9a541 | |
|
|
ece9569d66 | |
|
|
3bebf5bf51 | |
|
|
398161b03b | |
|
|
1fb7d384fb | |
|
|
77b40817f2 | |
|
|
e371e9725a | |
|
|
816529b0ca |
|
|
@ -33,6 +33,20 @@
|
||||||
:actor="actor"
|
:actor="actor"
|
||||||
class="header-social"
|
class="header-social"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-show="me && isStashed"
|
||||||
|
icon="heart7"
|
||||||
|
class="stash stashed noselect"
|
||||||
|
@click="unstashActor"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
v-show="me && !isStashed"
|
||||||
|
icon="heart8"
|
||||||
|
class="stash unstashed noselect"
|
||||||
|
@click="stashActor"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-inner actor-inner">
|
<div class="content-inner actor-inner">
|
||||||
|
|
@ -54,13 +68,6 @@
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<Expand
|
|
||||||
v-if="bioExpanded"
|
|
||||||
:expanded="bioExpanded"
|
|
||||||
class="expand expand-light"
|
|
||||||
@expand="(state) => bioExpanded = state"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ul class="bio nolist">
|
<ul class="bio nolist">
|
||||||
<li
|
<li
|
||||||
v-if="actor.realName"
|
v-if="actor.realName"
|
||||||
|
|
@ -384,7 +391,7 @@ import Scroll from '../scroll/scroll.vue';
|
||||||
import Gender from './gender.vue';
|
import Gender from './gender.vue';
|
||||||
import Social from './social.vue';
|
import Social from './social.vue';
|
||||||
|
|
||||||
async function fetchActor() {
|
async function fetchActor(scroll = true) {
|
||||||
const { actor, releases, totalCount } = await this.$store.dispatch('fetchActorById', {
|
const { actor, releases, totalCount } = await this.$store.dispatch('fetchActorById', {
|
||||||
actorId: Number(this.$route.params.actorId),
|
actorId: Number(this.$route.params.actorId),
|
||||||
limit: this.limit,
|
limit: this.limit,
|
||||||
|
|
@ -396,11 +403,37 @@ async function fetchActor() {
|
||||||
this.releases = releases;
|
this.releases = releases;
|
||||||
this.totalCount = totalCount;
|
this.totalCount = totalCount;
|
||||||
|
|
||||||
if (this.$refs.filter) {
|
if (this.$refs.filter && scroll) {
|
||||||
this.$refs.filter.$el.scrollIntoView();
|
this.$refs.filter.$el.scrollIntoView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function stashActor() {
|
||||||
|
this.$store.dispatch('stashActor', {
|
||||||
|
actorId: this.actor.id,
|
||||||
|
stashId: this.$store.getters.favorites.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchActor(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unstashActor() {
|
||||||
|
this.$store.dispatch('unstashActor', {
|
||||||
|
actorId: this.actor.id,
|
||||||
|
stashId: this.$store.getters.favorites.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.fetchActor(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
function me() {
|
||||||
|
return this.$store.state.auth.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStashed() {
|
||||||
|
return this.actor.stashes?.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function sfw() {
|
function sfw() {
|
||||||
return this.$store.state.ui.sfw;
|
return this.$store.state.ui.sfw;
|
||||||
}
|
}
|
||||||
|
|
@ -447,6 +480,8 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isStashed,
|
||||||
|
me,
|
||||||
sfw,
|
sfw,
|
||||||
showAlbum,
|
showAlbum,
|
||||||
},
|
},
|
||||||
|
|
@ -457,6 +492,8 @@ export default {
|
||||||
mounted,
|
mounted,
|
||||||
methods: {
|
methods: {
|
||||||
fetchActor,
|
fetchActor,
|
||||||
|
stashActor,
|
||||||
|
unstashActor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -477,11 +514,10 @@ export default {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: var(--lighten-extreme);
|
color: var(--lighten-extreme);
|
||||||
background: var(--profile);
|
background: var(--profile);
|
||||||
padding: .5rem 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-name {
|
.header-name {
|
||||||
padding: 0;
|
padding: .5rem 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
@ -491,7 +527,7 @@ export default {
|
||||||
.header-gender {
|
.header-gender {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin: 0 0 0 .5rem;
|
margin: 0 0 0 .5rem;
|
||||||
transform: translate(0, .1rem);
|
transform: translate(0, .125rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-social {
|
.header-social {
|
||||||
|
|
@ -731,6 +767,22 @@ export default {
|
||||||
border-bottom: solid 1px var(--shadow-hint);
|
border-bottom: solid 1px var(--shadow-hint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stash.icon {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
fill: var(--lighten);
|
||||||
|
|
||||||
|
&.stashed {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
fill: var(--primary);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: $breakpoint4) {
|
@media(max-width: $breakpoint4) {
|
||||||
.descriptions-container {
|
.descriptions-container {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
@ -795,8 +847,16 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-name {
|
.header-name {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
|
padding: .5rem .5rem .5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.stash.icon {
|
||||||
|
width: 1.25rem;
|
||||||
|
height: 1.25rem;
|
||||||
|
padding: 0 1rem 0 .25rem;
|
||||||
|
transform: translate(0, -.1rem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -81,8 +81,8 @@
|
||||||
@input="(range) => updateValue('age', range, false)"
|
@input="(range) => updateValue('age', range, false)"
|
||||||
@change="(range) => updateValue('age', range, true)"
|
@change="(range) => updateValue('age', range, true)"
|
||||||
>
|
>
|
||||||
<template v-slot:start><Icon icon="flower" /></template>
|
<template v-slot:start><Icon icon="leaf" /></template>
|
||||||
<template v-slot:end><Icon icon="pipe" /></template>
|
<template v-slot:end><Icon icon="tree3" /></template>
|
||||||
</RangeFilter>
|
</RangeFilter>
|
||||||
|
|
||||||
<div class="filter-section">
|
<div class="filter-section">
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="getPath(item, 'thumbnail', { local })"
|
:src="getPath(item, 'thumbnail', { local })"
|
||||||
|
:style="{ 'background-image': `url('${getPath(item, 'lazy', { local })}')` }"
|
||||||
|
:width="item.width"
|
||||||
|
:height="item.height"
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="item image"
|
class="item image"
|
||||||
|
|
@ -183,6 +186,9 @@ export default {
|
||||||
.item {
|
.item {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-comment {
|
.item-comment {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
<template>
|
||||||
|
<form
|
||||||
|
class="login"
|
||||||
|
@submit.prevent="login"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="error"
|
||||||
|
class="feedback error"
|
||||||
|
>{{ error }}</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="success"
|
||||||
|
class="feedback success"
|
||||||
|
>Login successful, redirecting</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<input
|
||||||
|
v-model="username"
|
||||||
|
placeholder="Username or e-mail"
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
v-model="password"
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
class="input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="button button-primary"
|
||||||
|
>Log in</button>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'signup', query: { ref: $route.query.ref } }"
|
||||||
|
class="link link-external signup"
|
||||||
|
>Sign up</router-link>
|
||||||
|
</template>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function login() {
|
||||||
|
this.error = null;
|
||||||
|
this.success = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const user = await this.$store.dispatch('login', {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.success = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.replace(this.$route.query.ref || { name: 'user', params: { username: user.username } });
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mounted() {
|
||||||
|
if (!this.$store.state.auth.enabled) {
|
||||||
|
this.$router.replace({ name: 'not-found' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: null,
|
||||||
|
password: null,
|
||||||
|
success: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted,
|
||||||
|
methods: {
|
||||||
|
login,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.login {
|
||||||
|
width: 20rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback {
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: var(--shadow-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 0 0 .25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.signup {
|
||||||
|
padding: .5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
<template>
|
||||||
|
<form
|
||||||
|
class="signup"
|
||||||
|
@submit.prevent="signup"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="error"
|
||||||
|
class="feedback error"
|
||||||
|
>{{ error }}</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="success"
|
||||||
|
class="feedback success"
|
||||||
|
>Signup successful, redirecting</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<input
|
||||||
|
v-model="username"
|
||||||
|
placeholder="Username"
|
||||||
|
type="text"
|
||||||
|
class="input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
v-model="email"
|
||||||
|
placeholder="E-mail"
|
||||||
|
type="email"
|
||||||
|
class="input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<input
|
||||||
|
v-model="password"
|
||||||
|
placeholder="Password"
|
||||||
|
type="password"
|
||||||
|
class="input"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="button button-primary"
|
||||||
|
>Sign up</button>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
:to="{ name: 'login', query: { ref: $route.query.ref } }"
|
||||||
|
class="link link-external login"
|
||||||
|
>Log in</router-link>
|
||||||
|
</template>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function signup() {
|
||||||
|
this.error = null;
|
||||||
|
this.success = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('signup', {
|
||||||
|
username: this.username,
|
||||||
|
email: this.email,
|
||||||
|
password: this.password,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.success = true;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$router.replace(this.$route.query.ref || { name: 'home' });
|
||||||
|
}, 1000);
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mounted() {
|
||||||
|
if (!this.$store.state.auth.enabled) {
|
||||||
|
this.$router.replace({ name: 'not-found' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: null,
|
||||||
|
email: null,
|
||||||
|
password: null,
|
||||||
|
success: false,
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted,
|
||||||
|
methods: {
|
||||||
|
signup,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.signup {
|
||||||
|
width: 20rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedback {
|
||||||
|
padding: 1rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: var(--shadow-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
margin: 0 0 .25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login {
|
||||||
|
padding: .5rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -93,67 +93,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-slot:tooltip>
|
<template v-slot:tooltip>
|
||||||
<div class="menu">
|
<Menu />
|
||||||
<ul class="menu-items noselect">
|
|
||||||
<li
|
|
||||||
class="menu-item disabled"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<Icon icon="enter2" />Sign in
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
v-show="!sfw"
|
|
||||||
class="menu-item"
|
|
||||||
@click.stop="setSfw(true)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="flower"
|
|
||||||
class="toggle noselect"
|
|
||||||
/>Safe mode
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
v-show="sfw"
|
|
||||||
class="menu-item"
|
|
||||||
@click.stop="setSfw(false)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="fire"
|
|
||||||
class="toggle noselect"
|
|
||||||
/>Filth mode
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
v-show="theme === 'light'"
|
|
||||||
class="menu-item"
|
|
||||||
@click.stop="setTheme('dark')"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="moon"
|
|
||||||
class="toggle noselect"
|
|
||||||
/>Dark theme
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
v-show="theme === 'dark'"
|
|
||||||
class="menu-item"
|
|
||||||
@click.stop="setTheme('light')"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
icon="sun"
|
|
||||||
class="toggle noselect"
|
|
||||||
/>Light theme
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li
|
|
||||||
class="menu-item"
|
|
||||||
@click="$emit('showFilters', true)"
|
|
||||||
>
|
|
||||||
<Icon icon="filter" />Filters
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
|
|
@ -186,30 +126,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex';
|
import Menu from './menu.vue';
|
||||||
|
|
||||||
import Search from './search.vue';
|
import Search from './search.vue';
|
||||||
|
|
||||||
import logo from '../../img/logo.svg';
|
import logo from '../../img/logo.svg';
|
||||||
|
|
||||||
function sfw(state) {
|
|
||||||
return state.ui.sfw;
|
|
||||||
}
|
|
||||||
|
|
||||||
function theme(state) {
|
|
||||||
return state.ui.theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setTheme(newTheme) {
|
|
||||||
this.$store.dispatch('setTheme', newTheme);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setSfw(enabled) {
|
|
||||||
this.$store.dispatch('setSfw', enabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
Menu,
|
||||||
Search,
|
Search,
|
||||||
},
|
},
|
||||||
emits: ['toggleSidebar', 'showFilters'],
|
emits: ['toggleSidebar', 'showFilters'],
|
||||||
|
|
@ -220,16 +144,6 @@ export default {
|
||||||
showFilters: false,
|
showFilters: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
...mapState({
|
|
||||||
sfw,
|
|
||||||
theme,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setSfw,
|
|
||||||
setTheme,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -394,40 +308,6 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-items {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-item {
|
|
||||||
display: flex;
|
|
||||||
padding: .75rem 1rem .75rem .75rem;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--darken);
|
|
||||||
margin: 0 1rem 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
color: var(--darken-weak);
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--darken-weak);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--primary);
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
fill: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-compact {
|
.search-compact {
|
||||||
display: none;
|
display: none;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
<template>
|
||||||
|
<div class="menu">
|
||||||
|
<ul class="menu-items noselect">
|
||||||
|
<router-link
|
||||||
|
v-if="auth && me"
|
||||||
|
:to="{ name: 'user', params: { username: me.username } }"
|
||||||
|
class="menu-username"
|
||||||
|
>{{ me.username }}</router-link>
|
||||||
|
|
||||||
|
<router-link
|
||||||
|
v-else-if="auth"
|
||||||
|
:to="{ name: 'login', query: { ref: $route.path } }"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop
|
||||||
|
>
|
||||||
|
<Icon icon="enter2" />Log in
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-if="auth && me"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop="$store.dispatch('logout')"
|
||||||
|
>
|
||||||
|
<Icon icon="enter2" />Log out
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-show="!sfw"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop="setSfw(true)"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="flower"
|
||||||
|
class="toggle noselect"
|
||||||
|
/>Safe mode
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-show="sfw"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop="setSfw(false)"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="fire"
|
||||||
|
class="toggle noselect"
|
||||||
|
/>Filth mode
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-show="theme === 'light'"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop="setTheme('dark')"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="moon"
|
||||||
|
class="toggle noselect"
|
||||||
|
/>Dark theme
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-show="theme === 'dark'"
|
||||||
|
class="menu-item"
|
||||||
|
@click.stop="setTheme('light')"
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
icon="sun"
|
||||||
|
class="toggle noselect"
|
||||||
|
/>Light theme
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class="menu-item"
|
||||||
|
@click="$emit('showFilters', true)"
|
||||||
|
>
|
||||||
|
<Icon icon="filter" />Filters
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'vuex';
|
||||||
|
|
||||||
|
function sfw(state) {
|
||||||
|
return state.ui.sfw;
|
||||||
|
}
|
||||||
|
|
||||||
|
function theme(state) {
|
||||||
|
return state.ui.theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth(state) {
|
||||||
|
return state.auth.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
function me(state) {
|
||||||
|
return state.auth.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTheme(newTheme) {
|
||||||
|
this.$store.dispatch('setTheme', newTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSfw(enabled) {
|
||||||
|
this.$store.dispatch('setSfw', enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
auth,
|
||||||
|
sfw,
|
||||||
|
theme,
|
||||||
|
me,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setSfw,
|
||||||
|
setTheme,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'breakpoints';
|
||||||
|
|
||||||
|
.menu-items {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
display: flex;
|
||||||
|
padding: .75rem 1rem .75rem .75rem;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--darken);
|
||||||
|
margin: 0 1rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
color: var(--darken-weak);
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--darken-weak);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--primary);
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-username {
|
||||||
|
display: block;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--shadow-strong);
|
||||||
|
font-size: .9rem;
|
||||||
|
padding: .75rem 1rem;
|
||||||
|
border-bottom: solid 1px var(--shadow-hint);
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -21,8 +21,10 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
async function search() {
|
async function search() {
|
||||||
this.$router.push({ name: 'search', query: { q: this.query } });
|
if (this.query) {
|
||||||
this.$emit('search');
|
this.$router.push({ name: 'search', query: { q: this.query } });
|
||||||
|
this.$emit('search');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function searching(to) {
|
function searching(to) {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@
|
||||||
v-else-if="release.teaser && /^image\//.test(release.teaser.mime)"
|
v-else-if="release.teaser && /^image\//.test(release.teaser.mime)"
|
||||||
:src="getPath(release.teaser, 'thumbnail', { original: true })"
|
:src="getPath(release.teaser, 'thumbnail', { original: true })"
|
||||||
:alt="release.title"
|
:alt="release.title"
|
||||||
|
:width="photo.width"
|
||||||
|
:height="photo.height"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="item trailer"
|
class="item trailer"
|
||||||
>
|
>
|
||||||
|
|
@ -66,6 +68,8 @@
|
||||||
<img
|
<img
|
||||||
:src="getPath(cover, 'thumbnail')"
|
:src="getPath(cover, 'thumbnail')"
|
||||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||||
|
:width="photo.width"
|
||||||
|
:height="photo.height"
|
||||||
class="item cover"
|
class="item cover"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@load="$emit('load', $event)"
|
@load="$emit('load', $event)"
|
||||||
|
|
@ -87,8 +91,10 @@
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="getPath(photo, 'thumbnail')"
|
:src="getPath(photo, 'thumbnail')"
|
||||||
:style="{ 'background-image': getPath(photo, 'lazy') }"
|
:style="{ 'background-image': `url('${getPath(photo, 'lazy')}` }"
|
||||||
:alt="`Photo ${photo.index + 1}`"
|
:alt="`Photo ${photo.index + 1}`"
|
||||||
|
:width="photo.width"
|
||||||
|
:height="photo.height"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="item"
|
class="item"
|
||||||
@load="$emit('load', $event)"
|
@load="$emit('load', $event)"
|
||||||
|
|
@ -273,6 +279,7 @@ export default {
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
width: auto;
|
||||||
height: 18rem;
|
height: 18rem;
|
||||||
box-shadow: 0 0 3px var(--shadow-weak);
|
box-shadow: 0 0 3px var(--shadow-weak);
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<ul class="chapters nolist">
|
<div
|
||||||
|
v-if="timeline"
|
||||||
|
class="timeline"
|
||||||
|
>
|
||||||
|
<ul class="timeline-items nolist">
|
||||||
|
<li
|
||||||
|
v-for="chapter in timeline"
|
||||||
|
:key="`chapter-${chapter.id}`"
|
||||||
|
:style="{ left: `${(chapter.time / duration) * 100}%` }"
|
||||||
|
:title="formatDuration(chapter.time)"
|
||||||
|
class="timeline-item"
|
||||||
|
><router-link
|
||||||
|
:to="`/tag/${chapter.tags[0].slug}`"
|
||||||
|
class="link"
|
||||||
|
>{{ chapter.tags[0]?.name || ' ' }}</router-link></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-else
|
||||||
|
class="chapters nolist"
|
||||||
|
>
|
||||||
<li
|
<li
|
||||||
v-for="chapter in chapters"
|
v-for="chapter in chapters"
|
||||||
:key="`chapter-${chapter.id}`"
|
:key="`chapter-${chapter.id}`"
|
||||||
|
|
@ -55,6 +76,14 @@
|
||||||
<script>
|
<script>
|
||||||
import Tags from './tags.vue';
|
import Tags from './tags.vue';
|
||||||
|
|
||||||
|
function timeline() {
|
||||||
|
if (this.chapters.every(chapter => chapter.time)) {
|
||||||
|
return this.chapters.filter(chapter => chapter.tags?.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Tags,
|
Tags,
|
||||||
|
|
@ -65,6 +94,16 @@ export default {
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
const lastChapter = this.chapters[this.chapters.length - 1];
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration: lastChapter.time + lastChapter.duration,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
timeline,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
@ -153,6 +192,41 @@ export default {
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timeline-items {
|
||||||
|
position: relative;
|
||||||
|
height: 5rem;
|
||||||
|
border-bottom: solid 1px var(--shadow-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -.25rem;
|
||||||
|
padding: .1rem .5rem;
|
||||||
|
border-radius: .6rem;
|
||||||
|
color: var(--primary);
|
||||||
|
background: var(--background);
|
||||||
|
transform: rotate(-60deg);
|
||||||
|
transform-origin: 0 50%;
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak);
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: '';
|
||||||
|
display: inline-block;
|
||||||
|
width: 1rem;
|
||||||
|
height: 2px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(-1rem + 1px);
|
||||||
|
margin: .3rem .5rem 0 0;
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: $breakpoint-micro) {
|
@media(max-width: $breakpoint-micro) {
|
||||||
.chapters {
|
.chapters {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,10 @@
|
||||||
>{{ release.entity.name }}</h3>
|
>{{ release.entity.name }}</h3>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<span class="row">
|
<span
|
||||||
|
v-if="release.actors?.length > 0"
|
||||||
|
class="row"
|
||||||
|
>
|
||||||
<ul
|
<ul
|
||||||
class="actors nolist"
|
class="actors nolist"
|
||||||
:title="release.actors.map(actor => actor.name).join(', ')"
|
:title="release.actors.map(actor => actor.name).join(', ')"
|
||||||
|
|
@ -106,7 +109,7 @@
|
||||||
>{{ release.shootId }}</span>
|
>{{ release.shootId }}</span>
|
||||||
|
|
||||||
<ul
|
<ul
|
||||||
v-if="release.tags.length > 0"
|
v-if="release.tags?.length > 0"
|
||||||
:title="release.tags.map(tag => tag.name).join(', ')"
|
:title="release.tags.map(tag => tag.name).join(', ')"
|
||||||
class="tags nolist"
|
class="tags nolist"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
:class="{ new: release.isNew }"
|
:class="{ new: release.isNew }"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="release.entity.type !== 'network' && !release.entity.independent && release.entity.parent"
|
v-if="release.entity && release.entity.type !== 'network' && !release.entity.independent && release.entity.parent"
|
||||||
class="site"
|
class="site"
|
||||||
>
|
>
|
||||||
<router-link
|
<router-link
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
v-else
|
v-else-if="release.entity"
|
||||||
:to="`/${release.entity.type}/${release.entity.slug}`"
|
:to="`/${release.entity.type}/${release.entity.slug}`"
|
||||||
class="site site-link"
|
class="site site-link"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="user"
|
||||||
|
class="user"
|
||||||
|
>
|
||||||
|
<div class="header">
|
||||||
|
<h2 class="username">{{ user.username }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section
|
||||||
|
v-if="user.stashes?.length > 0"
|
||||||
|
class="section"
|
||||||
|
>
|
||||||
|
<h3 class="heading">Stashes</h3>
|
||||||
|
|
||||||
|
<ul class="stashes nolist">
|
||||||
|
<li
|
||||||
|
v-for="stash in user.stashes"
|
||||||
|
:key="stash.id"
|
||||||
|
class="stash"
|
||||||
|
>
|
||||||
|
<h4 class="stash-name">{{ stash.name }}</h4>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-if="stash.scenes?.length > 0"
|
||||||
|
class="stash-section stash-scenes nolist"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="item in stash.scenes"
|
||||||
|
:key="item.id"
|
||||||
|
><Scene :release="item.scene" /></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul
|
||||||
|
v-if="stash.actors?.length > 0"
|
||||||
|
class="stash-section stash-actors nolist"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
v-for="item in stash.actors"
|
||||||
|
:key="item.id"
|
||||||
|
><Actor :actor="item.actor" /></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Actor from '../actors/tile.vue';
|
||||||
|
import Scene from '../releases/scene-tile.vue';
|
||||||
|
|
||||||
|
async function mounted() {
|
||||||
|
this.user = await this.$store.dispatch('fetchUser', this.$route.params.username);
|
||||||
|
this.pageTitle = this.user?.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Actor,
|
||||||
|
Scene,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: this.$route.params.username === this.$store.state.auth.user?.username
|
||||||
|
? this.$store.state.auth.user
|
||||||
|
: null,
|
||||||
|
pageTitle: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.header {
|
||||||
|
padding: .5rem 1rem;
|
||||||
|
background: var(--profile);
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash {
|
||||||
|
width: 100%;
|
||||||
|
background: var(--background);
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
box-shadow: 0 0 3px var(--shadow-weak);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-name {
|
||||||
|
color: var(--shadow-strong);
|
||||||
|
padding: 1rem .5rem 0 .5rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-section {
|
||||||
|
padding: 1rem .5rem;
|
||||||
|
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: solid 1px var(--shadow-hint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-actors,
|
||||||
|
.stash-scenes {
|
||||||
|
display: grid;
|
||||||
|
flex-grow: 1;
|
||||||
|
grid-gap: .5rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-actors {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.stash-scenes {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,10 +1,15 @@
|
||||||
.input {
|
.input {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
border: solid 1px var(--shadow-weak);
|
border: solid 1px var(--shadow-hint);
|
||||||
color: var(--shadow-strong);
|
color: var(--shadow-strong);
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: solid 1px var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
&::-webkit-calendar-picker-indicator {
|
&::-webkit-calendar-picker-indicator {
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
|
|
@ -20,6 +25,35 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: .5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-primary {
|
||||||
|
color: var(--text-light);
|
||||||
|
background: var(--primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--primary-strong);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-secondary {
|
||||||
|
color: var(--primary);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--highlight-strong);
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.album-toggle {
|
.album-toggle {
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,14 @@ $breakpoint3: 1200px;
|
||||||
$breakpoint4: 1500px;
|
$breakpoint4: 1500px;
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
/* --primary: #ff886c; */
|
/*
|
||||||
--primary: #ff6c88;
|
--primary: #ff6c88;
|
||||||
--primary-strong: #ff4166;
|
--primary-strong: #ff4166;
|
||||||
--primary-faded: #ffdfee;
|
--primary-faded: #ffdfee;
|
||||||
|
*/
|
||||||
|
--primary: #f28;
|
||||||
|
--primary-strong: #f90071;
|
||||||
|
--primary-faded: #ffcce4;
|
||||||
|
|
||||||
--text-dark: #222;
|
--text-dark: #222;
|
||||||
--text-light: #fff;
|
--text-light: #fff;
|
||||||
|
|
@ -36,6 +40,7 @@ $breakpoint4: 1500px;
|
||||||
--female: #f0a;
|
--female: #f0a;
|
||||||
|
|
||||||
--alert: #f00;
|
--alert: #f00;
|
||||||
|
--error: #f00;
|
||||||
--warn: #fa0;
|
--warn: #fa0;
|
||||||
--success: #5c2;
|
--success: #5c2;
|
||||||
|
|
||||||
|
|
@ -58,6 +63,7 @@ $breakpoint4: 1500px;
|
||||||
--tile: #2a2a2a;
|
--tile: #2a2a2a;
|
||||||
|
|
||||||
--link: #dd6688;
|
--link: #dd6688;
|
||||||
|
--link-external: #48f;
|
||||||
--empty: #333;
|
--empty: #333;
|
||||||
|
|
||||||
--crease: #eaeaea;
|
--crease: #eaeaea;
|
||||||
|
|
|
||||||
|
|
@ -44,3 +44,12 @@ body {
|
||||||
fill: var(--primary);
|
fill: var(--primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
color: var(--link);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link-external {
|
||||||
|
color: var(--link-external);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>leaf2</title>
|
||||||
|
<path d="M15.802 2.102c-1.73-1.311-4.393-2.094-7.124-2.094-3.377 0-6.129 1.179-7.549 3.235-0.667 0.965-1.036 2.109-1.097 3.398-0.065 1.375 0.225 2.925 0.859 4.606-0.586 1.428-0.904 2.898-0.904 4.254 0 0.276 0.224 0.5 0.5 0.5s0.5-0.224 0.5-0.5c0-1.198 0.293-2.535 0.818-3.835 1.472 0.272 2.712 0.405 3.776 0.405 1.839 0 3.146-0.398 4.115-1.252 0.868-0.765 1.347-1.794 1.854-2.882 0.774-1.663 1.651-3.547 4.198-5.002 0.146-0.083 0.24-0.234 0.251-0.402s-0.063-0.329-0.197-0.431zM10.644 7.515c-0.481 1.034-0.897 1.927-1.608 2.554-0.776 0.684-1.873 1.002-3.454 1.002-0.945 0-2.047-0.113-3.351-0.345 0.238-0.476 0.507-0.941 0.804-1.386 0.692-1.036 1.505-1.931 2.417-2.661 0.984-0.788 2.059-1.36 3.193-1.7 0.264-0.079 0.415-0.358 0.335-0.623s-0.358-0.415-0.623-0.335c-1.257 0.377-2.445 1.009-3.53 1.878-0.991 0.794-1.874 1.765-2.623 2.886-0.252 0.378-0.485 0.767-0.698 1.163-0.36-1.185-0.52-2.279-0.474-3.261 0.052-1.099 0.361-2.067 0.921-2.876 0.636-0.92 1.583-1.633 2.816-2.119 1.134-0.447 2.487-0.684 3.911-0.684 2.172 0 4.357 0.555 5.9 1.475-2.314 1.551-3.206 3.467-3.935 5.032z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>tree2</title>
|
||||||
|
<path d="M13.887 13.182l-2.387-3.182h1c0 0 0.001 0 0.001 0 0.276 0 0.5-0.224 0.5-0.5 0-0.121-0.043-0.231-0.114-0.318l-2.387-3.182h1c0.192 0 0.367-0.11 0.451-0.283s0.060-0.379-0.060-0.529l-4-5c-0.095-0.119-0.239-0.188-0.39-0.188s-0.296 0.069-0.39 0.188l-4 5c-0.12 0.15-0.143 0.356-0.060 0.529s0.258 0.283 0.451 0.283h1l-2.4 3.2c-0.114 0.152-0.132 0.354-0.047 0.524s0.258 0.276 0.447 0.276h1l-2.4 3.2c-0.114 0.152-0.132 0.354-0.047 0.524s0.258 0.276 0.447 0.276h4.5v1.5c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-1.5h4.5c0 0 0 0 0.001 0 0.276 0 0.5-0.224 0.5-0.5 0-0.121-0.043-0.231-0.114-0.318zM8 15h-1v-1h1v1zM2.5 13l2.4-3.2c0.114-0.152 0.132-0.354 0.047-0.524s-0.258-0.276-0.447-0.276h-1l2.4-3.2c0.114-0.152 0.132-0.354 0.047-0.524s-0.258-0.276-0.447-0.276h-0.96l2.96-3.7 2.96 3.7h-0.96c-0.189 0-0.363 0.107-0.447 0.276s-0.066 0.372 0.047 0.524l2.4 3.2h-1c-0.189 0-0.363 0.107-0.447 0.276s-0.066 0.372 0.047 0.524l2.4 3.2h-10z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>tree3</title>
|
||||||
|
<path d="M11.852 3.354c-0.508-1.928-2.266-3.354-4.352-3.354s-3.844 1.426-4.352 3.354c-1.891 0.754-3.148 2.596-3.148 4.646 0 2.757 2.243 5 5 5 0.34 0 0.674-0.035 1-0.101v2.601c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-2.601c0.327 0.066 0.661 0.101 1 0.101 2.757 0 5-2.243 5-5 0-2.060-1.254-3.892-3.148-4.646z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 489 B |
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>tree4</title>
|
||||||
|
<path d="M11.852 3.354c-0.508-1.928-2.266-3.354-4.352-3.354s-3.844 1.426-4.352 3.354c-1.891 0.754-3.148 2.596-3.148 4.646 0 2.757 2.243 5 5 5h1v2.5c0 0.276 0.224 0.5 0.5 0.5h2c0.276 0 0.5-0.224 0.5-0.5v-2.5h1c2.757 0 5-2.243 5-5 0-2.060-1.254-3.892-3.148-4.646zM8 15h-1v-2h1v2zM10 12h-5c-2.206 0-4-1.794-4-4 0-1.444 0.781-2.76 2.001-3.465 0.003 0.369 0.050 0.735 0.141 1.090 0.058 0.226 0.261 0.375 0.484 0.375 0.041 0 0.083-0.005 0.125-0.016 0.267-0.069 0.428-0.341 0.36-0.609-0.073-0.284-0.11-0.579-0.11-0.875 0-1.93 1.57-3.5 3.5-3.5s3.5 1.57 3.5 3.5c0 0.297-0.037 0.591-0.11 0.875-0.069 0.267 0.092 0.54 0.36 0.609s0.54-0.092 0.609-0.36c0.091-0.355 0.139-0.722 0.141-1.091 1.222 0.704 2.001 2.014 2.001 3.466 0 2.206-1.794 4-4 4z"></path>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 901 B |
|
|
@ -24,6 +24,8 @@ function initActorActions(store, router) {
|
||||||
const { actor } = await graphql(`
|
const { actor } = await graphql(`
|
||||||
query Actor(
|
query Actor(
|
||||||
$actorId: Int!
|
$actorId: Int!
|
||||||
|
$userId: Int,
|
||||||
|
$hasAuth: Boolean!,
|
||||||
$limit:Int = 10,
|
$limit:Int = 10,
|
||||||
$offset:Int = 0,
|
$offset:Int = 0,
|
||||||
$after:Datetime = "1900-01-01",
|
$after:Datetime = "1900-01-01",
|
||||||
|
|
@ -236,6 +238,21 @@ function initActorActions(store, router) {
|
||||||
}
|
}
|
||||||
totalCount
|
totalCount
|
||||||
}
|
}
|
||||||
|
stashes: stashesActors(
|
||||||
|
filter: {
|
||||||
|
stash: {
|
||||||
|
userId: {
|
||||||
|
equalTo: $userId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) @include(if: $hasAuth) {
|
||||||
|
stash {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`, {
|
`, {
|
||||||
|
|
@ -253,6 +270,8 @@ function initActorActions(store, router) {
|
||||||
includedEntities: getIncludedEntities(router),
|
includedEntities: getIncludedEntities(router),
|
||||||
includedActors: getIncludedActors(router),
|
includedActors: getIncludedActors(router),
|
||||||
mode,
|
mode,
|
||||||
|
hasAuth: !!store.state.auth.user,
|
||||||
|
userId: store.state.auth.user?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!actor) {
|
if (!actor) {
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,16 @@ async function get(endpoint, query = {}) {
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
const contentTypes = res.headers.get('content-type');
|
||||||
|
|
||||||
|
if (res.ok && contentTypes?.includes('application/json')) {
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const errorMsg = await res.text();
|
const errorMsg = await res.text();
|
||||||
|
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
|
|
@ -30,10 +36,32 @@ async function post(endpoint, data) {
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res.ok) {
|
const contentTypes = res.headers.get('content-type');
|
||||||
|
|
||||||
|
if (res.ok && contentTypes?.includes('application/json')) {
|
||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMsg = await res.text();
|
||||||
|
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function del(endpoint) {
|
||||||
|
const res = await fetch(`${config.api.url}${endpoint}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const errorMsg = await res.text();
|
const errorMsg = await res.text();
|
||||||
|
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
|
|
@ -67,5 +95,6 @@ async function graphql(query, variables = null) {
|
||||||
export {
|
export {
|
||||||
get,
|
get,
|
||||||
post,
|
post,
|
||||||
|
del,
|
||||||
graphql,
|
graphql,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,47 @@
|
||||||
function initAuthActions(_store, _router) {}
|
import { get, post, del } from '../api';
|
||||||
|
|
||||||
|
function initAuthActions(_store, _router) {
|
||||||
|
async function fetchMe({ commit }) {
|
||||||
|
try {
|
||||||
|
const user = await get('/session');
|
||||||
|
|
||||||
|
commit('setUser', user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
} catch (error) {
|
||||||
|
// continue as guest
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login({ commit }, credentials) {
|
||||||
|
const user = await post('/session', credentials);
|
||||||
|
|
||||||
|
commit('setUser', user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function signup({ commit }, credentials) {
|
||||||
|
const user = await post('/users', credentials);
|
||||||
|
|
||||||
|
commit('setUser', user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout({ commit }) {
|
||||||
|
await del('/session');
|
||||||
|
|
||||||
|
commit('setUser', null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchMe,
|
||||||
|
login,
|
||||||
|
logout,
|
||||||
|
signup,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default initAuthActions;
|
export default initAuthActions;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import state from './state';
|
import state from './state';
|
||||||
import mutations from './mutations';
|
import mutations from './mutations';
|
||||||
|
import getters from './getters';
|
||||||
import actions from './actions';
|
import actions from './actions';
|
||||||
|
|
||||||
function initAuthStore(store, router) {
|
function initAuthStore(store, router) {
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
mutations,
|
mutations,
|
||||||
|
getters,
|
||||||
actions: actions(store, router),
|
actions: actions(store, router),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
function favoritesStash(state) {
|
||||||
|
return state.user.stashes.find(stash => stash.slug === 'favorites');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
favoritesStash,
|
||||||
|
favorites: favoritesStash,
|
||||||
|
};
|
||||||
|
|
@ -1 +1,7 @@
|
||||||
export default {};
|
function setUser(state, user) {
|
||||||
|
state.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
setUser,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
async function initAuthObserver(store, _router) {
|
||||||
|
await store.dispatch('fetchMe');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initAuthObserver;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
export default {
|
export default {
|
||||||
authenticated: false,
|
enabled: window.env.auth,
|
||||||
user: null,
|
user: null,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,8 @@ function curateActor(actor, release) {
|
||||||
curatedActor.aliasFor = curateActor(curatedActor.aliasFor);
|
curatedActor.aliasFor = curateActor(curatedActor.aliasFor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
curatedActor.stashes = actor.stashes?.map(stash => stash.stash || stash) || [];
|
||||||
|
|
||||||
return curatedActor;
|
return curatedActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,9 +127,41 @@ function curateTag(tag) {
|
||||||
return curatedTag;
|
return curatedTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function curateStash(stash) {
|
||||||
|
const curatedStash = stash;
|
||||||
|
|
||||||
|
if (stash.scenes) {
|
||||||
|
curatedStash.scenes = stash.scenes.map(item => ({
|
||||||
|
...item,
|
||||||
|
scene: curateRelease(item.scene),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stash.actors) {
|
||||||
|
curatedStash.actors = stash.actors.map(item => ({
|
||||||
|
...item,
|
||||||
|
actor: curateActor(item.actor),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return curatedStash;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateUser(user) {
|
||||||
|
const curatedUser = user;
|
||||||
|
|
||||||
|
if (user.stashes) {
|
||||||
|
curatedUser.stashes = user.stashes.map(stash => curateStash(stash));
|
||||||
|
}
|
||||||
|
|
||||||
|
return curatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
curateActor,
|
curateActor,
|
||||||
curateEntity,
|
curateEntity,
|
||||||
curateRelease,
|
curateRelease,
|
||||||
curateTag,
|
curateTag,
|
||||||
|
curateStash,
|
||||||
|
curateUser,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ const releasePosterFragment = `
|
||||||
index
|
index
|
||||||
path
|
path
|
||||||
thumbnail
|
thumbnail
|
||||||
|
width
|
||||||
|
height
|
||||||
lazy
|
lazy
|
||||||
isS3
|
isS3
|
||||||
comment
|
comment
|
||||||
|
|
@ -129,6 +131,8 @@ const releaseCoversFragment = `
|
||||||
index
|
index
|
||||||
path
|
path
|
||||||
thumbnail
|
thumbnail
|
||||||
|
width
|
||||||
|
height
|
||||||
lazy
|
lazy
|
||||||
isS3
|
isS3
|
||||||
comment
|
comment
|
||||||
|
|
@ -150,6 +154,8 @@ const releasePhotosFragment = `
|
||||||
index
|
index
|
||||||
path
|
path
|
||||||
thumbnail
|
thumbnail
|
||||||
|
width
|
||||||
|
height
|
||||||
lazy
|
lazy
|
||||||
isS3
|
isS3
|
||||||
comment
|
comment
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import mitt from 'mitt';
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import initStore from './store';
|
import initStore from './store';
|
||||||
import initUiObservers from './ui/observers';
|
import initUiObservers from './ui/observers';
|
||||||
|
import initAuthObservers from './auth/observers';
|
||||||
|
|
||||||
import { formatDate, formatDuration } from './format';
|
import { formatDate, formatDuration } from './format';
|
||||||
|
|
||||||
|
|
@ -63,7 +64,8 @@ async function init() {
|
||||||
return `${path}/${filename}`;
|
return `${path}/${filename}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
initUiObservers(store, router);
|
await initAuthObservers(store, router);
|
||||||
|
await initUiObservers(store, router);
|
||||||
|
|
||||||
if (window.env.sfw) {
|
if (window.env.sfw) {
|
||||||
store.dispatch('setSfw', true);
|
store.dispatch('setSfw', true);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router';
|
import { createRouter, createWebHistory } from 'vue-router';
|
||||||
|
|
||||||
import Home from '../components/home/home.vue';
|
import Home from '../components/home/home.vue';
|
||||||
|
import Login from '../components/auth/login.vue';
|
||||||
|
import Signup from '../components/auth/signup.vue';
|
||||||
|
import User from '../components/users/user.vue';
|
||||||
import Release from '../components/releases/release.vue';
|
import Release from '../components/releases/release.vue';
|
||||||
import Entity from '../components/entities/entity.vue';
|
import Entity from '../components/entities/entity.vue';
|
||||||
import Networks from '../components/networks/networks.vue';
|
import Networks from '../components/networks/networks.vue';
|
||||||
|
|
@ -16,6 +19,7 @@ import NotFound from '../components/errors/404.vue';
|
||||||
const routes = [
|
const routes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '/',
|
||||||
|
name: 'home',
|
||||||
redirect: {
|
redirect: {
|
||||||
name: 'updates',
|
name: 'updates',
|
||||||
params: {
|
params: {
|
||||||
|
|
@ -25,6 +29,21 @@ const routes = [
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/login',
|
||||||
|
name: 'login',
|
||||||
|
component: Login,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/signup',
|
||||||
|
name: 'signup',
|
||||||
|
component: Signup,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/user/:username',
|
||||||
|
name: 'user',
|
||||||
|
component: User,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/updates',
|
path: '/updates',
|
||||||
redirect: {
|
redirect: {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { post, del } from '../api';
|
||||||
|
|
||||||
|
function initStashesActions(_store, _router) {
|
||||||
|
async function stashActor(context, { actorId, stashId }) {
|
||||||
|
await post(`/stashes/${stashId}/actors`, { actorId });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function unstashActor(context, { actorId, stashId }) {
|
||||||
|
await del(`/stashes/${stashId}/actors/${actorId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
stashActor,
|
||||||
|
unstashActor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initStashesActions;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import state from './state';
|
||||||
|
import mutations from './mutations';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
function initStashesStore(store, router) {
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions: actions(store, router),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initStashesStore;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
||||||
|
|
@ -2,20 +2,24 @@ import Vuex from 'vuex';
|
||||||
|
|
||||||
import initUiStore from './ui/ui';
|
import initUiStore from './ui/ui';
|
||||||
import initAuthStore from './auth/auth';
|
import initAuthStore from './auth/auth';
|
||||||
|
import initUsersStore from './users/users';
|
||||||
import initReleasesStore from './releases/releases';
|
import initReleasesStore from './releases/releases';
|
||||||
import initEntitiesStore from './entities/entities';
|
import initEntitiesStore from './entities/entities';
|
||||||
import initActorsStore from './actors/actors';
|
import initActorsStore from './actors/actors';
|
||||||
import initTagsStore from './tags/tags';
|
import initTagsStore from './tags/tags';
|
||||||
|
import initStashesStore from './stashes/stashes';
|
||||||
|
|
||||||
function initStore(router) {
|
function initStore(router) {
|
||||||
const store = new Vuex.Store();
|
const store = new Vuex.Store();
|
||||||
|
|
||||||
store.registerModule('ui', initUiStore(store, router));
|
store.registerModule('ui', initUiStore(store, router));
|
||||||
store.registerModule('auth', initAuthStore(store, router));
|
store.registerModule('auth', initAuthStore(store, router));
|
||||||
|
store.registerModule('users', initUsersStore(store, router));
|
||||||
store.registerModule('releases', initReleasesStore(store, router));
|
store.registerModule('releases', initReleasesStore(store, router));
|
||||||
store.registerModule('entities', initEntitiesStore(store, router));
|
store.registerModule('entities', initEntitiesStore(store, router));
|
||||||
store.registerModule('actors', initActorsStore(store, router));
|
store.registerModule('actors', initActorsStore(store, router));
|
||||||
store.registerModule('tags', initTagsStore(store, router));
|
store.registerModule('tags', initTagsStore(store, router));
|
||||||
|
store.registerModule('stashes', initStashesStore(store, router));
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { graphql } from '../api';
|
||||||
|
|
||||||
|
function initUsersActions(_store, _router) {
|
||||||
|
async function fetchUser(context, username) {
|
||||||
|
const { user } = await graphql(`
|
||||||
|
query User(
|
||||||
|
$username: String!
|
||||||
|
) {
|
||||||
|
user: userByUsername(username: $username) {
|
||||||
|
id
|
||||||
|
role
|
||||||
|
username
|
||||||
|
stashes {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
public
|
||||||
|
actors: stashesActors {
|
||||||
|
comment
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
gender
|
||||||
|
age
|
||||||
|
ageFromBirth
|
||||||
|
dateOfBirth
|
||||||
|
birthCity
|
||||||
|
birthState
|
||||||
|
birthCountry: countryByBirthCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
avatar: avatarMedia {
|
||||||
|
id
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
lazy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scenes: stashesScenes {
|
||||||
|
comment
|
||||||
|
scene {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
date
|
||||||
|
actors: releasesActors {
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tags: releasesTags {
|
||||||
|
tag {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entity {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
independent
|
||||||
|
parent {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
independent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
poster: releasesPosterByReleaseId {
|
||||||
|
media {
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
lazy
|
||||||
|
isS3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
username,
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchUser,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initUsersActions;
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import state from './state';
|
||||||
|
import mutations from './mutations';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
function initUsersStore(store, router) {
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions: actions(store, router),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initUsersStore;
|
||||||
|
|
@ -33,6 +33,9 @@ module.exports = {
|
||||||
accessKey: 'ABCDEFGHIJ1234567890',
|
accessKey: 'ABCDEFGHIJ1234567890',
|
||||||
secretKey: 'abcdefghijklmnopqrstuvwxyz1234567890ABCD',
|
secretKey: 'abcdefghijklmnopqrstuvwxyz1234567890ABCD',
|
||||||
},
|
},
|
||||||
|
auth: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
exclude: {
|
exclude: {
|
||||||
channels: [
|
channels: [
|
||||||
// 21sextreme, no longer updated
|
// 21sextreme, no longer updated
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
const config = require('config');
|
||||||
|
|
||||||
exports.up = knex => Promise.resolve()
|
exports.up = knex => Promise.resolve()
|
||||||
.then(() => knex.schema.createTable('countries', (table) => {
|
.then(() => knex.schema.createTable('countries', (table) => {
|
||||||
table.text('alpha2', 2)
|
table.text('alpha2', 2)
|
||||||
|
|
@ -105,6 +107,10 @@ exports.up = knex => Promise.resolve()
|
||||||
.inTable('entities')
|
.inTable('entities')
|
||||||
.onDelete('cascade');
|
.onDelete('cascade');
|
||||||
|
|
||||||
|
table.date('date');
|
||||||
|
table.enum('date_precision', ['year', 'month', 'day', 'hour', 'minute', 'second'])
|
||||||
|
.defaultTo('year');
|
||||||
|
|
||||||
table.text('comment');
|
table.text('comment');
|
||||||
table.text('group');
|
table.text('group');
|
||||||
|
|
||||||
|
|
@ -980,12 +986,125 @@ exports.up = knex => Promise.resolve()
|
||||||
|
|
||||||
table.unique(['tag_id', 'chapter_id']);
|
table.unique(['tag_id', 'chapter_id']);
|
||||||
}))
|
}))
|
||||||
|
.then(() => knex.schema.createTable('users_roles', (table) => {
|
||||||
|
table.string('role')
|
||||||
|
.primary();
|
||||||
|
|
||||||
|
table.json('abilities');
|
||||||
|
}))
|
||||||
|
.then(() => knex('users_roles').insert([
|
||||||
|
{
|
||||||
|
role: 'admin',
|
||||||
|
abilities: JSON.stringify([ // serialization necessary to avoid array being interpreted as a PG array
|
||||||
|
{ subject: 'scene', action: 'create' },
|
||||||
|
{ subject: 'scene', action: 'update' },
|
||||||
|
{ subject: 'scene', action: 'delete' },
|
||||||
|
{ subject: 'actor', action: 'create' },
|
||||||
|
{ subject: 'actor', action: 'update' },
|
||||||
|
{ subject: 'actor', action: 'delete' },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'editor',
|
||||||
|
abilities: JSON.stringify([ // serialization necessary to avoid array being interpreted as a PG array
|
||||||
|
{ subject: 'scene', action: 'update' },
|
||||||
|
{ subject: 'actor', action: 'update' },
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
},
|
||||||
|
]))
|
||||||
|
.then(() => knex.schema.createTable('users', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
|
||||||
|
table.text('username')
|
||||||
|
.unique()
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.text('email')
|
||||||
|
.unique()
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.text('password')
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.string('role')
|
||||||
|
.references('role')
|
||||||
|
.inTable('users_roles')
|
||||||
|
.defaultTo('user')
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.json('abilities');
|
||||||
|
|
||||||
|
table.boolean('email_verified')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(false);
|
||||||
|
|
||||||
|
table.boolean('identity_verified')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(false);
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('stashes', (table) => {
|
||||||
|
table.increments('id');
|
||||||
|
|
||||||
|
table.integer('user_id')
|
||||||
|
.references('id')
|
||||||
|
.inTable('users');
|
||||||
|
|
||||||
|
table.string('name')
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.string('slug')
|
||||||
|
.notNullable();
|
||||||
|
|
||||||
|
table.boolean('public')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(false);
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.notNullable()
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('stashes_scenes', (table) => {
|
||||||
|
table.integer('stash_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('stashes');
|
||||||
|
|
||||||
|
table.integer('scene_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
|
table.unique(['stash_id', 'scene_id']);
|
||||||
|
|
||||||
|
table.string('comment');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('stashes_actors', (table) => {
|
||||||
|
table.integer('stash_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('stashes');
|
||||||
|
|
||||||
|
table.integer('actor_id')
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('actors');
|
||||||
|
|
||||||
|
table.unique(['stash_id', 'actor_id']);
|
||||||
|
|
||||||
|
table.string('comment');
|
||||||
|
}))
|
||||||
// SEARCH
|
// SEARCH
|
||||||
.then(() => { // eslint-disable-line arrow-body-style
|
.then(() => { // eslint-disable-line arrow-body-style
|
||||||
// allow vim fold
|
// allow vim fold
|
||||||
return knex.raw(`
|
return knex.raw(`
|
||||||
ALTER TABLE releases_search
|
ALTER TABLE releases_search ADD COLUMN document tsvector;
|
||||||
ADD COLUMN document tsvector;
|
|
||||||
`);
|
`);
|
||||||
})
|
})
|
||||||
// INDEXES
|
// INDEXES
|
||||||
|
|
@ -1003,6 +1122,10 @@ exports.up = knex => Promise.resolve()
|
||||||
.then(() => { // eslint-disable-line arrow-body-style
|
.then(() => { // eslint-disable-line arrow-body-style
|
||||||
// allow vim fold
|
// allow vim fold
|
||||||
return knex.raw(`
|
return knex.raw(`
|
||||||
|
CREATE FUNCTION current_user_id() RETURNS INTEGER AS $$
|
||||||
|
SELECT current_setting('user.id', true)::integer;
|
||||||
|
$$ LANGUAGE SQL STABLE;
|
||||||
|
|
||||||
/* We need both the release entries and their search ranking, and PostGraphile does not seem to allow virtual foreign keys on function results.
|
/* We need both the release entries and their search ranking, and PostGraphile does not seem to allow virtual foreign keys on function results.
|
||||||
* Using a table as a proxy for the search results allows us to get both a reference to the releases table, and the ranking.
|
* Using a table as a proxy for the search results allows us to get both a reference to the releases table, and the ranking.
|
||||||
* A composite type does not seem to be compatible with PostGraphile's @sortable, and a view does not allow for many native constraints */
|
* A composite type does not seem to be compatible with PostGraphile's @sortable, and a view does not allow for many native constraints */
|
||||||
|
|
@ -1169,10 +1292,54 @@ exports.up = knex => Promise.resolve()
|
||||||
$$ LANGUAGE sql STABLE;
|
$$ LANGUAGE sql STABLE;
|
||||||
`);
|
`);
|
||||||
})
|
})
|
||||||
|
// POLICIES
|
||||||
|
.then(() => { // eslint-disable-line arrow-body-style
|
||||||
|
// allow vim fold
|
||||||
|
return knex.raw(`
|
||||||
|
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||||
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||||
|
|
||||||
|
REVOKE ALL ON users FROM :visitor;
|
||||||
|
GRANT SELECT (id, username, role, identity_verified, created_at) ON users TO :visitor;
|
||||||
|
|
||||||
|
ALTER TABLE stashes ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE stashes_scenes ENABLE ROW LEVEL SECURITY;
|
||||||
|
ALTER TABLE stashes_actors ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE POLICY stashes_policy_select ON stashes FOR SELECT USING (stashes.public OR stashes.user_id = current_user_id());
|
||||||
|
CREATE POLICY stashes_policy_update ON stashes FOR UPDATE USING (stashes.public OR stashes.user_id = current_user_id());
|
||||||
|
CREATE POLICY stashes_policy_delete ON stashes FOR DELETE USING (stashes.public OR stashes.user_id = current_user_id());
|
||||||
|
CREATE POLICY stashes_policy_insert ON stashes FOR INSERT WITH CHECK (true);
|
||||||
|
|
||||||
|
CREATE POLICY stashes_policy ON stashes_scenes
|
||||||
|
USING (EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM stashes
|
||||||
|
WHERE stashes.id = stashes_scenes.stash_id
|
||||||
|
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||||
|
));
|
||||||
|
|
||||||
|
CREATE POLICY stashes_policy ON stashes_actors
|
||||||
|
USING (EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM stashes
|
||||||
|
WHERE stashes.id = stashes_actors.stash_id
|
||||||
|
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||||
|
));
|
||||||
|
`, {
|
||||||
|
visitor: knex.raw(config.database.query.user),
|
||||||
|
password: knex.raw(config.database.query.password),
|
||||||
|
});
|
||||||
|
})
|
||||||
// VIEWS AND COMMENTS
|
// VIEWS AND COMMENTS
|
||||||
.then(() => { // eslint-disable-line arrow-body-style
|
.then(() => { // eslint-disable-line arrow-body-style
|
||||||
// allow vim fold
|
// allow vim fold
|
||||||
return knex.raw(`
|
return knex.raw(`
|
||||||
|
COMMENT ON COLUMN users.password IS E'@omit';
|
||||||
|
COMMENT ON COLUMN users.email IS E'@omit';
|
||||||
|
COMMENT ON COLUMN users.email_verified IS E'@omit';
|
||||||
|
COMMENT ON COLUMN users.abilities IS E'@omit';
|
||||||
|
|
||||||
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
|
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
|
||||||
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
|
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
|
||||||
COMMENT ON COLUMN actors.penis_length IS E'@omit read,update,create,delete,all,many';
|
COMMENT ON COLUMN actors.penis_length IS E'@omit read,update,create,delete,all,many';
|
||||||
|
|
@ -1249,6 +1416,13 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
||||||
DROP TABLE IF EXISTS entities_types CASCADE;
|
DROP TABLE IF EXISTS entities_types CASCADE;
|
||||||
DROP TABLE IF EXISTS entities CASCADE;
|
DROP TABLE IF EXISTS entities CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS stashes_scenes CASCADE;
|
||||||
|
DROP TABLE IF EXISTS stashes_actors CASCADE;
|
||||||
|
DROP TABLE IF EXISTS stashes CASCADE;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS users CASCADE;
|
||||||
|
DROP TABLE IF EXISTS users_roles CASCADE;
|
||||||
|
|
||||||
DROP FUNCTION IF EXISTS search_releases;
|
DROP FUNCTION IF EXISTS search_releases;
|
||||||
DROP FUNCTION IF EXISTS search_sites;
|
DROP FUNCTION IF EXISTS search_sites;
|
||||||
DROP FUNCTION IF EXISTS search_entities;
|
DROP FUNCTION IF EXISTS search_entities;
|
||||||
|
|
@ -1265,6 +1439,12 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style
|
||||||
DROP FUNCTION IF EXISTS movies_tags;
|
DROP FUNCTION IF EXISTS movies_tags;
|
||||||
DROP FUNCTION IF EXISTS movies_photos;
|
DROP FUNCTION IF EXISTS movies_photos;
|
||||||
|
|
||||||
|
DROP POLICY IF EXISTS stashes_policy ON stashes;
|
||||||
|
DROP POLICY IF EXISTS stashes_policy ON stashes_scenes;
|
||||||
|
DROP POLICY IF EXISTS stashes_policy ON stashes_actors;
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS current_user_id;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS releases_search_results;
|
DROP TABLE IF EXISTS releases_search_results;
|
||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
{
|
{
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.184.2",
|
"version": "1.185.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"version": "1.184.2",
|
"version": "1.185.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@casl/ability": "^5.2.2",
|
||||||
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
||||||
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
||||||
"acorn": "^8.0.4",
|
"acorn": "^8.0.4",
|
||||||
|
|
@ -1262,6 +1263,17 @@
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@casl/ability": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-A0GTDWojP72Z4HSgS0pfbtGnhQWbquhn9luAr4Uc/HnqWWib0NvmpXC4//7gsiMUiVYCoFozQ+nG1oeZuhT7Jg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/mongo2js": "^1.3.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/stalniy/casl/blob/master/BACKERS.md"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@eslint/eslintrc": {
|
"node_modules/@eslint/eslintrc": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
||||||
|
|
@ -1492,6 +1504,37 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ucast/core": {
|
||||||
|
"version": "1.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.8.2.tgz",
|
||||||
|
"integrity": "sha512-pc+XGjJmZkfypJIIRo38el/FUDtBXBlGQbXafWwRwInocXVwNbJ56efECKLgAQSyI7OCJFSaEeqpf3SrR3D6cw=="
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/js": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-sabiuYsM5VUg4EaCwlDxnqcrHPFvbZcXvBu+P/o4pqK2q046RLTdo0bM7iVCn5Ro4HpCiRv3QzxtW8epcluY1g==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-l/hc3TxjWO9inBrgM5iMCAcsIeV2DToppRlabQa5xB/6uHYtCXfm3TPaJgr8TU1OFxqPlaXEnNQhaV0sVHGsoQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo2js": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-KNOEs61wxo4VJkVGqwP2a03TKuLx9fLMQgW5HD8Th/mrcuP1SspS4W+kUQD+wB1AA5pOn65hzlHUw5wZBwme0Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.6.1",
|
||||||
|
"@ucast/js": "^3.0.0",
|
||||||
|
"@ucast/mongo": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@videojs/http-streaming": {
|
"node_modules/@videojs/http-streaming": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
||||||
|
|
@ -16603,6 +16646,14 @@
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@casl/ability": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-A0GTDWojP72Z4HSgS0pfbtGnhQWbquhn9luAr4Uc/HnqWWib0NvmpXC4//7gsiMUiVYCoFozQ+nG1oeZuhT7Jg==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/mongo2js": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@eslint/eslintrc": {
|
"@eslint/eslintrc": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
||||||
|
|
@ -16805,6 +16856,37 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ucast/core": {
|
||||||
|
"version": "1.8.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.8.2.tgz",
|
||||||
|
"integrity": "sha512-pc+XGjJmZkfypJIIRo38el/FUDtBXBlGQbXafWwRwInocXVwNbJ56efECKLgAQSyI7OCJFSaEeqpf3SrR3D6cw=="
|
||||||
|
},
|
||||||
|
"@ucast/js": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-sabiuYsM5VUg4EaCwlDxnqcrHPFvbZcXvBu+P/o4pqK2q046RLTdo0bM7iVCn5Ro4HpCiRv3QzxtW8epcluY1g==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@ucast/mongo": {
|
||||||
|
"version": "2.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.1.tgz",
|
||||||
|
"integrity": "sha512-l/hc3TxjWO9inBrgM5iMCAcsIeV2DToppRlabQa5xB/6uHYtCXfm3TPaJgr8TU1OFxqPlaXEnNQhaV0sVHGsoQ==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@ucast/mongo2js": {
|
||||||
|
"version": "1.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.2.tgz",
|
||||||
|
"integrity": "sha512-KNOEs61wxo4VJkVGqwP2a03TKuLx9fLMQgW5HD8Th/mrcuP1SspS4W+kUQD+wB1AA5pOn65hzlHUw5wZBwme0Q==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.6.1",
|
||||||
|
"@ucast/js": "^3.0.0",
|
||||||
|
"@ucast/mongo": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@videojs/http-streaming": {
|
"@videojs/http-streaming": {
|
||||||
"version": "2.2.4",
|
"version": "2.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.184.2",
|
"version": "1.185.0",
|
||||||
"description": "All the latest porn releases in one place",
|
"description": "All the latest porn releases in one place",
|
||||||
"main": "src/app.js",
|
"main": "src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -69,6 +69,7 @@
|
||||||
"webpack-cli": "^3.3.11"
|
"webpack-cli": "^3.3.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@casl/ability": "^5.2.2",
|
||||||
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
||||||
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
||||||
"acorn": "^8.0.4",
|
"acorn": "^8.0.4",
|
||||||
|
|
|
||||||
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 7.3 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 775 KiB |
|
After Width: | Height: | Size: 781 KiB |
|
After Width: | Height: | Size: 798 KiB |
|
After Width: | Height: | Size: 682 KiB |
|
After Width: | Height: | Size: 692 KiB |
|
After Width: | Height: | Size: 2.9 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.4 MiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 34 KiB |