Updated tag page layout. Added shoot date property. Showing parent favicon on compact entity page. Re-added 'new' indicator on tile. Added Family Sinner to Mile High Media. Various fixes and improvements.

This commit is contained in:
ThePendulum 2020-07-03 01:28:22 +02:00
parent 749864e922
commit 945c2c45ce
81 changed files with 488 additions and 955 deletions

146
] Normal file
View File

@ -0,0 +1,146 @@
<template>
<div
v-if="tag"
class="content"
>
<div
class="tag"
:class="{ nomedia: !hasMedia }"
>
<div class="header">
<h2 class="title">
<Icon icon="price-tag4" />
{{ tag.name }}
</h2>
<p
v-if="description"
class="description header-description"
v-html="description"
/>
</div>
<div class="content-inner">
<Photos
v-if="hasMedia"
:tag="tag"
class="compact"
/>
<FilterBar :fetch-releases="fetchReleases" />
<Releases :releases="tag.releases" />
</div>
</div>
</div>
</template>
<script>
/* eslint-disable no-v-html */
import { Converter } from 'showdown';
import escapeHtml from '../../../src/utils/escape-html';
import FilterBar from '../header/filter-bar.vue';
import Photos from './photos.vue';
import Releases from '../releases/releases.vue';
const converter = new Converter();
async function fetchReleases() {
this.tag = await this.$store.dispatch('fetchTagBySlug', {
tagSlug: this.$route.params.tagSlug,
range: this.$route.params.range,
});
this.hasMedia = this.tag.poster || this.tag.photos.length > 0;
this.description = this.tag.description && converter.makeHtml(escapeHtml(this.tag.description));
}
async function route() {
await this.fetchReleases();
}
async function mounted() {
await this.fetchReleases();
this.pageTitle = this.tag.name;
}
export default {
components: {
FilterBar,
Photos,
Releases,
},
data() {
return {
tag: null,
description: null,
releases: null,
pageTitle: null,
hasMedia: false,
};
},
watch: {
$route: route,
},
mounted,
methods: {
fetchReleases,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.tag {
flex-grow: 1;
overflow: hidden;
&.nomedia {
flex-direction: column;
.sidebar {
display: none;
}
.header {
display: flex;
}
}
}
.header {
background: var(--profile);
color: var(--text-light);
justify-content: space-between;
padding: .5rem 1rem;
.title {
margin: 0 2rem 0 0;
}
}
.title {
padding: 0;
margin: 0;
flex-shrink: 0;
text-transform: capitalize;
.icon {
fill: var(--text-light);
width: 1.25rem;
height: 1.25rem;
}
}
.description {
margin: 0;
line-height: 1.5;
}
.dark .sidebar {
border-right: solid 1px var(--shadow-hint);
}
</style>

View File

@ -4,14 +4,14 @@
<ul class="genders nolist">
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'female', letter } }"
:to="{ name: 'actors', params: { gender: 'female', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'female' }"
class="gender-link female"
><Gender gender="female" /></router-link>
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'male', letter } }"
:to="{ name: 'actors', params: { gender: 'male', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'male' }"
class="gender-link male"
replace
@ -19,7 +19,7 @@
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'trans', letter } }"
:to="{ name: 'actors', params: { gender: 'trans', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'trans' }"
class="gender-link transsexual"
replace
@ -27,7 +27,7 @@
</li>
<li class="gender">
<router-link
:to="{ name: 'actors', params: { gender: 'other', letter } }"
:to="{ name: 'actors', params: { gender: 'other', letter, pageNumber: 1 } }"
:class="{ selected: gender === 'other' }"
class="gender-link other"
replace
@ -42,7 +42,7 @@
class="letter"
>
<router-link
:to="{ name: 'actors', params: { gender, letter: letterX } }"
:to="{ name: 'actors', params: { gender, letter: letterX, pageNumber: 1 } }"
:class="{ selected: letterX === letter }"
class="letter-link"
replace

View File

