Implemented negative filters in back-end, added basic fixed filters settings dialog.
This commit is contained in:
parent
30fdbbd737
commit
98c25cd24e
|
@ -124,6 +124,15 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li
|
||||||
|
v-close-popper
|
||||||
|
class="menu-item menu-button"
|
||||||
|
@click="showSettings = true"
|
||||||
|
>
|
||||||
|
<Icon icon="equalizer" />
|
||||||
|
Settings
|
||||||
|
</li>
|
||||||
|
|
||||||
<li
|
<li
|
||||||
class="menu-button menu-item logout"
|
class="menu-button menu-item logout"
|
||||||
@click="logout"
|
@click="logout"
|
||||||
|
@ -147,6 +156,11 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Settings
|
||||||
|
v-if="showSettings"
|
||||||
|
@close="showSettings = false"
|
||||||
|
/>
|
||||||
</header>
|
</header>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -160,6 +174,8 @@ import {
|
||||||
import navigate from '#/src/navigate.js';
|
import navigate from '#/src/navigate.js';
|
||||||
import { del } from '#/src/api.js';
|
import { del } from '#/src/api.js';
|
||||||
|
|
||||||
|
import Settings from '#/components/settings/settings.vue';
|
||||||
|
|
||||||
import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
import logo from '../../assets/img/logo.svg?raw'; // eslint-disable-line import/no-unresolved
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
|
@ -168,6 +184,7 @@ const user = pageContext.user;
|
||||||
const query = ref(pageContext.urlParsed.search.q || '');
|
const query = ref(pageContext.urlParsed.search.q || '');
|
||||||
const allowLogin = pageContext.env.allowLogin;
|
const allowLogin = pageContext.env.allowLogin;
|
||||||
const searchFocused = ref(false);
|
const searchFocused = ref(false);
|
||||||
|
const showSettings = ref(false);
|
||||||
|
|
||||||
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
|
const activePage = computed(() => pageContext.urlParsed.pathname.split('/')[1]);
|
||||||
const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`;
|
const currentPath = `${pageContext.urlParsed.pathnameOriginal}${pageContext.urlParsed.searchOriginal || ''}`;
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
<template>
|
||||||
|
<Dialog title="Settings">
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div class="dialog-section">
|
||||||
|
<ul class="tags nolist">
|
||||||
|
<li
|
||||||
|
v-for="tag in tags"
|
||||||
|
:key="`tag-${tag}`"
|
||||||
|
class="tags-item"
|
||||||
|
>
|
||||||
|
<label class="tag noselect">
|
||||||
|
<Checkbox
|
||||||
|
:checked="checkedTags.has(tag)"
|
||||||
|
@change="(checked) => toggleTag(tag, checked)"
|
||||||
|
/>{{ tag }}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
import Dialog from '#/components/dialog/dialog.vue';
|
||||||
|
import Checkbox from '#/components/form/checkbox.vue';
|
||||||
|
|
||||||
|
const cookies = Cookies.withConverter({
|
||||||
|
write: (value) => value,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tags = [
|
||||||
|
'anal',
|
||||||
|
'anal prolapse',
|
||||||
|
'bisexual',
|
||||||
|
'gay',
|
||||||
|
'pissing',
|
||||||
|
'transsexual',
|
||||||
|
];
|
||||||
|
|
||||||
|
const storedTags = cookies.get('tags');
|
||||||
|
const checkedTags = ref(new Set(storedTags ? JSON.parse(storedTags) : []));
|
||||||
|
|
||||||
|
function toggleTag(tag, isChecked) {
|
||||||
|
if (isChecked) {
|
||||||
|
checkedTags.value.add(tag);
|
||||||
|
} else {
|
||||||
|
checkedTags.value.delete(tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
cookies.set('tags', JSON.stringify(Array.from(checkedTags.value)), { expires: 400 }); // 100 years from now
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dialog-body {
|
||||||
|
padding: 1rem;
|
||||||
|
width: 30rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tags-item {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: flex;
|
||||||
|
padding: .5rem 0;
|
||||||
|
text-transform: capitalize;
|
||||||
|
|
||||||
|
.check-container {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -24,6 +24,7 @@
|
||||||
"config": "^3.3.9",
|
"config": "^3.3.9",
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
"convert": "^4.14.1",
|
"convert": "^4.14.1",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
"cron": "^3.1.6",
|
"cron": "^3.1.6",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
"floating-vue": "^5.2.2",
|
"floating-vue": "^5.2.2",
|
||||||
"ip-cidr": "^4.0.0",
|
"ip-cidr": "^4.0.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"knex": "^3.1.0",
|
"knex": "^3.1.0",
|
||||||
"manticoresearch": "^4.0.0",
|
"manticoresearch": "^4.0.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
|
@ -4627,9 +4629,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
"node_modules/cookie": {
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
|
@ -5732,14 +5734,6 @@
|
||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-session/node_modules/cookie": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/express-session/node_modules/cookie-signature": {
|
"node_modules/express-session/node_modules/cookie-signature": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
@ -5764,6 +5758,14 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/express/node_modules/cookie": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/path-to-regexp": {
|
"node_modules/express/node_modules/path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
@ -6833,6 +6835,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -13532,9 +13542,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cookie": {
|
"cookie": {
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
|
||||||
},
|
},
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
|
@ -14348,6 +14358,11 @@
|
||||||
"vary": "~1.1.2"
|
"vary": "~1.1.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||||
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
|
||||||
|
@ -14390,11 +14405,6 @@
|
||||||
"uid-safe": "~2.1.5"
|
"uid-safe": "~2.1.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="
|
|
||||||
},
|
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
@ -15149,6 +15159,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
|
||||||
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw=="
|
||||||
},
|
},
|
||||||
|
"js-cookie": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw=="
|
||||||
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
"config": "^3.3.9",
|
"config": "^3.3.9",
|
||||||
"connect-redis": "^7.1.1",
|
"connect-redis": "^7.1.1",
|
||||||
"convert": "^4.14.1",
|
"convert": "^4.14.1",
|
||||||
|
"cookie": "^0.6.0",
|
||||||
"cron": "^3.1.6",
|
"cron": "^3.1.6",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"date-fns": "^3.0.0",
|
"date-fns": "^3.0.0",
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
"express-session": "^1.18.0",
|
"express-session": "^1.18.0",
|
||||||
"floating-vue": "^5.2.2",
|
"floating-vue": "^5.2.2",
|
||||||
"ip-cidr": "^4.0.0",
|
"ip-cidr": "^4.0.0",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"knex": "^3.1.0",
|
"knex": "^3.1.0",
|
||||||
"manticoresearch": "^4.0.0",
|
"manticoresearch": "^4.0.0",
|
||||||
"markdown-it": "^14.0.0",
|
"markdown-it": "^14.0.0",
|
||||||
|
|
|
@ -11,6 +11,7 @@ export async function onBeforeRender(pageContext) {
|
||||||
...pageContext.urlQuery,
|
...pageContext.urlQuery,
|
||||||
scope: pageContext.routeParams.scope || 'latest',
|
scope: pageContext.routeParams.scope || 'latest',
|
||||||
actorId: Number(pageContext.routeParams.actorId),
|
actorId: Number(pageContext.routeParams.actorId),
|
||||||
|
tagFilter: pageContext.tagFilter,
|
||||||
}), {
|
}), {
|
||||||
page: Number(pageContext.routeParams.page) || 1,
|
page: Number(pageContext.routeParams.page) || 1,
|
||||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||||
|
|
|
@ -18,6 +18,7 @@ export async function onBeforeRender(pageContext) {
|
||||||
...pageContext.urlQuery,
|
...pageContext.urlQuery,
|
||||||
scope: pageContext.routeParams.scope || 'latest',
|
scope: pageContext.routeParams.scope || 'latest',
|
||||||
entityId: Number(entityId),
|
entityId: Number(entityId),
|
||||||
|
tagFilter: pageContext.tagFilter,
|
||||||
}), {
|
}), {
|
||||||
page: Number(pageContext.routeParams.page) || 1,
|
page: Number(pageContext.routeParams.page) || 1,
|
||||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||||
|
|
|
@ -24,11 +24,7 @@
|
||||||
v-else-if="scene.poster"
|
v-else-if="scene.poster"
|
||||||
class="poster-container"
|
class="poster-container"
|
||||||
>
|
>
|
||||||
<a
|
<div class="poster-link">
|
||||||
:href="getPath(scene.poster)"
|
|
||||||
target="_blank"
|
|
||||||
class="poster-link"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
:src="getPath(scene.poster, 'thumbnail')"
|
:src="getPath(scene.poster, 'thumbnail')"
|
||||||
:style="{ 'background-image': getPath(scene.poster, 'lazy') }"
|
:style="{ 'background-image': getPath(scene.poster, 'lazy') }"
|
||||||
|
@ -36,7 +32,7 @@
|
||||||
:height="scene.poster.height"
|
:height="scene.poster.height"
|
||||||
class="poster"
|
class="poster"
|
||||||
>
|
>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
@ -49,11 +45,7 @@
|
||||||
:key="`photo-${photo.id}`"
|
:key="`photo-${photo.id}`"
|
||||||
class="photo-container"
|
class="photo-container"
|
||||||
>
|
>
|
||||||
<a
|
<div class="photo-link">
|
||||||
:href="getPath(photo)"
|
|
||||||
target="_blank"
|
|
||||||
class="photo-link"
|
|
||||||
>
|
|
||||||
<img
|
<img
|
||||||
:src="getPath(photo, 'thumbnail')"
|
:src="getPath(photo, 'thumbnail')"
|
||||||
:style="{ 'background-image': getPath(photo, 'lazy') }"
|
:style="{ 'background-image': getPath(photo, 'lazy') }"
|
||||||
|
@ -61,7 +53,7 @@
|
||||||
:height="photo.height"
|
:height="photo.height"
|
||||||
class="photo"
|
class="photo"
|
||||||
>
|
>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,10 +91,21 @@
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
:href="scene.url"
|
||||||
|
target="_blank"
|
||||||
|
class="date nolink"
|
||||||
|
>
|
||||||
<time
|
<time
|
||||||
:datetime="scene.effectiveDate.toISOString()"
|
:datetime="scene.effectiveDate.toISOString()"
|
||||||
class="date ellipsis"
|
class="ellipsis compact-hide"
|
||||||
>{{ formatDate(scene.effectiveDate, 'MMMM d, y') }}</time>
|
>{{ formatDate(scene.effectiveDate, 'MMMM d, y') }}</time>
|
||||||
|
|
||||||
|
<time
|
||||||
|
:datetime="scene.effectiveDate.toISOString()"
|
||||||
|
class="ellipsis compact-show"
|
||||||
|
>{{ formatDate(scene.effectiveDate, 'MMM d, y') }}</time>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
|
@ -526,6 +529,10 @@ const poster = computed(() => {
|
||||||
margin-left: .25rem;
|
margin-left: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compact-show {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@media(--small) {
|
@media(--small) {
|
||||||
.content {
|
.content {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -606,6 +613,20 @@ const poster = computed(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media(--small-50) {
|
||||||
|
.compact-show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-size: .9rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media(--small-60) {
|
@media(--small-60) {
|
||||||
.actors {
|
.actors {
|
||||||
grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
|
grid-template-columns: repeat(auto-fill, minmax(6.5rem, 1fr));
|
||||||
|
|
|
@ -14,6 +14,7 @@ export async function onBeforeRender(pageContext) {
|
||||||
...pageContext.urlQuery,
|
...pageContext.urlQuery,
|
||||||
scope: pageContext.routeParams.scope || 'latest',
|
scope: pageContext.routeParams.scope || 'latest',
|
||||||
tagSlug: pageContext.routeParams.tagSlug,
|
tagSlug: pageContext.routeParams.tagSlug,
|
||||||
|
tagFilter: pageContext.tagFilter,
|
||||||
}), {
|
}), {
|
||||||
page: Number(pageContext.routeParams.page) || 1,
|
page: Number(pageContext.routeParams.page) || 1,
|
||||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import { fetchScenes } from '#/src/scenes.js';
|
import { fetchScenes } from '#/src/scenes.js';
|
||||||
|
import { curateScenesQuery } from '#/src/web/scenes.js';
|
||||||
|
|
||||||
export async function onBeforeRender(pageContext) {
|
export async function onBeforeRender(pageContext) {
|
||||||
const { scenes, limit, total } = await fetchScenes({
|
const { scenes, limit, total } = await fetchScenes(await curateScenesQuery({
|
||||||
|
...pageContext.urlQuery,
|
||||||
scope: pageContext.routeParams.scope || 'latest',
|
scope: pageContext.routeParams.scope || 'latest',
|
||||||
isShowcased: true,
|
isShowcased: true,
|
||||||
}, {
|
tagFilter: pageContext.tagFilter,
|
||||||
|
}), {
|
||||||
page: Number(pageContext.routeParams.page) || 1,
|
page: Number(pageContext.routeParams.page) || 1,
|
||||||
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
limit: Number(pageContext.urlParsed.search.limit) || 30,
|
||||||
aggregate: false,
|
aggregate: false,
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
<h3 class="heading">Stashes</h3>
|
<h3 class="heading">Stashes</h3>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
v-if="profile.id === user?.id"
|
||||||
class="button"
|
class="button"
|
||||||
@click="showStashDialog = true"
|
@click="showStashDialog = true"
|
||||||
>
|
>
|
||||||
|
@ -79,6 +80,7 @@ import StashTile from '#/components/stashes/tile.vue';
|
||||||
import Dialog from '#/components/dialog/dialog.vue';
|
import Dialog from '#/components/dialog/dialog.vue';
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
|
const user = pageContext.user;
|
||||||
const profile = ref(pageContext.pageProps.profile);
|
const profile = ref(pageContext.pageProps.profile);
|
||||||
|
|
||||||
const stashName = ref(null);
|
const stashName = ref(null);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import VueVirtualScroller from 'vue-virtual-scroller';
|
||||||
import FloatingVue from 'floating-vue';
|
import FloatingVue from 'floating-vue';
|
||||||
|
|
||||||
import { setPageContext } from './usePageContext.js';
|
import { setPageContext } from './usePageContext.js';
|
||||||
|
import initClient from './init-client.js';
|
||||||
|
|
||||||
import '../assets/css/style.css';
|
import '../assets/css/style.css';
|
||||||
|
|
||||||
|
@ -27,7 +28,6 @@ function createApp(Page, pageProps, pageContext) {
|
||||||
|
|
||||||
const app = createSSRApp(PageWithLayout);
|
const app = createSSRApp(PageWithLayout);
|
||||||
|
|
||||||
// We make pageContext available from any Vue component
|
|
||||||
setPageContext(app, pageContext);
|
setPageContext(app, pageContext);
|
||||||
|
|
||||||
app.provide('pageContext', pageContext);
|
app.provide('pageContext', pageContext);
|
||||||
|
@ -38,6 +38,10 @@ function createApp(Page, pageProps, pageContext) {
|
||||||
app.component('Link', Link);
|
app.component('Link', Link);
|
||||||
app.component('Icon', Icon);
|
app.component('Icon', Icon);
|
||||||
|
|
||||||
|
if (typeof window === 'object') {
|
||||||
|
initClient(pageContext);
|
||||||
|
}
|
||||||
|
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import Cookies from 'js-cookie';
|
||||||
|
|
||||||
|
const cookies = Cookies.withConverter({
|
||||||
|
write: (value) => value,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function init(_pageContext) {
|
||||||
|
// update tag filter to prevent unexpected expiry
|
||||||
|
const storedTags = cookies.get('tags');
|
||||||
|
|
||||||
|
if (storedTags) {
|
||||||
|
cookies.set('tags', storedTags, { expires: 400 });
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,10 @@
|
||||||
import redis from './redis.js';
|
import redis from './redis.js';
|
||||||
|
|
||||||
export async function getIdsBySlug(slugs, domain) {
|
export async function getIdsBySlug(slugs, domain) {
|
||||||
|
if (!slugs) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
const ids = await Promise.all(slugs.map(async (slug) => {
|
const ids = await Promise.all(slugs.map(async (slug) => {
|
||||||
if (!slug) {
|
if (!slug) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -9,7 +9,9 @@ export default function navigate(path, query, options = {}) {
|
||||||
}).toString();
|
}).toString();
|
||||||
|
|
||||||
const url = queryString
|
const url = queryString
|
||||||
? `${path}?${queryString.replace(/%2C/g, ',')}` // URLSearchParams encodes commas, we don't want that
|
? `${path}?${queryString
|
||||||
|
.replace(/%2C/g, ',') // URLSearchParams encodes commas and colons, we don't want that
|
||||||
|
.replace(/%3A/g, ':')}`
|
||||||
: path;
|
: path;
|
||||||
|
|
||||||
if (options.redirect) {
|
if (options.redirect) {
|
||||||
|
|
|
@ -439,10 +439,18 @@ async function queryManticoreSql(filters, options, _reqUser) {
|
||||||
builder.where('any(tag_ids)', tagId);
|
builder.where('any(tag_ids)', tagId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (filters.notTagIds) {
|
||||||
|
builder.whereNotIn('tag_ids', filters.notTagIds);
|
||||||
|
}
|
||||||
|
|
||||||
filters.actorIds?.forEach((actorId) => {
|
filters.actorIds?.forEach((actorId) => {
|
||||||
builder.where('any(actor_ids)', actorId);
|
builder.where('any(actor_ids)', actorId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (filters.notActorIds) {
|
||||||
|
builder.whereNotIn('actor_ids', filters.notActorIds);
|
||||||
|
}
|
||||||
|
|
||||||
if (filters.entityId) {
|
if (filters.entityId) {
|
||||||
builder.whereRaw('any(entity_ids) = ?', filters.entityId);
|
builder.whereRaw('any(entity_ids) = ?', filters.entityId);
|
||||||
|
|
||||||
|
@ -455,6 +463,10 @@ async function queryManticoreSql(filters, options, _reqUser) {
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (filters.notEntityIds) {
|
||||||
|
builder.whereNotIn('entity_ids', filters.notEntityIds);
|
||||||
|
}
|
||||||
|
|
||||||
if (filters.movieId) {
|
if (filters.movieId) {
|
||||||
builder.whereRaw('any(movie_ids) = ?', filters.movieId);
|
builder.whereRaw('any(movie_ids) = ?', filters.movieId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,36 @@ import { stringify } from '@brillout/json-serializer/stringify'; /* eslint-disab
|
||||||
import { fetchScenes } from '../scenes.js';
|
import { fetchScenes } from '../scenes.js';
|
||||||
import { parseActorIdentifier } from '../query.js';
|
import { parseActorIdentifier } from '../query.js';
|
||||||
import { getIdsBySlug } from '../cache.js';
|
import { getIdsBySlug } from '../cache.js';
|
||||||
|
import slugify from '../../utils/slugify.js';
|
||||||
|
import promiseProps from '../../utils/promise-props.js';
|
||||||
|
|
||||||
export async function curateScenesQuery(query) {
|
export async function curateScenesQuery(query) {
|
||||||
|
const splitTags = query.tags?.split(',') || [];
|
||||||
|
const splitActors = query.actors?.split(',') || [];
|
||||||
|
const splitEntities = query.e?.split(',') || [];
|
||||||
|
const mainEntity = splitEntities.find((entity) => entity.charAt(0) !== '!');
|
||||||
|
|
||||||
|
const {
|
||||||
|
tagIds,
|
||||||
|
notTagIds,
|
||||||
|
entityId,
|
||||||
|
notEntityIds,
|
||||||
|
} = await promiseProps({
|
||||||
|
tagIds: getIdsBySlug([query.tagSlug, ...splitTags.filter((tag) => tag.charAt(0) !== '!')], 'tags'),
|
||||||
|
notTagIds: getIdsBySlug([...query.tagFilter, ...(splitTags.filter((tag) => tag.charAt(0) === '!').map((tag) => tag.slice(1)) || [])].map((tag) => slugify(tag)), 'tags'),
|
||||||
|
entityId: mainEntity ? await getIdsBySlug([mainEntity], 'entities').then(([id]) => id) : query.entityId,
|
||||||
|
notEntityIds: await getIdsBySlug(splitEntities.filter((entity) => entity.charAt(0) === '!').map((entity) => entity.slice(1)), 'entities'),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
scope: query.scope || 'latest',
|
scope: query.scope || 'latest',
|
||||||
query: query.q,
|
query: query.q,
|
||||||
actorIds: [query.actorId, ...(query.actors?.split(',') || []).map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
actorIds: [query.actorId, ...splitActors.filter((actor) => actor.charAt(0) !== '!').map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
||||||
tagIds: await getIdsBySlug([query.tagSlug, ...(query.tags?.split(',') || [])], 'tags'),
|
notActorIds: splitActors.filter((actor) => actor.charAt(0) === '!').map((identifier) => parseActorIdentifier(identifier.slice(1))?.id).filter(Boolean),
|
||||||
entityId: query.e ? await getIdsBySlug([query.e], 'entities').then(([id]) => id) : query.entityId,
|
tagIds,
|
||||||
|
notTagIds: notTagIds.filter((tagId) => !tagIds.includes(tagId)), // included tags get priority over excluded tags
|
||||||
|
entityId,
|
||||||
|
notEntityIds,
|
||||||
movieId: Number(query.movieId) || null,
|
movieId: Number(query.movieId) || null,
|
||||||
stashId: Number(query.stashId) || null,
|
stashId: Number(query.stashId) || null,
|
||||||
};
|
};
|
||||||
|
@ -24,7 +46,10 @@ export async function fetchScenesApi(req, res) {
|
||||||
aggChannels,
|
aggChannels,
|
||||||
limit,
|
limit,
|
||||||
total,
|
total,
|
||||||
} = await fetchScenes(await curateScenesQuery(req.query), {
|
} = await fetchScenes(await curateScenesQuery({
|
||||||
|
...req.query,
|
||||||
|
tagFilter: req.tagFilter,
|
||||||
|
}), {
|
||||||
page: Number(req.query.page) || 1,
|
page: Number(req.query.page) || 1,
|
||||||
limit: Number(req.query.limit) || 30,
|
limit: Number(req.query.limit) || 30,
|
||||||
}, req.user);
|
}, req.user);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Router from 'express-promise-router';
|
||||||
import session from 'express-session';
|
import session from 'express-session';
|
||||||
import RedisStore from 'connect-redis';
|
import RedisStore from 'connect-redis';
|
||||||
import compression from 'compression';
|
import compression from 'compression';
|
||||||
|
import cookie from 'cookie';
|
||||||
import { renderPage } from 'vike/server'; // eslint-disable-line import/extensions
|
import { renderPage } from 'vike/server'; // eslint-disable-line import/extensions
|
||||||
|
|
||||||
// import root from './root.js';
|
// import root from './root.js';
|
||||||
|
@ -58,6 +59,17 @@ export default async function initServer() {
|
||||||
router.use('/', express.static('static'));
|
router.use('/', express.static('static'));
|
||||||
router.use('/media', express.static(config.media.path));
|
router.use('/media', express.static(config.media.path));
|
||||||
|
|
||||||
|
router.use((req, res, next) => {
|
||||||
|
const cookies = cookie.parse(req.headers.cookie);
|
||||||
|
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
req.cookies = cookies;
|
||||||
|
req.tagFilter = cookies.tags ? JSON.parse(cookies.tags) : [];
|
||||||
|
/* eslint-enable no-param-reassign */
|
||||||
|
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
router.use(express.json());
|
router.use(express.json());
|
||||||
|
|
||||||
const redisStore = new RedisStore({
|
const redisStore = new RedisStore({
|
||||||
|
@ -129,6 +141,9 @@ export default async function initServer() {
|
||||||
const pageContextInit = {
|
const pageContextInit = {
|
||||||
urlOriginal: req.originalUrl,
|
urlOriginal: req.originalUrl,
|
||||||
urlQuery: req.query, // vike's own query does not apply boolean parser
|
urlQuery: req.query, // vike's own query does not apply boolean parser
|
||||||
|
headers: req.headers,
|
||||||
|
cookies: req.cookies,
|
||||||
|
tagFilter: req.tagFilter,
|
||||||
user: req.user && {
|
user: req.user && {
|
||||||
id: req.user.id,
|
id: req.user.id,
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
|
|
Loading…
Reference in New Issue