diff --git a/assets/css/theme.css b/assets/css/theme.css
index f91e6ca..96d48ce 100644
--- a/assets/css/theme.css
+++ b/assets/css/theme.css
@@ -29,6 +29,7 @@
--background-level-20: #eee;
--background-level-30: #eee;
--background-dim: var(--shadow-weak-10);
+ --background-error: rgba(255, 0, 0, .1);
--shadow-weak-50: rgba(0, 0, 0, .02);
--shadow-weak-40: rgba(0, 0, 0, .05);
diff --git a/assets/summary.yaml b/assets/summary.yaml
index 6347994..debe0a9 100644
--- a/assets/summary.yaml
+++ b/assets/summary.yaml
@@ -1,8 +1,14 @@
-- ' - ':
+- delimit: ' - '
+ items:
- channel
- - - movie
- - scene|Scene $
+ - items:
+ - movie
+ - scene
- title
-- ', |(|)':
- - actors
- - date|yyyy-MM-dd
+- delimit: ', '
+ wrap: ['(', ')']
+ items:
+ - key: actors
+ genders: fmtou
+ - key: date
+ format: yyyy-MM-dd
diff --git a/components/scenes/edit-summary.vue b/components/scenes/edit-summary.vue
new file mode 100644
index 0000000..0f1b8e4
--- /dev/null
+++ b/components/scenes/edit-summary.vue
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
diff --git a/pages/scene/+Page.vue b/pages/scene/+Page.vue
index 5561e1f..960c07c 100644
--- a/pages/scene/+Page.vue
+++ b/pages/scene/+Page.vue
@@ -314,6 +314,13 @@
@focus="$event.target.select()"
>
+
+
+
+
@@ -331,7 +344,9 @@
import { ref, computed, inject } from 'vue';
import { formatDate, formatDuration } from '#/utils/format.js';
+import events from '#/src/events.js';
import getPath from '#/src/get-path.js';
+import processSummaryTemplate from '#/utils/process-summary-template.js';
import ActorTile from '#/components/actors/tile.vue';
import MovieTile from '#/components/movies/tile.vue';
@@ -339,8 +354,7 @@ import SerieTile from '#/components/series/tile.vue';
import Player from '#/components/video/player.vue';
import Heart from '#/components/stashes/heart.vue';
import Campaign from '#/components/campaigns/campaign.vue';
-
-import summaryTemplate from '#/assets/summary.yaml';
+import EditSummary from '#/components/scenes/edit-summary.vue';
const { pageProps, campaigns } = inject('pageContext');
const { scene } = pageProps;
@@ -348,6 +362,8 @@ const { scene } = pageProps;
const playing = ref(false);
const paused = ref(false);
+const showSummaryDialog = ref(false);
+
const qualities = {
2160: '4K',
1440: 'Quad HD',
@@ -375,62 +391,24 @@ const poster = computed(() => {
return null;
});
-const propProcessors = {
- channel: (sceneInfo) => sceneInfo.channel?.name || sceneInfo.network?.name,
- network: (sceneInfo) => sceneInfo.network?.name || sceneInfo.channel?.name,
- actors: (sceneInfo) => sceneInfo.actors.map((actor) => actor.name),
- movie: (sceneInfo) => sceneInfo.movies[0]?.title,
- date: (sceneInfo, format) => formatDate(sceneInfo.effectiveDate, format),
-};
-
-function processTemplate(chain, delimit = ' ', wrapOpen = '', wrapClose = '') {
- const results = chain.reduce((result, item) => {
- if (typeof item === 'string') {
- const [prop, format] = item.split('|');
- const value = propProcessors[prop]?.(scene, format) || scene[prop];
-
- if (Array.isArray(value)) {
- return result.concat(value.join(format || ', '));
- }
-
- return result.concat(value);
- }
-
- if (Array.isArray(item)) {
- const values = processTemplate(item, ', ');
-
- return result.concat(values);
- }
-
- if (typeof item === 'object') {
- const [meta, items] = Object.entries(item)[0];
- const [delimiter, wrapStart, wrapEnd] = meta.split('|');
- const values = processTemplate(items, delimiter, wrapStart, wrapEnd);
-
- return result.concat(values);
- }
-
- return [];
- }, []);
-
- if (results.length > 0) {
- return `${wrapOpen}${results.filter(Boolean).join(delimit)}${wrapClose}`;
- }
-
- return '';
-}
-
const summary = (() => {
try {
- return processTemplate(summaryTemplate);
+ const result = processSummaryTemplate(scene);
+
+ return result;
} catch (error) {
- console.error(`Failed to process template: ${error.message}`);
+ console.error(`Failed to process summary template: ${error.message}`);
return null;
}
})();
function copySummary() {
navigator.clipboard.writeText(summary);
+
+ events.emit('feedback', {
+ type: 'success',
+ message: 'Summary copied to clipboard',
+ });
}
diff --git a/src/scenes.js b/src/scenes.js
index 221d924..51318c4 100644
--- a/src/scenes.js
+++ b/src/scenes.js
@@ -97,6 +97,7 @@ function curateScene(rawScene, assets) {
id: tag.id,
slug: tag.slug,
name: tag.name,
+ priority: tag.priority,
})),
qualities: rawScene.qualities?.sort((qualityA, qualityB) => qualityB - qualityA) || [],
movies: assets.movies.map((movie) => ({
@@ -176,7 +177,7 @@ export async function fetchScenesById(sceneIds, { reqUser, ...context } = {}) {
.whereIn('release_id', sceneIds)
.leftJoin('actors as directors', 'directors.id', 'releases_directors.director_id'),
tags: knex('releases_tags')
- .select('id', 'slug', 'name', 'release_id')
+ .select('id', 'slug', 'name', 'priority', 'release_id')
.leftJoin('tags', 'tags.id', 'releases_tags.tag_id')
.whereNotNull('tags.id')
.whereIn('release_id', sceneIds)
diff --git a/utils/process-summary-template.js b/utils/process-summary-template.js
new file mode 100644
index 0000000..58f7c00
--- /dev/null
+++ b/utils/process-summary-template.js
@@ -0,0 +1,85 @@
+import { format } from 'date-fns';
+import Cookies from 'js-cookie';
+import { parse } from 'yaml';
+
+import slugify from '#/utils/slugify.js';
+
+import defaultTemplate from '#/assets/summary.yaml';
+
+const cookies = Cookies.withConverter({
+ write: (value) => value,
+});
+
+const storedTemplate = cookies.get('summary');
+const template = storedTemplate
+ ? parse(JSON.parse(storedTemplate)?.custom)
+ : defaultTemplate;
+
+const genderMap = {
+ f: 'female',
+ m: 'male',
+ t: 'transsexual',
+ o: 'other',
+ u: null,
+};
+
+const propProcessors = {
+ channel: (sceneInfo) => sceneInfo.channel?.name || sceneInfo.network?.name,
+ network: (sceneInfo) => sceneInfo.network?.name || sceneInfo.channel?.name,
+ actors: (sceneInfo, options) => {
+ const genders = (options.genders || 'fmtou').split('').map((genderKey) => genderMap[genderKey]);
+
+ return sceneInfo.actors
+ .filter((actor) => genders.includes(actor.gender))
+ .map((actor) => actor.name);
+ },
+ tags: (sceneInfo, options) => sceneInfo.tags
+ .filter((tag) => {
+ if (options.include && !options.include.includes(tag.name) && !options.include.includes(tag.slug)) {
+ return false;
+ }
+
+ if (options.exclude?.includes(tag.name) || options.exclude?.includes(tag.slug)) {
+ return false;
+ }
+
+ if (options.priority && tag.priority < options.priority) {
+ return false;
+ }
+
+ return true;
+ })
+ .map((tag) => tag.name),
+ movie: (sceneInfo) => sceneInfo.movies[0]?.title,
+ date: (sceneInfo, options) => format(sceneInfo.effectiveDate, options.format || 'yyyy-MM-dd'),
+};
+
+export default function processReleaseTemplate(release, chain = template, delimit = ' ', wrapOpen = '', wrapClose = '') {
+ const results = chain.reduce((result, item) => {
+ const key = typeof item === 'string' ? item : item.key;
+
+ if (key) {
+ const value = propProcessors[key]?.(release, typeof item === 'string' ? { key } : item) || release[key];
+
+ if (Array.isArray(value)) {
+ return result.concat(value.join(item.delimit || ', '));
+ }
+
+ return result.concat(item.slugify ? slugify(value, item.slugify) : value);
+ }
+
+ if (item.items) {
+ const value = processReleaseTemplate(release, item.items, item.delimit, item.wrap?.[0] || '', item.wrap?.[1] || '');
+
+ return result.concat(item.slugify ? slugify(value, item.slugify) : value);
+ }
+
+ return [];
+ }, []);
+
+ if (results.length > 0) {
+ return `${wrapOpen}${results.filter(Boolean).join(delimit)}${wrapClose}`;
+ }
+
+ return '';
+}