@ -86,5 +86,6 @@ export default {
.content-inner {
flex-grow: 1;
overflow-y: auto;
overflow-x: hidden;
}
</style>

View File

@ -34,6 +34,8 @@
v-else
class="name"
>{{ entity.name }}</h2>
<Icon icon="share2" />
</a>
<router-link
@ -47,6 +49,12 @@
:src="`/img/logos/${entity.parent.slug}/thumbs/network.png`"
>
<img
v-if="entity.parent.hasLogo"
class="favicon"
:src="`/img/logos/${entity.parent.slug}/favicon.png`"
>
<h3
v-else
class="name parent-name"
@ -123,6 +131,7 @@ async function mounted() {
async function route() {
await this.fetchEntity();
this.expanded = false;
}
export default {
@ -155,35 +164,50 @@ export default {
@import 'theme';
.info {
height: 2.5rem;
display: flex;
justify-content: space-between;
padding: 1rem;
background: var(--profile);
border-bottom: solid 1px var(--lighten-hint);
}
.link {
display: flex;
align-items: center;
text-decoration: none;
flex-grow: 1;
.link {
max-width: 20rem;
display: flex;
align-items: center;
box-sizing: border-box;
padding: 1rem;
text-decoration: none;
}
.link-child {
.icon {
fill: var(--lighten);
margin: 0 0 0 1rem;
}
.link-child {
margin: 0 2rem 0 0;
&:hover .icon {
fill: var(--text-light);
}
}
.link-parent {
flex-direction: row-reverse;
}
.link-parent {
flex-direction: row-reverse;
margin: 0 0 0 2rem;
}
.logo {
height: 100%;
width: 100%;
max-width: 20rem;
max-width: 100%;
object-fit: contain;
object-position: 0 50%;
}
.logo-child {
height: 2.5rem;
}
.logo-parent,
.favicon {
height: 1.5rem;
}
.name {
@ -191,11 +215,23 @@ export default {
display: flex;
align-items: center;
padding: 0;
margin: 0;
white-space: nowrap;
font-size: 1.5rem;
}
.logo-parent {
object-position: 100% 50%;
.favicon {
display: none;
}
@media(max-width: $breakpoint0) {
.logo-parent,
.link-child .icon {
display: none;
}
.favicon {
display: inline-block;
}
}
</style>

View File

@ -6,7 +6,7 @@
>
<template v-if="entity.hasLogo">
<img
v-if="entity.type === 'network'"
v-if="entity.type === 'network' || entity.independent"
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
:alt="entity.name"
class="logo"

View File

@ -1,331 +0,0 @@
<template>
<div
v-if="network"
class="content"
>
<div
class="network"
:class="{ nosites: sites.length === 0 && networks.length === 0 }"
>
<div
v-show="sites.length > 0 || networks.length > 0"
class="sidebar"
:class="{ expanded }"
>
<a
v-tooltip.bottom="`Go to ${network.url}`"
:href="network.url"
target="_blank"
rel="noopener noreferrer"
class="title"
>
<img
:src="`/img/logos/${network.slug}/thumbs/network.png`"
class="logo"
>
</a>
<p
v-if="network.description"
class="description"
>{{ network.description }}</p>
<Sites
v-if="sites.length"
:sites="sites"
:class="{ expanded }"
/>
<div
v-if="networks.length > 0"
class="networks"
>
<Network
v-for="childNetwork in networks"
:key="`network-${childNetwork.id}`"
:network="childNetwork"
/>
</div>
<Network
v-if="network.parent"
:network="network.parent"
class="parent"
/>
</div>
<template v-if="sites.length > 0 || networks.length > 0">
<span
v-show="!expanded"
class="expand expand-sidebar noselect"
@click="expanded = true"
><Icon icon="arrow-right3" /></span>
<span
v-show="expanded"
class="expand expand-sidebar noselect"
@click="expanded = false"
><Icon icon="arrow-left3" /></span>
</template>
<div
class="header"
:class="{ hideable: sites.length > 0 || networks.length > 0 }"
>
<a
v-tooltip.bottom="`Go to ${network.url}`"
:href="network.url"
target="_blank"
rel="noopener noreferrer"
class="title"
>
<img
:src="`/img/logos/${network.slug}/thumbs/network.png`"
class="logo"
>
</a>
</div>
<div class="content-inner">
<FilterBar
:fetch-releases="fetchNetwork"
:items-total="totalCount"
:items-per-page="10"
/>
<template v-if="sites.length > 0 || networks.length > 0">
<span
v-show="expanded"
class="expand collapse-header noselect"
@click="expanded = false"
><Icon icon="arrow-up3" /></span>
<Sites
:sites="sites"
:class="{ expanded }"
class="compact"
/>
<span
v-show="!expanded"
class="expand expand-header noselect"
@click="expanded = true"
><Icon icon="arrow-down3" /></span>
<span
v-show="expanded"
class="expand expand-header noselect"
@click="expanded = false"
><Icon icon="arrow-up3" /></span>
</template>
<Releases :releases="releases" />
</div>
</div>
</div>
</template>
<script>
import FilterBar from '../header/filter-bar.vue';
import Releases from '../releases/releases.vue';
import Sites from '../sites/sites.vue';
import Network from '../tile/network.vue';
async function fetchNetwork() {
const { entity, totalCount } = await this.$store.dispatch('fetchEntityBySlugAndType', {
entitySlug: this.$route.params.networkSlug,
entityType: 'network',
limit: this.limit,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber),
});
this.network = entity;
this.networks = this.network.children;
this.releases = this.network.releases;
this.totalCount = totalCount;
}
async function route() {
await this.fetchNetwork();
}
async function mounted() {
await this.fetchNetwork();
this.pageTitle = this.network.name;
}
export default {
components: {
FilterBar,
Releases,
Sites,
Network,
},
data() {
return {
network: null,
sites: [],
networks: [],
studios: [],
releases: [],
totalCount: null,
limit: 10,
pageTitle: null,
expanded: false,
};
},
watch: {
$route: route,
},
mounted,
methods: {
fetchNetwork,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.network {
display: flex;
flex-direction: row;
flex-grow: 1;
justify-content: stretch;
overflow-y: auto;
&.nosites {
flex-direction: column;
}
}
.content-inner {
padding: 0;
overflow-y: auto;
}
.releases {
padding: 1rem 1rem 1rem .5rem;
border-top: solid 1px var(--crease);
}
.sidebar {
background: var(--profile);
height: 100%;
width: 18rem;
display: flex;
flex-direction: column;
flex-shrink: 0;
color: var(--text-light);
overflow: hidden;
.title {
display: flex;
justify-content: center;
border-bottom: solid 1px var(--highlight-hint);
}
&.expanded {
width: calc(100% - 25rem);
.logo {
max-width: 18rem;
}
}
}
.networks {
display: grid;
grid-gap: 0 1rem;
flex-grow: 1;
padding: 1rem;
grid-template-columns: 1fr;
grid-template-rows: repeat(auto-fit, 6rem);
overflow-y: auto;
scrollbar-color: var(--highlight-weak) var(--profile);
}
.logo {
width: 100%;
max-height: 8rem;
display: flex;
justify-content: center;
object-fit: contain;
box-sizing: border-box;
padding: 1rem;
filter: var(--logo-highlight);
}
.parent {
display: inline-block;
height: 3rem;
}
.header {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
flex-shrink: 0;
border-bottom: solid 1px var(--shadow-hint);
background: var(--profile);
&.hideable {
display: none;
}
.logo {
max-width: 20rem;
max-height: 3rem;
padding: .5rem;
}
}
.sites.compact {
display: none;
background: var(--profile);
grid-row: 1;
}
.collapse-header {
display: none;
}
@media(max-width: $breakpoint3) {
.header,
.header.hideable {
display: flex;
}
.sites.compact {
display: flex;
&.expanded {
display: grid;
}
}
.expand-header,
.collapse-header {
display: flex;
}
.expand-sidebar,
.collapse-sidebar {
display: none;
}
.network {
flex-direction: column;
}
.sidebar {
display: none;
height: auto;
width: 100%;
overflow: hidden;
}
}
</style>

View File

@ -144,8 +144,8 @@ export default {
padding: 1rem 0;
}
@media(max-width: $breakpoint) {
.networks {
@media(max-width: $breakpoint0) {
.entity-tiles {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
}

View File

@ -1,5 +1,8 @@
<template>
<div class="pagination">
<div
v-if="itemsTotal > 0 || !hideEmpty"
class="pagination"
>
<span
v-show="pageNumber > 1"
class="cursors"
@ -79,6 +82,10 @@ export default {
type: Number,
default: 10,
},
hideEmpty: {
type: Boolean,
default: true,
},
},
computed: {
pageNumber,

View File

@ -149,6 +149,10 @@ export default {
scroll-behavior: smooth;
font-size: 0;
&::-webkit-scrollbar {
display: none;
}
&.expanded {
display: flex;
justify-content: center;
@ -165,10 +169,6 @@ export default {
height: 100%;
}
}
&::-webkit-scrollbar {
display: none;
}
}
.poster-link {
@ -241,7 +241,7 @@ export default {
height: 18rem;
vertical-align: middle;
object-fit: cover;
box-shadow: 0 0 3px var(--shadow);
box-shadow: 0 0 3px var(--shadow-weak);
}
.trailer-container {

View File

@ -25,7 +25,6 @@
<div class="tidbits">
<a
v-if="release.date"
v-tooltip.bottom="release.url && `View scene on ${release.entity.name}`"
:title="release.url && `View scene on ${release.entity.name}`"
:href="release.url"
:class="{ link: release.url }"
@ -35,18 +34,22 @@
>
<span class="showable">{{ formatDate(release.date, 'MMM D, YYYY') }}</span>
<span class="hideable">{{ formatDate(release.date, 'MMMM D, YYYY') }}</span>
<Icon icon="share2" />
<Icon
v-if="release.url"
icon="share2"
/>
</a>
</div>
<div class="site">
<template v-if="release.entity.parent">
<template v-if="release.entity.parent && !release.entity.independent">
<a :href="`/network/${release.entity.parent.slug}`">
<img
:src="`/img/logos/${release.entity.parent.slug}/thumbs/network.png`"
:title="release.entity.parent.name"
:alt="release.entity.parent.name"
class="logo logo-network"
class="logo logo-parent"
>
</a>
@ -191,14 +194,22 @@
class="link studio"
>{{ release.studio.name }}</router-link>
</div>
<div
v-if="release.productionDate"
class="row-tidbit"
>
<span class="row-label">Production date</span>
{{ formatDate(release.productionDate, 'MMMM D, YYYY') }}
</div>
</div>
<div class="row">
<span class="row-label">Added</span>
<span class="row-label">Indexed</span>
<router-link
:to="`/added/${formatDate(release.dateAdded, 'YYYY/MM/DD')}`"
:title="`Added on ${formatDate(release.dateAdded, 'MMMM D, YYYY')}`"
:to="`/added/${formatDate(release.createdAt, 'YYYY/MM/DD')}`"
:title="`Added on ${formatDate(release.createdAt, 'MMMM D, YYYY HH:mm')}`"
class="link added"
>{{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</router-link>
</div>
@ -207,9 +218,6 @@
</template>
<script>
// import config from 'config';
// import format from 'template-format';
import Media from './media.vue';
import Actor from '../actors/tile.vue';
import Release from './tile.vue';
@ -273,6 +281,18 @@ export default {
.link {
color: var(--text-light);
.icon {
fill: var(--lighten);
}
&:hover {
color: var(--text-light);
.icon {
fill: var(--text-light);
}
}
}
}
@ -293,7 +313,7 @@ export default {
.icon {
fill: var(--lighten);
margin: -.2rem 0 0 .5rem;
margin: -.2rem 0 0 .75rem;
}
}
}
@ -317,7 +337,7 @@ export default {
object-position: 100% 50%;
}
.logo-network {
.logo-parent {
height: 1.5rem;
max-width: 10rem;
object-fit: contain;
@ -343,7 +363,7 @@ export default {
}
.row {
margin: 0 0 1rem 0;
margin: 0 0 1.5rem 0;
&.associations {
align-items: start;
@ -364,7 +384,7 @@ export default {
.row-tidbit {
display: inline-block;
margin: 0 1rem 0 0;
margin: 0 2rem 0 0;
}
.title {
@ -432,7 +452,7 @@ export default {
}
@media(max-width: $breakpoint3) {
.logo-network,
.logo-parent,
.chain {
display: none;
}

View File

@ -43,7 +43,7 @@
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ formatDate(release.date, 'MMM D, YYYY') }}</a>
>{{ formatDate(release.date, 'MMMM D, YYYY') }}</a>
<a
v-else
@ -53,7 +53,7 @@
target="_blank"
rel="noopener noreferrer"
class="date"
>{{ `(${formatDate(release.dateAdded, 'MMM D, YYYY')})` }}</a>
>{{ `(${formatDate(release.dateAdded, 'MMMM D, YYYY')})` }}</a>
</span>
</template>

View File

@ -1,12 +1,12 @@
<template>
<div
:id="`${release.type}-${release.id}`"
:class="{ [release.type]: true }"
:class="{ new: release.isNew }"
class="tile"
>
<span class="poster">
<a
:href="`/${release.type || 'scene'}/${release.id}/${release.slug}`"
:href="`/scene/${release.id}/${release.slug}`"
target="_blank"
rel="noopener noreferrer"
class="link"
@ -39,13 +39,13 @@
class="thumbnail"
>No thumbnail available</div>
</a>
</span>
<Details :release="release" />
<Details :release="release" />
</span>
<div class="info">
<a
:href="`/${release.type || 'scene'}/${release.id}/${release.slug}`"
:href="`/scene/${release.id}/${release.slug}`"
target="_blank"
rel="noopener noreferrer"
class="row link"
@ -138,10 +138,23 @@ export default {
overflow: hidden;
box-shadow: 0 0 3px var(--darken-weak);
height: 100%;
&.new .poster::after {
content: 'new';
position: absolute;
top: 0;
right: 0;
padding: .15rem .25rem;
color: var(--text-light);
background: var(--primary);
font-size: .8rem;
font-weight: bold;
}
}
.poster {
position: relative;
margin: 0 0 .6rem 0;
}
.covers {
@ -167,10 +180,6 @@ export default {
text-shadow: 1px 1px 0 var(--highlight);
}
.details {
margin: 0 0 .6rem 0;
}
.row {
display: flex;
justify-content: space-between;
@ -193,9 +202,9 @@ export default {
.title {
margin: 0 .25rem .25rem 0;
color: var(--text);
max-height: 2.75rem;
font-size: 1rem;
line-height: 1.5;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}

View File

@ -77,7 +77,7 @@ function mounted() {
window.addEventListener('resize', this.updateScroll);
this.updateScroll();
setTimeout(() => this.updateScroll(), 500); // allow CSS to calculate
setTimeout(() => this.updateScroll(), 50); // allow CSS to calculate
}
function beforeDestroy() {
@ -250,9 +250,11 @@ export default {
padding: 1rem .5rem 1rem 2rem;
}
/*
@media(max-width: $breakpoint) {
.scroll-button {
display: none;
}
}
*/
</style>

View File

@ -1,166 +0,0 @@
<template>
<div
v-if="site"
class="content site"
>
<div class="header">
<a
v-tooltip.bottom="site.url && `Go to ${site.url}`"
:href="site.url"
target="_blank"
rel="noopener noreferrer"
class="link link-site"
>
<img
:src="`/img/logos/${site.network.slug}/${site.slug}.png`"
:title="site.name"
:alt="site.name"
class="logo logo-site"
>
</a>
<ul class="tags nolist">
<li
v-for="tag in site.tags"
:key="`tag-${tag.slug}`"
class="tag"
>{{ tag.name }}</li>
</ul>
<router-link
v-tooltip.bottom="`Go to ${site.network.name} overview`"
:to="{ name: 'network', params: { networkSlug: site.network.slug } }"
class="link link-network"
>
<img
:src="`/img/logos/${site.network.slug}/network.png`"
:title="site.network.name"
:alt="site.network.name"
class="logo logo-network"
>
</router-link>
</div>
<FilterBar
:fetch-releases="fetchSite"
:items-total="totalCount"
:items-per-page="limit"
/>
<div class="content-inner">
<Releases :releases="releases" />
</div>
</div>
</template>
<script>
import FilterBar from '../header/filter-bar.vue';
import Releases from '../releases/releases.vue';
async function fetchSite() {
const { site, releases, totalCount } = await this.$store.dispatch('fetchSiteBySlug', {
siteSlug: this.$route.params.siteSlug,
range: this.$route.params.range,
pageNumber: Number(this.$route.params.pageNumber) || 1,
limit: this.limit,
});
this.site = site;
this.releases = releases;
this.totalCount = totalCount;
}
async function route() {
await this.fetchSite();
}
async function mounted() {
await this.fetchSite();
this.pageTitle = this.site.name;
}
export default {
components: {
FilterBar,
Releases,
},
data() {
return {
site: null,
releases: null,
totalCount: 0,
limit: 10,
pageTitle: null,
};
},
watch: {
$route: route,
},
mounted,
methods: {
fetchSite,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.header {
background: $profile;
color: $text-contrast;
display: flex;
align-items: center;
justify-content: space-between;
}
.link {
padding: 1rem;
}
.link-site {
margin: 0 1rem 0 0;
justify-content: flex-start;
.logo {
object-position: 0 0;
}
}
.link-network {
justify-content: flex-end;
.logo {
object-position: 100% 0;
}
}
.logo {
width: 100%;
max-width: 15rem;
max-height: 5rem;
object-fit: contain;
filter: $logo-highlight;
}
.tag {
background: $shadow;
padding: .5rem;
margin: 0 .5rem .5rem 0;
}
@media(max-width: $breakpoint) {
.link {
padding: .5rem 1rem;
}
.logo {
max-height: 2.5rem;
}
.tags {
display: none;
}
}
</style>

View File

@ -1,95 +0,0 @@
<template>
<div class="sites">
<ul class="nolist tiles">
<li
v-for="site in sites"
:key="`site-${site.id}`"
class="site"
>
<SiteTile :site="site" />
</li>
</ul>
</div>
</template>
<script>
import SiteTile from '../tile/site.vue';
export default {
components: {
SiteTile,
},
props: {
network: {
type: Object,
default: null,
},
sites: {
type: Array,
default: () => [],
},
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.sites {
overflow: hidden;
display: flex;
flex-grow: 1;
&.compact:not(.expanded) {
flex-direction: row;
.tiles {
display: flex;
overflow-x: auto;
}
.tile {
width: 15rem;
margin: 0 1rem 0 0;
}
}
&.expanded {
.tiles {
grid-template-columns: repeat(auto-fit, minmax(15rem, .5fr));
}
&.compact .tiles {
padding: 0 1rem 1rem 1rem;
}
}
}
.tiles {
display: grid;
grid-gap: 0 1rem;
flex-grow: 1;
padding: 1rem;
grid-template-columns: 1fr;
grid-template-rows: min-content;
overflow-y: auto;
scrollbar-color: $highlight-weak $profile;
}
.site {
/* vertical grid-gap not compatible with bottom padding on scrolling containers */
margin: 0 0 1rem 0;
}
@media(max-width: $breakpoint3) {
.sites.expanded .tiles {
grid-template-columns: repeat(auto-fit, minmax(12rem, .5fr));
}
}
@media(max-width: $breakpoint0) {
.sites.expanded .tiles {
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
}
}
</style>

View File

@ -1,104 +1,105 @@
<template>
<div class="photos">
<ul class="nolist photos-inner">
<li>
<a
v-if="tag.poster"
:href="`/img/${poster.path}`"
:title="poster.comment"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="`/img/${poster.thumbnail}`"
:alt="tag.poster.comment"
class="poster"
>
</a>
</li>
<div class="photos">
<a
v-if="tag.poster"
:href="`/img/${poster.path}`"
:title="poster.comment"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="`/img/${poster.thumbnail}`"
:alt="tag.poster.comment"
class="poster"
>
</a>
<li
v-for="photo in photos"
:key="`photo-${photo.id}`"
>
<a
:title="photo.comment"
:href="`/img/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="`/img/${photo.thumbnail}`"
:alt="photo.comment"
class="photo"
>
</a>
</li>
</ul>
</div>
<a
v-for="photo in photos"
:key="`photo-${photo.id}`"
:title="photo.comment"
:href="`/img/${photo.path}`"
target="_blank"
rel="noopener noreferrer"
class="photo-link"
>
<img
:src="`/img/${photo.thumbnail}`"
:alt="photo.comment"
class="photo"
>
</a>
</div>
</template>
<script>
function poster() {
if (this.$store.state.ui.sfw) {
return this.tag.poster.sfw;
}
if (this.$store.state.ui.sfw) {
return this.tag.poster.sfw;
}
return this.tag.poster;
return this.tag.poster;
}
function photos() {
if (this.$store.state.ui.sfw) {
return this.tag.photos.map(photo => photo.sfw);
}
if (this.$store.state.ui.sfw) {
return this.tag.photos.map(photo => photo.sfw);
}
return this.tag.photos;
return this.tag.photos;
}
export default {
props: {
tag: {
type: Object,
default: null,
},
},
computed: {
poster,
photos,
},
props: {
tag: {
type: Object,
default: null,
},
},
computed: {
poster,
photos,
},
};
</script>
<style lang="scss" scoped>
@import 'theme';
.photos {
background: $profile;
display: flex;
padding: 0 1rem;
overflow: hidden;
width: 100%;
padding: 1rem 1rem 0 1rem;
box-sizing: border-box;
overflow-x: auto;
white-space: nowrap;
scrollbar-width: none;
scroll-behavior: smooth;
font-size: 0;
&.compact {
&::-webkit-scrollbar {
display: none;
padding: 0 1rem 0 1rem;
overflow-x: auto;
.photos-inner {
flex-shrink: 0;
}
.photo-link {
display: inline-block;
margin: 0 .5rem 0 0;
}
}
&.expanded {
display: flex;
justify-content: center;
flex-wrap: wrap;
padding: 0 1rem;
.poster,
.photo {
height: 18rem;
margin: 0 .5rem .5rem 0;
}
}
}
.photo-link:not(:last-child) {
margin: 0 .5rem 0 0;
}
.poster,
.photo {
width: 100%;
margin: 0 0 .5rem 0;
height: 15rem;
box-shadow: 0 0 3px var(--shadow-weak);
}
</style>

View File

@ -13,41 +13,26 @@
{{ tag.name }}
</h2>
<p
<div
v-if="description"
class="description header-description"
v-html="description"
/>
</div>
<div class="sidebar">
<h2 class="title">
<Icon icon="price-tag4" />
{{ tag.name }}
</h2>
<div class="sidebar-content">
<p
v-if="description"
class="description"
v-html="description"
/>
<Photos
v-if="hasMedia"
:tag="tag"
/>
</div>
</div>
<Scroll
v-if="hasMedia"
:expanded="expanded"
class="scroll-light"
@expand="(state) => expanded = state"
>
<Photos
:tag="tag"
:class="{ expanded }"
/>
</Scroll>
<div class="content-inner">
<Photos
v-if="hasMedia"
:tag="tag"
class="compact"
/>
<FilterBar :fetch-releases="fetchReleases" />
<Releases :releases="tag.releases" />
</div>
@ -64,6 +49,7 @@ import escapeHtml from '../../../src/utils/escape-html';
import FilterBar from '../header/filter-bar.vue';
import Photos from './photos.vue';
import Releases from '../releases/releases.vue';
import Scroll from '../scroll/scroll.vue';
const converter = new Converter();
@ -89,8 +75,9 @@ async function mounted() {
export default {
components: {
FilterBar,
Photos,
Releases,
Photos,
Scroll,
},
data() {
return {
@ -99,6 +86,7 @@ export default {
releases: null,
pageTitle: null,
hasMedia: false,
expanded: false,
};
},
watch: {
@ -133,71 +121,14 @@ export default {
<style lang="scss" scoped>
@import 'theme';
.tag {
display: flex;
flex-grow: 1;
overflow: hidden;
&.nomedia {
flex-direction: column;
.sidebar {
display: none;
}
.header {
display: flex;
}
}
}
.content-inner {
padding: 0;
overflow-y: auto;
}
.header {
background: var(--profile);
color: var(--text-light);
display: none;
justify-content: space-between;
padding: .5rem 1rem;
.title {
margin: 0 2rem 0 0;
}
}
.sidebar {
background: var(--profile);
color: var(--text-light);
display: flex;
flex-direction: column;
flex-shrink: 0;
width: 25rem;
box-sizing: border-box;
overflow: hidden;
.title {
padding: 1rem;
}
.description {
padding: 0 1rem;
margin: -1rem 0 0 0;
}
&.empty {
display: none;
}
}
.sidebar-content {
overflow-y: auto;
}
.title {
padding: 0;
padding: 1rem;
margin: 0;
flex-shrink: 0;
text-transform: capitalize;
@ -210,33 +141,11 @@ export default {
}
.description {
margin: 0;
padding: 0 1rem 1rem 1rem;
line-height: 1.5;
}
.releases {
padding: 1rem;
}
.dark .sidebar {
border-right: solid 1px var(--shadow-hint);
}
@media(max-width: $breakpoint3) {
.tag {
flex-direction: column;
}
.sidebar {
display: none;
}
.header {
display: flex;
}
.photos.compact {
display: flex;
}
}
</style>

View File

@ -36,6 +36,11 @@ function initEntitiesActions(store, _router) {
hasLogo
children: childEntitiesConnection(
orderBy: [PRIORITY_DESC, NAME_ASC],
filter: {
type: {
notEqualTo: "info"
}
}
) {
nodes {
id
@ -44,6 +49,7 @@ function initEntitiesActions(store, _router) {
url
type
priority
independent
hasLogo
}
}
@ -175,6 +181,7 @@ function initEntitiesActions(store, _router) {
slug
type
url
independent
hasLogo
children: childEntitiesConnection {
totalCount
@ -203,6 +210,7 @@ function initEntitiesActions(store, _router) {
slug
type
url
independent
hasLogo
parent {
name

View File

@ -172,6 +172,7 @@ const releaseFields = `
slug
type
shootId
productionDate
createdAt
url
${releaseActorsFragment}
@ -237,6 +238,7 @@ const releaseFragment = `
duration
createdAt
shootId
productionDate
url
${releaseActorsFragment}
${releaseTagsFragment}

View File

@ -3,7 +3,6 @@ import VueRouter from 'vue-router';
import Home from '../components/home/home.vue';
import Release from '../components/releases/release.vue';
import Site from '../components/sites/site.vue';
import Entity from '../components/entities/entity.vue';
import Networks from '../components/networks/networks.vue';
import Actor from '../components/actors/actor.vue';
@ -71,7 +70,6 @@ const routes = [
},
{
path: '/channel/:entitySlug',
component: Site,
redirect: from => ({
name: 'channel',
params: {

View File

@ -4,7 +4,7 @@ const storedSfw = localStorage.getItem('sfw');
const storedTheme = localStorage.getItem('theme');
export default {
filter: storedFilter ? storedFilter.split(',') : ['gay', 'transsexual'],
filter: storedFilter ? storedFilter.split(',') : [],
range: 'latest',
batch: storedBatch || 'all',
sfw: storedSfw === 'true' || false,

View File

@ -615,6 +615,8 @@ exports.up = knex => Promise.resolve()
table.date('date');
table.index('date');
table.date('production_date');
table.text('description');
table.integer('duration')

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 0 B

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 KiB

After

Width:  |  Height:  |  Size: 628 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 438 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -110,6 +110,7 @@ const tags = [
name: 'anal toys',
slug: 'anal-toys',
description: 'Stuffing a toy, such as a dildo or buttplug, into the ass',
priority: 7,
},
{
name: 'animated',
@ -394,7 +395,7 @@ const tags = [
{
name: 'facefucking',
slug: 'facefucking',
priority: 9,
priority: 7,
group: 'oral',
},
{
@ -410,6 +411,7 @@ const tags = [
{
name: 'family taboo',
slug: 'family',
priority: 7,
},
{
name: 'feet',
@ -658,6 +660,16 @@ const tags = [
name: 'piercings',
slug: 'piercings',
},
{
name: 'piss drinking',
slug: 'piss-drinking',
priority: 6,
},
{
name: 'pissing',
slug: 'pissing',
priority: 6,
},
{
name: 'POV',
slug: 'pov',
@ -822,6 +834,7 @@ const tags = [
{
name: 'toys',
slug: 'toys',
priority: 7,
},
{
name: 'transsexual',
@ -1138,6 +1151,10 @@ const aliases = [
name: 'butt plug',
for: 'anal-toys',
},
{
name: 'butt plugs',
for: 'anal-toys',
},
{
name: 'caning',
for: 'corporal-punishment',
@ -1456,6 +1473,18 @@ const aliases = [
name: 'pierced',
for: 'piercings',
},
{
name: 'piss',
for: 'pissing',
},
{
name: 'pee',
for: 'pissing',
},
{
name: 'peeing',
for: 'pissing',
},
{
name: 'prolapse',
for: 'anal-prolapse',

View File

@ -3142,6 +3142,7 @@ const sites = [
slug: 'doghousedigital',
name: 'Doghouse Digital',
url: 'https://www.doghousedigital.com',
alias: ['dhd'],
parameters: { siteId: 321 },
parent: 'milehighmedia',
},
@ -3149,12 +3150,15 @@ const sites = [
slug: 'milehighmedia',
name: 'Mile High Media',
url: 'https://www.milehighmedia.com/scenes?site=323',
alias: ['mhm'],
parameters: { siteId: 323 },
parent: 'milehighmedia',
},
{
slug: 'realityjunkies',
name: 'Reality Junkies',
url: 'https://www.realityjunkies.com',
alias: ['rj'],
parameters: { siteId: 324 },
parent: 'milehighmedia',
},
@ -3162,6 +3166,7 @@ const sites = [
slug: 'sweetheartvideo',
name: 'Sweetheart Video',
url: 'https://www.sweetheartvideo.com',
alias: ['shv'],
parameters: { siteId: 325 },
parent: 'milehighmedia',
},
@ -3169,15 +3174,26 @@ const sites = [
slug: 'sweetsinner',
name: 'Sweet Sinner',
url: 'https://www.sweetsinner.com',
alias: ['ss'],
parameters: { siteId: 326 },
parent: 'milehighmedia',
},
{
slug: 'familysinners',
name: 'Family Sinners',
alias: ['fs'],
tags: ['family'],
url: 'https://www.familysinners.com',
parameters: { siteId: 317 },
parent: 'milehighmedia',
},
{
slug: 'iconmale',
name: 'Icon Male',
url: 'https://www.iconmale.com',
alias: ['im'],
tags: ['gay'],
parameters: { native: true },
parameters: { native: true, siteId: 328 },
parent: 'milehighmedia',
},
// MOFOS
@ -4380,7 +4396,6 @@ const sites = [
description: 'PureTaboo.com is the ultimate site for family taboo porn, featuring submissive teens & virgins in rough sex videos in ultra 4k HD.',
parent: 'puretaboo',
priority: 1,
independent: true,
parameters: {
mobile: 'https://m.dpfanatics.com/en/video',
},
@ -4627,6 +4642,7 @@ const sites = [
description: "There's no denying it, at Reality Kings we love all kinds of pussy! Ask us what we really love however, and you'll get one answer: hot wet pussy! Team Squirt invites you to strap on your snorkel and fins, because we're going diving in some of the wettest pussy around! This is NOT pee ladies and gentlemen, this is real female ejaculation. Watch these beautiful ladies experience pleasure beyond belief as they try to control their squirting pussy on camera. Masturbation, fucking, whatever it takes, these babes will do anything for a squirting orgasm! Team Squirt has tons of high quality videos of girls squirting available for you to download right now. Be prepared, this is some serious female squirting content! From the girl, to the camera... everything is drenched when these super soakers take aim. These babes all pack a loaded, squirting pussy, and they know exactly how to use it! Grab your eye protection and join the team... Team Squirt.",
parameters: null,
slug: 'teamsquirt',
hasLogo: false,
parent: 'realitykings',
},
{
@ -4635,6 +4651,7 @@ const sites = [
description: "We all love them, from the sexy mom at the grocery store, to the mature hottie down the block... we're talking about the MILF Next Door! There is nothing that these hot MILFs need more than a good pounding. If you don't know what a MILF is, allow us to explain... a Mother I'd Like to Fuck, a MILF! Watch as these sex starved sluts and their girlfriends search for a lucky dude to satisfy their craving for cock. MILF Next Door offers lesbian threesomes, amazing foursomes, and more mature sex movies featuring the hottest mature women! Start downloading some of this incredible content right now from our free pics and videos below. Every episode features another stunningly hot MILF finally getting the attention she deserves. If you love everyday mom's and can't wait to see these ladies get off, join Reality Kings and the MILF Next Door.",
parameters: null,
slug: 'milfnextdoor',
hasLogo: false,
parent: 'realitykings',
},
{
@ -4724,6 +4741,7 @@ const sites = [
description: 'Wives in Pantyhose features all kinds of real wives in sexy lingerie fingering their pussies with sex toys while they squeeze their big mature tits and moan. This Reality Kings network site has collected tons of pantyhose pics of hot wives and presented them to you for your viewing pleasure. No matter whether you prefer Latinas, MILFs, redheads, blondes or ebony babes, Wives in Pantyhose has all the sexiest nylon wives masturbating. There are even pantyhose lesbians playing with each other using dildos while they orgasm in smoking hot pantyhose videos. Wives in Pantyhose is easily one the best collection of real wives engaging in pantyhose porn ever put together on the net. So if you have a housewife pantyhose fetish, the the website Wives in Pantyhose is sure to deliver for you all the best models and porn the Reality Kings network has to offer.',
parameters: null,
slug: 'wivesinpantyhose',
hasLogo: false,
parent: 'realitykings',
},
{
@ -4740,6 +4758,7 @@ const sites = [
description: "What's better than a Saturday Night out partying$18 Taking home a beautiful chick at the end of the night to fuck and have your way with! Reality Kings presents Saturday Night Latinas, gorgeous babes from the steamy night clubs and streets of Brazil. These hotties may have left the club, but the real party is about to begin! Real latina girls sucking and fucking after a night of partying! From deliciously round asses to amazing tan lines, these Brazilian bombshells are sure to please. Browse our videos below to download free latina porn movies and pictures. We have hundreds of latina sex scenes available for you to download. Grab your bags and get ready to head to Brazil, Reality Kings invites you to take home a Saturday Night Latina of your very own. Hot latina babes who love to party, join us today for a steamy Saturday Night out!",
parameters: null,
slug: 'saturdaynightlatinas',
hasLogo: false,
parent: 'realitykings',
},
{
@ -4748,6 +4767,7 @@ const sites = [
description: 'There are big natural breasts, then there are Extreme Naturals. On this site, we say, "Go big or go home!" That\'s why we only deliver massive naturals straight from the best Reality Kings has to offer. Extreme Naturals has painstakingly combed the RK network for the best giant naturals models and the hottest big naturals videos with the most hardcore XXX. These sexy babes have giant naturals that bounce while they ride cock and while they get stroked from behind doggy style in their perfect porn asses. For true fans of huge natural breasts, be sure to watch tons of free big naturals videos exclusively available as Extreme Naturals trailers on the website. Whether you like your giant naturals to be on Latinas, MILFs, college babes, blondes, teens or ebony babes, Extreme Naturals has the best collection of massive naturals straight from the vaults of Reality Kings.',
parameters: null,
slug: 'extremenaturals',
hasLogo: false,
parent: 'realitykings',
},
{
@ -4789,14 +4809,16 @@ const sites = [
description: 'Have you been spying on that hot couple next door$26 See My Wife invites you to view the private porn collection of horny amateurs everywhere! We\'re talking about 100% user submitted movies and pictures. Real women appearing in the hottest wife sex scenes around, that is what See My Wife is about. Our users have a chance to make 0 for pics and 00 for videos when they submit their homemade content. If you\'ve ever said "I wish I could bang my wife on film and get paid for it," look no further! Reality Kings considers every submission when we post new episodes. Check out some of our free pics and trailers below, this is one amazing collection of girlfriend and wife sex scenes. Every week we post a new episode crammed with four incredible babes showing off in front of the camera. No need to spy on the couple next door when you come See My Wife!',
parameters: null,
slug: 'seemywife',
hasLogo: false,
parent: 'realitykings',
},
{
name: 'Girls of Naked',
slug: 'girlsofnaked',
url: 'https://www.realitykings.com/scenes?site=18',
description: 'Nothing is hotter than voluptuous minxes who love getting naked. Girlsofnaked.com is home to a bevy of bodacious beauties who are all about showing as much skin to whomever is willing to satisfy their sexual desires. Our 18+ pornstars are daring and always curious for new carnal adventures in HD porn videos. Reality Kings has compiled an incredible assortment of erotica with big boob naughty nymphos. Watch them squeeze their perky nipples before rubbing their ticklish clits in steamy scenes. Our deviant divas need their juicy pussies stuffed 24/7 by the biggest cocks in the adult biz and will stop at nothing to devour as much man meat as they can fit into every hungry orifice. Girls of Naked celebrate nudity and hardcore sex in all its glory. Fetishes, orgies, bukkake, anal creampies and much more are their favorite pastimes. RK has full-length premium porno movies bursting with our luscious babes bursting out of their clothes just for you!',
parameters: null,
slug: 'girlsofnaked',
hasLogo: false,
parent: 'realitykings',
},
{

View File

@ -152,6 +152,48 @@ const studios = [
url: 'https://www.legalporno.com/studios/nf-studio',
parent: 'legalporno',
},
{
slug: 'natashateenproductions',
name: 'Natasha Teen Productions',
url: 'https://www.legalporno.com/studios/natasha-teen-productions',
parent: 'legalporno',
},
{
slug: 'mixedstudios',
name: 'Mixed Studios',
url: 'https://www.legalporno.com/studios/mixed-studios',
parent: 'legalporno',
},
{
slug: 'claudiasclips',
name: 'Claudia\'s Clips',
url: 'https://www.legalporno.com/studios/claudia--s-clips',
parent: 'legalporno',
},
{
slug: 'rebeccasclips',
name: 'Rebecca\'s Clips',
url: 'https://www.legalporno.com/studios/rebecca--s-clips',
parent: 'legalporno',
},
{
slug: 'private',
name: 'Private',
url: 'https://www.legalporno.com/studios/private',
parent: 'legalporno',
},
{
slug: 'privatecastings',
name: 'Private Castings',
url: 'https://www.legalporno.com/studios/private-castings',
parent: 'legalporno',
},
{
slug: 'privateblack',
name: 'Private Black',
url: 'https://www.legalporno.com/studios/private-black',
parent: 'legalporno',
},
];
/* eslint-disable max-len */
@ -171,5 +213,5 @@ exports.seed = knex => Promise.resolve()
has_logo: studio.hasLogo || false,
}));
return upsert('entities', studiosWithNetwork, 'slug', knex);
return upsert('entities', studiosWithNetwork, ['slug', 'type'], knex);
});

View File

@ -633,6 +633,7 @@ const tagPosters = [
['oral-creampie', 0, 'Henessy in "B(ass)t Friends" for Asshole Fever'],
['orgy', 1, 'Megan Rain (DP), Morgan Lee (anal), Jessa Rhodes, Melissa Moore and Kimmy Granger in "Orgy Masters 8" for Jules Jordan'],
['piercings', 0, 'Kaegune in "When The Sun Goes Down" for Suicide Girls'],
['piss-drinking', 0, 'Scarlet Domingo in LegalPorno GL227'],
['pussy-eating', 0, 'Kali Roses licking Emily Willis\' pussy in "Peeping On My Neighbor" for Girl Girl'],
['redhead', 1, 'Lacy Lennon in "Girl Crush" for When Girls Play'],
['squirting', 0, 'Veronica Rodriguez in "Hot Latina Squirting" for Jules Jordan'],

View File

@ -9,7 +9,7 @@ const slugify = require('../utils/slugify');
function extractTitle(originalTitle) {
const titleComponents = originalTitle.split(' ');
const sceneIdMatch = titleComponents.slice(-1)[0].match(/(AB|AF|GP|SZ|IV|GIO|RS|TW|MA|FM|SAL|NR|AA|GL|BZ|FS|KS|OTS|NF)\d+/); // detect studio prefixes
const sceneIdMatch = titleComponents.slice(-1)[0].match(/(AB|AF|GP|SZ|IV|GIO|RS|TW|MA|FM|SAL|NR|AA|GL|BZ|FS|KS|OTS|NF|NT|AX|RV)\d+/); // detect studio prefixes
const shootId = sceneIdMatch ? sceneIdMatch[0] : null;
const title = sceneIdMatch ? titleComponents.slice(0, -1).join(' ') : originalTitle;

View File

@ -1,110 +0,0 @@
'use strict';
const logger = require('./logger')(__filename);
const knex = require('./knex');
const whereOr = require('./utils/where-or');
async function curateTag(tag) {
const [aliases, media] = await Promise.all([
knex('tags').where({ alias_for: tag.id }),
knex('media')
.where('domain', 'tags')
.andWhere('target_id', tag.id)
.orderBy('index'),
]);
return {
id: tag.id,
name: tag.name,
slug: tag.slug,
description: tag.description,
poster: media.find(photo => photo.role === 'poster'),
photos: media.filter(photo => photo.role === 'photo'),
group: {
id: tag.group_id,
name: tag.group_name,
description: tag.group_description,
slug: tag.group_slug,
},
aliases: aliases.map(({ name }) => name),
};
}
function curateTags(tags) {
return Promise.all(tags.map(async tag => curateTag(tag)));
}
async function matchTags(rawTags) {
const filteredTags = rawTags.filter(Boolean);
const tags = filteredTags
.concat(filteredTags.map(tag => tag.toLowerCase()))
.concat(filteredTags.map(tag => tag.toUpperCase()));
const tagEntries = await knex('tags')
.pluck('aliases.id')
.whereIn('tags.name', tags)
.leftJoin('tags as aliases', function join() {
this
.on('tags.alias_for', 'aliases.id')
.orOn('tags.id', 'aliases.id');
})
.where(function where() {
this
.whereNull('tags.alias_for')
.orWhereNull('aliases.alias_for');
})
.groupBy('aliases.id');
return tagEntries;
}
async function associateTags(release, releaseId) {
const siteTags = release.site?.tags?.filter(tag => tag.inherit === true).map(tag => tag.id) || [];
const rawReleaseTags = release.tags?.filter(Boolean) || [];
const releaseTags = rawReleaseTags.some(tag => typeof tag === 'string')
? await matchTags(release.tags) // scraper returned raw tags
: rawReleaseTags; // tags already matched by (outdated) scraper
const tags = Array.from(new Set(releaseTags.concat(siteTags)));
if (tags.length === 0) {
logger.info(`No tags available for (${release.site.name}, ${releaseId}) "${release.title}"`);
return;
}
const associationEntries = await knex('releases_tags')
.where('release_id', releaseId)
.whereIn('tag_id', tags);
const existingAssociations = new Set(associationEntries.map(association => association.tag_id));
const newAssociations = tags.filter(tagId => !existingAssociations.has(tagId));
await knex('releases_tags').insert(newAssociations.map(tagId => ({
tag_id: tagId,
release_id: releaseId,
})));
}
async function fetchTags(queryObject, groupsQueryObject, limit = 100) {
const tags = await knex('tags')
.where(builder => whereOr(queryObject, 'tags', builder))
.orWhere(builder => whereOr(groupsQueryObject, 'tags_groups', builder))
.andWhere({ 'tags.alias_for': null })
.select(
'tags.*',
'tags_groups.id as group_id', 'tags_groups.name as group_name', 'tags_groups.slug as group_slug', 'tags_groups.description as groups_description',
)
.leftJoin('tags_groups', 'tags.group_id', 'tags_groups.id')
.orderBy('name')
.limit(limit);
return curateTags(tags);
}
module.exports = {
associateTags,
fetchTags,
matchTags,
};