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"
|
||||
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 class="content-inner actor-inner">
|
||||
|
|
@ -54,13 +68,6 @@
|
|||
>
|
||||
</a>
|
||||
|
||||
<Expand
|
||||
v-if="bioExpanded"
|
||||
:expanded="bioExpanded"
|
||||
class="expand expand-light"
|
||||
@expand="(state) => bioExpanded = state"
|
||||
/>
|
||||
|
||||
<ul class="bio nolist">
|
||||
<li
|
||||
v-if="actor.realName"
|
||||
|
|
@ -384,7 +391,7 @@ import Scroll from '../scroll/scroll.vue';
|
|||
import Gender from './gender.vue';
|
||||
import Social from './social.vue';
|
||||
|
||||
async function fetchActor() {
|
||||
async function fetchActor(scroll = true) {
|
||||
const { actor, releases, totalCount } = await this.$store.dispatch('fetchActorById', {
|
||||
actorId: Number(this.$route.params.actorId),
|
||||
limit: this.limit,
|
||||
|
|
@ -396,11 +403,37 @@ async function fetchActor() {
|
|||
this.releases = releases;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
if (this.$refs.filter) {
|
||||
if (this.$refs.filter && scroll) {
|
||||
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() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
|
|
@ -447,6 +480,8 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
isStashed,
|
||||
me,
|
||||
sfw,
|
||||
showAlbum,
|
||||
},
|
||||
|
|
@ -457,6 +492,8 @@ export default {
|
|||
mounted,
|
||||
methods: {
|
||||
fetchActor,
|
||||
stashActor,
|
||||
unstashActor,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -477,11 +514,10 @@ export default {
|
|||
align-items: center;
|
||||
color: var(--lighten-extreme);
|
||||
background: var(--profile);
|
||||
padding: .5rem 1rem;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
padding: 0;
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
display: inline-flex;
|
||||
justify-content: space-between;
|
||||
|
|
@ -491,7 +527,7 @@ export default {
|
|||
.header-gender {
|
||||
display: inline-block;
|
||||
margin: 0 0 0 .5rem;
|
||||
transform: translate(0, .1rem);
|
||||
transform: translate(0, .125rem);
|
||||
}
|
||||
|
||||
.header-social {
|
||||
|
|
@ -731,6 +767,22 @@ export default {
|
|||
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) {
|
||||
.descriptions-container {
|
||||
display: none;
|
||||
|
|
@ -795,8 +847,16 @@ export default {
|
|||
}
|
||||
|
||||
.header-name {
|
||||
flex-grow: 1;
|
||||
flex-grow: 1;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@
|
|||
@input="(range) => updateValue('age', range, false)"
|
||||
@change="(range) => updateValue('age', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="flower" /></template>
|
||||
<template v-slot:end><Icon icon="pipe" /></template>
|
||||
<template v-slot:start><Icon icon="leaf" /></template>
|
||||
<template v-slot:end><Icon icon="tree3" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<div class="filter-section">
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@
|
|||
>
|
||||
<img
|
||||
:src="getPath(item, 'thumbnail', { local })"
|
||||
:style="{ 'background-image': `url('${getPath(item, 'lazy', { local })}')` }"
|
||||
:width="item.width"
|
||||
:height="item.height"
|
||||
:title="item.title"
|
||||
loading="lazy"
|
||||
class="item image"
|
||||
|
|
@ -183,6 +186,9 @@ export default {
|
|||
.item {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: auto;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.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>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<div class="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>
|
||||
<Menu />
|
||||
</template>
|
||||
</Tooltip>
|
||||
|
||||
|
|
@ -186,30 +126,14 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex';
|
||||
|
||||
import Menu from './menu.vue';
|
||||
import Search from './search.vue';
|
||||
|
||||
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 {
|
||||
components: {
|
||||
Menu,
|
||||
Search,
|
||||
},
|
||||
emits: ['toggleSidebar', 'showFilters'],
|
||||
|
|
@ -220,16 +144,6 @@ export default {
|
|||
showFilters: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
sfw,
|
||||
theme,
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
setSfw,
|
||||
setTheme,
|
||||
},
|
||||
};
|
||||
</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 {
|
||||
display: none;
|
||||
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>
|
||||
async function search() {
|
||||
this.$router.push({ name: 'search', query: { q: this.query } });
|
||||
this.$emit('search');
|
||||
if (this.query) {
|
||||
this.$router.push({ name: 'search', query: { q: this.query } });
|
||||
this.$emit('search');
|
||||
}
|
||||
}
|
||||
|
||||
function searching(to) {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
v-else-if="release.teaser && /^image\//.test(release.teaser.mime)"
|
||||
:src="getPath(release.teaser, 'thumbnail', { original: true })"
|
||||
:alt="release.title"
|
||||
:width="photo.width"
|
||||
:height="photo.height"
|
||||
loading="lazy"
|
||||
class="item trailer"
|
||||
>
|
||||
|
|
@ -66,6 +68,8 @@
|
|||
<img
|
||||
:src="getPath(cover, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||
:width="photo.width"
|
||||
:height="photo.height"
|
||||
class="item cover"
|
||||
loading="lazy"
|
||||
@load="$emit('load', $event)"
|
||||
|
|
@ -87,8 +91,10 @@
|
|||
>
|
||||
<img
|
||||
:src="getPath(photo, 'thumbnail')"
|
||||
:style="{ 'background-image': getPath(photo, 'lazy') }"
|
||||
:style="{ 'background-image': `url('${getPath(photo, 'lazy')}` }"
|
||||
:alt="`Photo ${photo.index + 1}`"
|
||||
:width="photo.width"
|
||||
:height="photo.height"
|
||||
loading="lazy"
|
||||
class="item"
|
||||
@load="$emit('load', $event)"
|
||||
|
|
@ -273,6 +279,7 @@ export default {
|
|||
|
||||
.item {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
height: 18rem;
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
background-size: cover;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,26 @@
|
|||
<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
|
||||
v-for="chapter in chapters"
|
||||
:key="`chapter-${chapter.id}`"
|
||||
|
|
@ -55,6 +76,14 @@
|
|||
<script>
|
||||
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 {
|
||||
components: {
|
||||
Tags,
|
||||
|
|
@ -65,6 +94,16 @@ export default {
|
|||
default: () => [],
|
||||
},
|
||||
},
|
||||
data() {
|
||||
const lastChapter = this.chapters[this.chapters.length - 1];
|
||||
|
||||
return {
|
||||
duration: lastChapter.time + lastChapter.duration,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
timeline,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -153,6 +192,41 @@ export default {
|
|||
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) {
|
||||
.chapters {
|
||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||
|
|
|
|||
|
|
@ -73,7 +73,10 @@
|
|||
>{{ release.entity.name }}</h3>
|
||||
</a>
|
||||
|
||||
<span class="row">
|
||||
<span
|
||||
v-if="release.actors?.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<ul
|
||||
class="actors nolist"
|
||||
:title="release.actors.map(actor => actor.name).join(', ')"
|
||||
|
|
@ -106,7 +109,7 @@
|
|||
>{{ release.shootId }}</span>
|
||||
|
||||
<ul
|
||||
v-if="release.tags.length > 0"
|
||||
v-if="release.tags?.length > 0"
|
||||
:title="release.tags.map(tag => tag.name).join(', ')"
|
||||
class="tags nolist"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
:class="{ new: release.isNew }"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<router-link
|
||||
|
|
@ -33,7 +33,7 @@
|
|||
</span>
|
||||
|
||||
<router-link
|
||||
v-else
|
||||
v-else-if="release.entity"
|
||||
:to="`/${release.entity.type}/${release.entity.slug}`"
|
||||
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 {
|
||||
box-sizing: border-box;
|
||||
padding: .5rem;
|
||||
border: solid 1px var(--shadow-weak);
|
||||
border: solid 1px var(--shadow-hint);
|
||||
color: var(--shadow-strong);
|
||||
background: var(--background);
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
|
||||
&:focus {
|
||||
border: solid 1px var(--primary);
|
||||
}
|
||||
|
||||
&::-webkit-calendar-picker-indicator {
|
||||
opacity: .5;
|
||||
|
|
@ -20,6 +25,35 @@
|
|||
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 {
|
||||
height: fit-content;
|
||||
display: inline-flex;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ $breakpoint3: 1200px;
|
|||
$breakpoint4: 1500px;
|
||||
|
||||
:root {
|
||||
/* --primary: #ff886c; */
|
||||
/*
|
||||
--primary: #ff6c88;
|
||||
--primary-strong: #ff4166;
|
||||
--primary-faded: #ffdfee;
|
||||
*/
|
||||
--primary: #f28;
|
||||
--primary-strong: #f90071;
|
||||
--primary-faded: #ffcce4;
|
||||
|
||||
--text-dark: #222;
|
||||
--text-light: #fff;
|
||||
|
|
@ -36,6 +40,7 @@ $breakpoint4: 1500px;
|
|||
--female: #f0a;
|
||||
|
||||
--alert: #f00;
|
||||
--error: #f00;
|
||||
--warn: #fa0;
|
||||
--success: #5c2;
|
||||
|
||||
|
|
@ -58,6 +63,7 @@ $breakpoint4: 1500px;
|
|||
--tile: #2a2a2a;
|
||||
|
||||
--link: #dd6688;
|
||||
--link-external: #48f;
|
||||
--empty: #333;
|
||||
|
||||
--crease: #eaeaea;
|
||||
|
|
|
|||
|
|
@ -44,3 +44,12 @@ body {
|
|||
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(`
|
||||
query Actor(
|
||||
$actorId: Int!
|
||||
$userId: Int,
|
||||
$hasAuth: Boolean!,
|
||||
$limit:Int = 10,
|
||||
$offset:Int = 0,
|
||||
$after:Datetime = "1900-01-01",
|
||||
|
|
@ -236,6 +238,21 @@ function initActorActions(store, router) {
|
|||
}
|
||||
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),
|
||||
includedActors: getIncludedActors(router),
|
||||
mode,
|
||||
hasAuth: !!store.state.auth.user,
|
||||
userId: store.state.auth.user?.id,
|
||||
});
|
||||
|
||||
if (!actor) {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,16 @@ async function get(endpoint, query = {}) {
|
|||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
const contentTypes = res.headers.get('content-type');
|
||||
|
||||
if (res.ok && contentTypes?.includes('application/json')) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
if (res.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const errorMsg = await res.text();
|
||||
|
||||
throw new Error(errorMsg);
|
||||
|
|
@ -30,10 +36,32 @@ async function post(endpoint, 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();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
throw new Error(errorMsg);
|
||||
|
|
@ -67,5 +95,6 @@ async function graphql(query, variables = null) {
|
|||
export {
|
||||
get,
|
||||
post,
|
||||
del,
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import state from './state';
|
||||
import mutations from './mutations';
|
||||
import getters from './getters';
|
||||
import actions from './actions';
|
||||
|
||||
function initAuthStore(store, router) {
|
||||
return {
|
||||
state,
|
||||
mutations,
|
||||
getters,
|
||||
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 {
|
||||
authenticated: false,
|
||||
enabled: window.env.auth,
|
||||
user: null,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ function curateActor(actor, release) {
|
|||
curatedActor.aliasFor = curateActor(curatedActor.aliasFor);
|
||||
}
|
||||
|
||||
curatedActor.stashes = actor.stashes?.map(stash => stash.stash || stash) || [];
|
||||
|
||||
return curatedActor;
|
||||
}
|
||||
|
||||
|
|
@ -125,9 +127,41 @@ function curateTag(tag) {
|
|||
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 {
|
||||
curateActor,
|
||||
curateEntity,
|
||||
curateRelease,
|
||||
curateTag,
|
||||
curateStash,
|
||||
curateUser,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ const releasePosterFragment = `
|
|||
index
|
||||
path
|
||||
thumbnail
|
||||
width
|
||||
height
|
||||
lazy
|
||||
isS3
|
||||
comment
|
||||
|
|
@ -129,6 +131,8 @@ const releaseCoversFragment = `
|
|||
index
|
||||
path
|
||||
thumbnail
|
||||
width
|
||||
height
|
||||
lazy
|
||||
isS3
|
||||
comment
|
||||
|
|
@ -150,6 +154,8 @@ const releasePhotosFragment = `
|
|||
index
|
||||
path
|
||||
thumbnail
|
||||
width
|
||||
height
|
||||
lazy
|
||||
isS3
|
||||
comment
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import mitt from 'mitt';
|
|||
import router from './router';
|
||||
import initStore from './store';
|
||||
import initUiObservers from './ui/observers';
|
||||
import initAuthObservers from './auth/observers';
|
||||
|
||||
import { formatDate, formatDuration } from './format';
|
||||
|
||||
|
|
@ -63,7 +64,8 @@ async function init() {
|
|||
return `${path}/${filename}`;
|
||||
}
|
||||
|
||||
initUiObservers(store, router);
|
||||
await initAuthObservers(store, router);
|
||||
await initUiObservers(store, router);
|
||||
|
||||
if (window.env.sfw) {
|
||||
store.dispatch('setSfw', true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
|
||||
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 Entity from '../components/entities/entity.vue';
|
||||
import Networks from '../components/networks/networks.vue';
|
||||
|
|
@ -16,6 +19,7 @@ import NotFound from '../components/errors/404.vue';
|
|||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
redirect: {
|
||||
name: 'updates',
|
||||
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',
|
||||
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 initAuthStore from './auth/auth';
|
||||
import initUsersStore from './users/users';
|
||||
import initReleasesStore from './releases/releases';
|
||||
import initEntitiesStore from './entities/entities';
|
||||
import initActorsStore from './actors/actors';
|
||||
import initTagsStore from './tags/tags';
|
||||
import initStashesStore from './stashes/stashes';
|
||||
|
||||
function initStore(router) {
|
||||
const store = new Vuex.Store();
|
||||
|
||||
store.registerModule('ui', initUiStore(store, router));
|
||||
store.registerModule('auth', initAuthStore(store, router));
|
||||
store.registerModule('users', initUsersStore(store, router));
|
||||
store.registerModule('releases', initReleasesStore(store, router));
|
||||
store.registerModule('entities', initEntitiesStore(store, router));
|
||||
store.registerModule('actors', initActorsStore(store, router));
|
||||
store.registerModule('tags', initTagsStore(store, router));
|
||||
store.registerModule('stashes', initStashesStore(store, router));
|
||||
|
||||
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',
|
||||
secretKey: 'abcdefghijklmnopqrstuvwxyz1234567890ABCD',
|
||||
},
|
||||
auth: {
|
||||
enabled: true,
|
||||
},
|
||||
exclude: {
|
||||
channels: [
|
||||
// 21sextreme, no longer updated
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
const config = require('config');
|
||||
|
||||
exports.up = knex => Promise.resolve()
|
||||
.then(() => knex.schema.createTable('countries', (table) => {
|
||||
table.text('alpha2', 2)
|
||||
|
|
@ -105,6 +107,10 @@ exports.up = knex => Promise.resolve()
|
|||
.inTable('entities')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.date('date');
|
||||
table.enum('date_precision', ['year', 'month', 'day', 'hour', 'minute', 'second'])
|
||||
.defaultTo('year');
|
||||
|
||||
table.text('comment');
|
||||
table.text('group');
|
||||
|
||||
|
|
@ -980,12 +986,125 @@ exports.up = knex => Promise.resolve()
|
|||
|
||||
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
|
||||
.then(() => { // eslint-disable-line arrow-body-style
|
||||
// allow vim fold
|
||||
return knex.raw(`
|
||||
ALTER TABLE releases_search
|
||||
ADD COLUMN document tsvector;
|
||||
ALTER TABLE releases_search ADD COLUMN document tsvector;
|
||||
`);
|
||||
})
|
||||
// INDEXES
|
||||
|
|
@ -1003,6 +1122,10 @@ exports.up = knex => Promise.resolve()
|
|||
.then(() => { // eslint-disable-line arrow-body-style
|
||||
// allow vim fold
|
||||
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.
|
||||
* 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 */
|
||||
|
|
@ -1169,10 +1292,54 @@ exports.up = knex => Promise.resolve()
|
|||
$$ 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
|
||||
.then(() => { // eslint-disable-line arrow-body-style
|
||||
// allow vim fold
|
||||
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.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';
|
||||
|
|
@ -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 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_sites;
|
||||
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_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;
|
||||
`);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.184.2",
|
||||
"version": "1.185.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "1.184.2",
|
||||
"version": "1.185.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@casl/ability": "^5.2.2",
|
||||
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
||||
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
||||
"acorn": "^8.0.4",
|
||||
|
|
@ -1262,6 +1263,17 @@
|
|||
"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": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
||||
|
|
@ -1492,6 +1504,37 @@
|
|||
"@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": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
||||
|
|
@ -16603,6 +16646,14 @@
|
|||
"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": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz",
|
||||
|
|
@ -16805,6 +16856,37 @@
|
|||
"@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": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.2.4.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.184.2",
|
||||
"version": "1.185.0",
|
||||
"description": "All the latest porn releases in one place",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
|
|
@ -69,6 +69,7 @@
|
|||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"@casl/ability": "^5.2.2",
|
||||
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
||||
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
||||
"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 |