Upgraded dependencies, bumped to Node 24.
This commit is contained in:
@@ -1,3 +1,42 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import ActorSearch from '#/components/actors/search.vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['actors']);
|
||||
|
||||
const newActors = ref([]);
|
||||
|
||||
function addActor(actor) {
|
||||
newActors.value = newActors.value.concat(actor);
|
||||
|
||||
emit('actors', props.edits.actors.concat(actor.id));
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => {
|
||||
newActors.value = [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
class="actors nolist"
|
||||
@@ -40,43 +79,6 @@
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import ActorSearch from '#/components/actors/search.vue';
|
||||
|
||||
const newActors = ref([]);
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['actors']);
|
||||
|
||||
function addActor(actor) {
|
||||
newActors.value = newActors.value.concat(actor);
|
||||
|
||||
emit('actors', props.edits.actors.concat(actor.id));
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => { newActors.value = []; });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.actors {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,23 @@
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['augmentation']);
|
||||
const augmentation = reactive(props.edits.augmentation);
|
||||
|
||||
watch(augmentation, () => emit('augmentation', augmentation));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="figure augmentation"
|
||||
@@ -208,23 +228,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['augmentation']);
|
||||
const augmentation = reactive(props.edits.augmentation);
|
||||
|
||||
watch(augmentation, () => emit('augmentation', augmentation));
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
<script setup>
|
||||
import { format } from 'date-fns';
|
||||
import getPath from '#/src/get-path.js';
|
||||
|
||||
defineProps({
|
||||
avatar: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="avatar noshrink">
|
||||
<img
|
||||
@@ -37,18 +49,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { format } from 'date-fns';
|
||||
import getPath from '#/src/get-path.js';
|
||||
|
||||
defineProps({
|
||||
avatar: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.avatar {
|
||||
display: inline-block;
|
||||
|
||||
@@ -1,104 +1,3 @@
|
||||
<template>
|
||||
<div class="figure">
|
||||
<div class="value-section">
|
||||
<span class="value-label">Units</span>
|
||||
|
||||
<select
|
||||
:value="units"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
@change="emit('units', $event.target.value)"
|
||||
>
|
||||
<option value="us">USA</option>
|
||||
<option value="uk">UK</option>
|
||||
<option value="eu">Europe/Asia</option>
|
||||
<option value="jp">Japan</option>
|
||||
<option value="au">Australia</option>
|
||||
<option value="it">Italy</option>
|
||||
<option value="fr">France</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span class="figure-bust">
|
||||
<div class="value-section">
|
||||
<span class="value-label">Bust</span>
|
||||
|
||||
<select
|
||||
v-model="figure.bust"
|
||||
class="select input"
|
||||
placeholder="Bust"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
<option
|
||||
:key="`figure-bust-unknown`"
|
||||
:value="null"
|
||||
/>
|
||||
|
||||
<option
|
||||
v-for="bust in bustSizes[units]"
|
||||
:key="`figure-bust-${bust}`"
|
||||
:value="Array.isArray(bust) ? bust[0] : bust"
|
||||
>{{ Array.isArray(bust) ? bust.join('/') : bust }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Cup</span>
|
||||
|
||||
<select
|
||||
v-model="figure.cup"
|
||||
class="select input"
|
||||
placeholder="Cup"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
<option
|
||||
:key="`figure-cup-unknown`"
|
||||
:value="null"
|
||||
/>
|
||||
|
||||
<option
|
||||
v-for="cup in cupSizes[units]"
|
||||
:key="`figure-cup-${cup}`"
|
||||
:value="Array.isArray(cup) ? cup[0] : cup"
|
||||
>{{ Array.isArray(cup) ? cup.join('/') : cup }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Waist</span>
|
||||
|
||||
<span>
|
||||
<input
|
||||
v-model="figure.waist"
|
||||
type="number"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
|
||||
<template v-if="['us', 'uk'].includes(units)"> inch</template>
|
||||
<template v-else> cm</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Hip</span>
|
||||
|
||||
<span>
|
||||
<input
|
||||
v-model="figure.hip"
|
||||
type="number"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
|
||||
<template v-if="['us', 'uk'].includes(units)"> inch</template>
|
||||
<template v-else> cm</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
@@ -145,3 +44,104 @@ const bustSizes = {
|
||||
bustSizes.uk = bustSizes.us;
|
||||
bustSizes.jp = bustSizes.eu;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="figure">
|
||||
<div class="value-section">
|
||||
<span class="value-label">Units</span>
|
||||
|
||||
<select
|
||||
:value="units"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
@change="emit('units', $event.target.value)"
|
||||
>
|
||||
<option value="us">USA</option>
|
||||
<option value="uk">UK</option>
|
||||
<option value="eu">Europe/Asia</option>
|
||||
<option value="jp">Japan</option>
|
||||
<option value="au">Australia</option>
|
||||
<option value="it">Italy</option>
|
||||
<option value="fr">France</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<span class="figure-bust">
|
||||
<div class="value-section">
|
||||
<span class="value-label">Bust</span>
|
||||
|
||||
<select
|
||||
v-model="figure.bust"
|
||||
class="select input"
|
||||
placeholder="Bust"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
<option
|
||||
key="figure-bust-unknown"
|
||||
:value="null"
|
||||
/>
|
||||
|
||||
<option
|
||||
v-for="bust in bustSizes[units]"
|
||||
:key="`figure-bust-${bust}`"
|
||||
:value="Array.isArray(bust) ? bust[0] : bust"
|
||||
>{{ Array.isArray(bust) ? bust.join('/') : bust }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Cup</span>
|
||||
|
||||
<select
|
||||
v-model="figure.cup"
|
||||
class="select input"
|
||||
placeholder="Cup"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
<option
|
||||
key="figure-cup-unknown"
|
||||
:value="null"
|
||||
/>
|
||||
|
||||
<option
|
||||
v-for="cup in cupSizes[units]"
|
||||
:key="`figure-cup-${cup}`"
|
||||
:value="Array.isArray(cup) ? cup[0] : cup"
|
||||
>{{ Array.isArray(cup) ? cup.join('/') : cup }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Waist</span>
|
||||
|
||||
<span>
|
||||
<input
|
||||
v-model="figure.waist"
|
||||
type="number"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
|
||||
<template v-if="['us', 'uk'].includes(units)"> inch</template>
|
||||
<template v-else> cm</template>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="value-section">
|
||||
<span class="value-label">Hip</span>
|
||||
|
||||
<span>
|
||||
<input
|
||||
v-model="figure.hip"
|
||||
type="number"
|
||||
class="input"
|
||||
:disabled="!editing.has('figure')"
|
||||
>
|
||||
|
||||
<template v-if="['us', 'uk'].includes(units)"> inch</template>
|
||||
<template v-else> cm</template>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import MovieSearch from '#/components/movies/search.vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['movies']);
|
||||
|
||||
const newMovies = ref([]);
|
||||
|
||||
function addMovie(movie) {
|
||||
newMovies.value = newMovies.value.concat(movie);
|
||||
|
||||
console.log(movie);
|
||||
|
||||
emit('movies', props.edits.movies.concat(movie.id));
|
||||
|
||||
console.log(props.edits);
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => {
|
||||
newMovies.value = [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
class="movies nolist"
|
||||
@@ -40,47 +83,6 @@
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import MovieSearch from '#/components/movies/search.vue';
|
||||
|
||||
const newMovies = ref([]);
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['movies']);
|
||||
|
||||
function addMovie(movie) {
|
||||
newMovies.value = newMovies.value.concat(movie);
|
||||
|
||||
console.log(movie);
|
||||
|
||||
emit('movies', props.edits.movies.concat(movie.id));
|
||||
|
||||
console.log(props.edits);
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => { newMovies.value = []; });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.movies {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
units: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['penis', 'units']);
|
||||
const penis = reactive(props.edits.penis);
|
||||
|
||||
watch(penis, () => emit('penis', penis));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="figure penis"
|
||||
@@ -83,27 +107,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
units: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['penis', 'units']);
|
||||
const penis = reactive(props.edits.penis);
|
||||
|
||||
watch(penis, () => emit('penis', penis));
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,43 @@
|
||||
<script setup>
|
||||
import { inject, reactive, watch } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['place']);
|
||||
const pageContext = inject('pageContext');
|
||||
const countries = pageContext.pageProps.countries;
|
||||
|
||||
const place = reactive(props.edits[props.item.key]);
|
||||
|
||||
watch(place, () => emit('place', place));
|
||||
|
||||
const topCountries = [
|
||||
'AU',
|
||||
'BR',
|
||||
'CZ',
|
||||
'DE',
|
||||
'JP',
|
||||
'RU',
|
||||
'GB',
|
||||
'US',
|
||||
];
|
||||
|
||||
const sortedCountries = countries.toSorted((countryA, countryB) => topCountries.indexOf(countryB.alpha2) - topCountries.indexOf(countryA.alpha2));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="place"
|
||||
@@ -35,43 +75,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch, inject } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const countries = pageContext.pageProps.countries;
|
||||
|
||||
const emit = defineEmits(['place']);
|
||||
const place = reactive(props.edits[props.item.key]);
|
||||
|
||||
watch(place, () => emit('place', place));
|
||||
|
||||
const topCountries = [
|
||||
'AU',
|
||||
'BR',
|
||||
'CZ',
|
||||
'DE',
|
||||
'JP',
|
||||
'RU',
|
||||
'GB',
|
||||
'US',
|
||||
];
|
||||
|
||||
const sortedCountries = countries.toSorted((countryA, countryB) => topCountries.indexOf(countryB.alpha2) - topCountries.indexOf(countryA.alpha2));
|
||||
</script>
|
||||
|
||||
@@ -1,3 +1,42 @@
|
||||
<script setup>
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
import { inject } from 'vue';
|
||||
|
||||
defineProps({
|
||||
rev: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
socials: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return social.url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="socials nolist">
|
||||
<li
|
||||
@@ -38,46 +77,6 @@
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
defineProps({
|
||||
rev: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
socials: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return social.url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.socials {
|
||||
display: flex;
|
||||
|
||||
@@ -1,235 +1,6 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<div
|
||||
v-if="context === 'admin'"
|
||||
class="revs-header"
|
||||
>
|
||||
<Checkbox
|
||||
label="Show finalized"
|
||||
:checked="showReviewed"
|
||||
@change="(checked) => { showReviewed = checked; reloadRevisions(); }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="revs nolist"
|
||||
:class="{ [`revs-${context}`]: true }"
|
||||
>
|
||||
<li
|
||||
v-for="rev in curatedRevisions"
|
||||
:key="`rev-${rev.id}`"
|
||||
class="rev"
|
||||
:class="{
|
||||
reviewed: reviewedRevisions.has(rev.id),
|
||||
expanded: context === 'admin' || expanded.has(rev.id)
|
||||
}"
|
||||
>
|
||||
<template v-if="context === 'admin' || expanded.has(rev.id)">
|
||||
<div class="rev-header">
|
||||
<a
|
||||
:href="`/${domain.slice(0, -1)}/${rev.sceneId || rev.actorId}/${rev.base.slug}`"
|
||||
target="_blank"
|
||||
class="rev-link rev-scene nolink noshrink"
|
||||
>{{ rev.sceneId || rev.actorId }}@{{ rev.hash.slice(0, 6) }}</a>
|
||||
|
||||
<a
|
||||
:href="`/${domain.slice(0, -1)}/${rev.sceneId || rev.actorId}/${rev.base.slug}`"
|
||||
target="_blank"
|
||||
class="rev-link rev-title nolink ellipsis"
|
||||
>{{ rev.base.title || rev.base.name }}</a>
|
||||
|
||||
<div class="rev-details noshrink">
|
||||
<a
|
||||
v-if="rev.user"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-username nolink"
|
||||
>{{ rev.user.username }}</a>
|
||||
|
||||
<time
|
||||
:datetime="rev.createdAt"
|
||||
class="rev-created"
|
||||
>{{ format(rev.createdAt, 'yyyy-MM-dd hh:mm') }}</time>
|
||||
</div>
|
||||
|
||||
<div class="rev-actions noshrink">
|
||||
<span
|
||||
v-if="rev.review && context === 'admin'"
|
||||
class="approved"
|
||||
:class="{ rejected: !rev.review.isApproved }"
|
||||
>{{ rev.review.isApproved ? 'Approved' : 'Rejected' }} by <a
|
||||
:href="`/user/${rev.review.username}`"
|
||||
target="_blank"
|
||||
class="nolink"
|
||||
>{{ rev.review.username }}</a> {{ format(rev.review.reviewedAt, 'yyyy-MM-dd hh:mm') }}</span>
|
||||
|
||||
<template v-else-if="context === 'admin'">
|
||||
<Icon
|
||||
v-tooltip="`Ban user from submitting revisions`"
|
||||
icon="user-block"
|
||||
class="review-reject review-ban"
|
||||
@click="banEditor(rev)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-tooltip="`Reject revision`"
|
||||
icon="blocked"
|
||||
class="review-reject"
|
||||
@click="reviewRevision(rev, false)"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-model="feedbacks[rev.id]"
|
||||
placeholder="Feedback"
|
||||
class="input"
|
||||
>
|
||||
|
||||
<Icon
|
||||
v-tooltip="`Approve and apply revision`"
|
||||
icon="checkmark"
|
||||
class="review-approve"
|
||||
@click="reviewRevision(rev, true)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="rev-deltas">
|
||||
<li
|
||||
v-for="(delta, index) in rev.deltas"
|
||||
:key="`delta-${rev.id}-${index}`"
|
||||
class="delta"
|
||||
>
|
||||
<span class="delta-key ellipsis">{{ delta.key }}</span>
|
||||
|
||||
<div class="delta-deltas">
|
||||
<span class="delta-from delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="rev.base[delta.key]"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-else-if="Array.isArray(rev.base[delta.key])"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
v-for="item in rev.base[delta.key]"
|
||||
:key="`item-${rev.id}-${index}-${item.id}`"
|
||||
class="delta-item"
|
||||
:class="{ modified: item.modified }"
|
||||
>{{ item.name || item.id || item }}</li> ]
|
||||
</ul>
|
||||
|
||||
<Avatar
|
||||
v-else-if="delta.key === 'avatar'"
|
||||
:avatar="avatarsById[rev.base[delta.key]]"
|
||||
class="delta-avatar"
|
||||
/>
|
||||
|
||||
<template v-else-if="rev.base[delta.key] instanceof Date">{{ format(rev.base[delta.key], 'yyyy-MM-dd hh:mm') }}</template>
|
||||
<template v-else>{{ rev.base[delta.key] }}</template>
|
||||
</span>
|
||||
|
||||
<span class="delta-arrow">⇒</span>
|
||||
|
||||
<span class="delta-to delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="delta.value"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-else-if="Array.isArray(delta.value)"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
v-for="item in delta.value"
|
||||
:key="`item-${rev.id}-${index}-${item.id}`"
|
||||
class="delta-item"
|
||||
:class="{ modified: item.modified }"
|
||||
>{{ item.name || item.id || item }}</li> ]
|
||||
</ul>
|
||||
|
||||
<Avatar
|
||||
v-else-if="delta.key === 'avatar'"
|
||||
:avatar="avatarsById[delta.value]"
|
||||
class="delta-avatar"
|
||||
/>
|
||||
|
||||
<template v-else-if="delta.value instanceof Date">{{ format(delta.value, 'yyyy-MM-dd hh:mm') }}</template>
|
||||
<template v-else>{{ delta.value }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="rev.comment"
|
||||
class="rev-comment"
|
||||
>
|
||||
{{ rev.comment }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="rev-compact"
|
||||
@click="expanded.add(rev.id)"
|
||||
>
|
||||
|
||||
<span class="rev-scene nolink noshrink"><template v-if="context !== 'scene' && context !== 'actor'">{{ rev.sceneId || rev.actorId }}</template>@{{ rev.hash.slice(0, 6) }}</span>
|
||||
|
||||
<span
|
||||
v-if="context !== 'scene'"
|
||||
class="rev-title nolink ellipsis"
|
||||
>{{ rev.base.title }}</span>
|
||||
|
||||
<span class="rev-summary">
|
||||
<span class="summary-deltas ellipsis">{{ rev.deltas.map((delta) => delta.key).join(', ') }}</span>
|
||||
|
||||
<span
|
||||
v-if="rev.comment"
|
||||
class="summary-comment ellipsis"
|
||||
>{{ rev.comment }}</span>
|
||||
</span>
|
||||
|
||||
<div class="rev-details noshrink">
|
||||
<a
|
||||
v-if="rev.user && context !== 'user'"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-username nolink ellipsis"
|
||||
@click.stop
|
||||
>{{ rev.user.username }}</a>
|
||||
|
||||
<a
|
||||
v-if="rev.user && context !== 'user'"
|
||||
v-tooltip="rev.user.username"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-avatar nolink ellipsis"
|
||||
@click.stop
|
||||
><Icon icon="user3-long" /></a>
|
||||
|
||||
<time
|
||||
:datetime="rev.createdAt"
|
||||
class="rev-created"
|
||||
>{{ format(rev.createdAt, 'yyyy-MM-dd hh:mm') }}</time>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject } from 'vue';
|
||||
import { format } from 'date-fns';
|
||||
import { computed, inject, ref } from 'vue';
|
||||
|
||||
import Avatar from '#/components/edit/avatar.vue';
|
||||
import Socials from '#/components/edit/revision-socials.vue';
|
||||
@@ -410,7 +181,8 @@ async function reviewRevision(revision, isApproved) {
|
||||
});
|
||||
|
||||
revisions.value = revisions.value.map((rev) => (rev.id === updatedRevision.revision.id ? updatedRevision.revision : rev));
|
||||
} catch (error) {
|
||||
}
|
||||
catch (error) {
|
||||
reviewedRevisions.value.delete(revision.id);
|
||||
}
|
||||
}
|
||||
@@ -425,6 +197,235 @@ async function banEditor(revision) {
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div
|
||||
v-if="context === 'admin'"
|
||||
class="revs-header"
|
||||
>
|
||||
<Checkbox
|
||||
label="Show finalized"
|
||||
:checked="showReviewed"
|
||||
@change="(checked) => { showReviewed = checked; reloadRevisions(); }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
class="revs nolist"
|
||||
:class="{ [`revs-${context}`]: true }"
|
||||
>
|
||||
<li
|
||||
v-for="rev in curatedRevisions"
|
||||
:key="`rev-${rev.id}`"
|
||||
class="rev"
|
||||
:class="{
|
||||
reviewed: reviewedRevisions.has(rev.id),
|
||||
expanded: context === 'admin' || expanded.has(rev.id),
|
||||
}"
|
||||
>
|
||||
<template v-if="context === 'admin' || expanded.has(rev.id)">
|
||||
<div class="rev-header">
|
||||
<a
|
||||
:href="`/${domain.slice(0, -1)}/${rev.sceneId || rev.actorId}/${rev.base.slug}`"
|
||||
target="_blank"
|
||||
class="rev-link rev-scene nolink noshrink"
|
||||
>{{ rev.sceneId || rev.actorId }}@{{ rev.hash.slice(0, 6) }}</a>
|
||||
|
||||
<a
|
||||
:href="`/${domain.slice(0, -1)}/${rev.sceneId || rev.actorId}/${rev.base.slug}`"
|
||||
target="_blank"
|
||||
class="rev-link rev-title nolink ellipsis"
|
||||
>{{ rev.base.title || rev.base.name }}</a>
|
||||
|
||||
<div class="rev-details noshrink">
|
||||
<a
|
||||
v-if="rev.user"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-username nolink"
|
||||
>{{ rev.user.username }}</a>
|
||||
|
||||
<time
|
||||
:datetime="rev.createdAt"
|
||||
class="rev-created"
|
||||
>{{ format(rev.createdAt, 'yyyy-MM-dd hh:mm') }}</time>
|
||||
</div>
|
||||
|
||||
<div class="rev-actions noshrink">
|
||||
<span
|
||||
v-if="rev.review && context === 'admin'"
|
||||
class="approved"
|
||||
:class="{ rejected: !rev.review.isApproved }"
|
||||
>{{ rev.review.isApproved ? 'Approved' : 'Rejected' }} by <a
|
||||
:href="`/user/${rev.review.username}`"
|
||||
target="_blank"
|
||||
class="nolink"
|
||||
>{{ rev.review.username }}</a> {{ format(rev.review.reviewedAt, 'yyyy-MM-dd hh:mm') }}</span>
|
||||
|
||||
<template v-else-if="context === 'admin'">
|
||||
<Icon
|
||||
v-tooltip="{ content: 'Ban user from submitting revisions', ariaId: `rev-ban-${rev.id}` }"
|
||||
icon="user-block"
|
||||
class="review-reject review-ban"
|
||||
@click="banEditor(rev)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-tooltip="{ content: 'Reject revision', ariaId: `rev-reject-${rev.id}` }"
|
||||
icon="blocked"
|
||||
class="review-reject"
|
||||
@click="reviewRevision(rev, false)"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-model="feedbacks[rev.id]"
|
||||
placeholder="Feedback"
|
||||
class="input"
|
||||
>
|
||||
|
||||
<Icon
|
||||
v-tooltip="{ content: 'Approve and apply revision', ariaId: `rev-approve-${rev.id}` }"
|
||||
icon="checkmark"
|
||||
class="review-approve"
|
||||
@click="reviewRevision(rev, true)"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="rev-deltas">
|
||||
<li
|
||||
v-for="(delta, index) in rev.deltas"
|
||||
:key="`delta-${rev.id}-${index}`"
|
||||
class="delta"
|
||||
>
|
||||
<span class="delta-key ellipsis">{{ delta.key }}</span>
|
||||
|
||||
<div class="delta-deltas">
|
||||
<span class="delta-from delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="rev.base[delta.key]"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-else-if="Array.isArray(rev.base[delta.key])"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
v-for="item in rev.base[delta.key]"
|
||||
:key="`item-${rev.id}-${index}-${item.id}`"
|
||||
class="delta-item"
|
||||
:class="{ modified: item.modified }"
|
||||
>{{ item.name || item.id || item }}</li> ]
|
||||
</ul>
|
||||
|
||||
<Avatar
|
||||
v-else-if="delta.key === 'avatar'"
|
||||
:avatar="avatarsById[rev.base[delta.key]]"
|
||||
class="delta-avatar"
|
||||
/>
|
||||
|
||||
<template v-else-if="rev.base[delta.key] instanceof Date">{{ format(rev.base[delta.key], 'yyyy-MM-dd hh:mm') }}</template>
|
||||
<template v-else>{{ rev.base[delta.key] }}</template>
|
||||
</span>
|
||||
|
||||
<span class="delta-arrow">⇒</span>
|
||||
|
||||
<span class="delta-to delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="delta.value"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-else-if="Array.isArray(delta.value)"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
v-for="item in delta.value"
|
||||
:key="`item-${rev.id}-${index}-${item.id}`"
|
||||
class="delta-item"
|
||||
:class="{ modified: item.modified }"
|
||||
>{{ item.name || item.id || item }}</li> ]
|
||||
</ul>
|
||||
|
||||
<Avatar
|
||||
v-else-if="delta.key === 'avatar'"
|
||||
:avatar="avatarsById[delta.value]"
|
||||
class="delta-avatar"
|
||||
/>
|
||||
|
||||
<template v-else-if="delta.value instanceof Date">{{ format(delta.value, 'yyyy-MM-dd hh:mm') }}</template>
|
||||
<template v-else>{{ delta.value }}</template>
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="rev.comment"
|
||||
class="rev-comment"
|
||||
>
|
||||
{{ rev.comment }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="rev-compact"
|
||||
@click="expanded.add(rev.id)"
|
||||
>
|
||||
|
||||
<span class="rev-scene nolink noshrink"><template v-if="context !== 'scene' && context !== 'actor'">{{ rev.sceneId || rev.actorId }}</template>@{{ rev.hash.slice(0, 6) }}</span>
|
||||
|
||||
<span
|
||||
v-if="context !== 'scene'"
|
||||
class="rev-title nolink ellipsis"
|
||||
>{{ rev.base.title }}</span>
|
||||
|
||||
<span class="rev-summary">
|
||||
<span class="summary-deltas ellipsis">{{ rev.deltas.map((delta) => delta.key).join(', ') }}</span>
|
||||
|
||||
<span
|
||||
v-if="rev.comment"
|
||||
class="summary-comment ellipsis"
|
||||
>{{ rev.comment }}</span>
|
||||
</span>
|
||||
|
||||
<div class="rev-details noshrink">
|
||||
<a
|
||||
v-if="rev.user && context !== 'user'"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-username nolink ellipsis"
|
||||
@click.stop
|
||||
>{{ rev.user.username }}</a>
|
||||
|
||||
<a
|
||||
v-if="rev.user && context !== 'user'"
|
||||
v-tooltip="{ content: rev.user.username, ariaId: `rev-user-${rev.id}` }"
|
||||
:href="`/user/${rev.user.username}`"
|
||||
target="_blank"
|
||||
class="rev-avatar nolink ellipsis"
|
||||
@click.stop
|
||||
><Icon icon="user3-long" /></a>
|
||||
|
||||
<time
|
||||
:datetime="rev.createdAt"
|
||||
class="rev-created"
|
||||
>{{ format(rev.createdAt, 'yyyy-MM-dd hh:mm') }}</time>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,75 @@
|
||||
<script setup>
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
import {
|
||||
inject,
|
||||
ref,
|
||||
useId,
|
||||
watch,
|
||||
} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
const emit = defineEmits(['socials']);
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
const socials = ref(props.edits.socials);
|
||||
|
||||
const platform = ref('');
|
||||
const handle = ref('');
|
||||
const url = ref('');
|
||||
const addSocialDropdownId = useId();
|
||||
|
||||
watch(socials, () => emit('socials', socials.value));
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function addSocial() {
|
||||
if (!handle.value && !url.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle.value && !platform.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
socials.value = socials.value.concat({
|
||||
platform: platform.value || null,
|
||||
handle: handle.value || null,
|
||||
url: url.value || null,
|
||||
});
|
||||
|
||||
emit('socials', socials.value);
|
||||
|
||||
platform.value = '';
|
||||
handle.value = '';
|
||||
url.value = '';
|
||||
}
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul
|
||||
class="list nolist"
|
||||
@@ -54,7 +126,7 @@
|
||||
</li>
|
||||
|
||||
<li class="list-new">
|
||||
<VDropdown>
|
||||
<VDropdown :aria-id="addSocialDropdownId">
|
||||
<Icon
|
||||
icon="plus2"
|
||||
class="add noselect"
|
||||
@@ -110,77 +182,6 @@
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
inject,
|
||||
} from 'vue';
|
||||
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['socials']);
|
||||
const socials = ref(props.edits.socials);
|
||||
|
||||
const platform = ref('');
|
||||
const handle = ref('');
|
||||
const url = ref('');
|
||||
|
||||
watch(socials, () => emit('socials', socials));
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function addSocial() {
|
||||
if (!handle.value && !url.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle.value && !platform.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
socials.value = socials.value.concat({
|
||||
platform: platform.value || null,
|
||||
handle: handle.value || null,
|
||||
url: url.value || null,
|
||||
});
|
||||
|
||||
emit('socials', socials.value);
|
||||
|
||||
platform.value = '';
|
||||
handle.value = '';
|
||||
url.value = '';
|
||||
}
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list {
|
||||
display: flex;
|
||||
|
||||
@@ -1,3 +1,72 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import TagSearch from '#/components/tags/search.vue';
|
||||
|
||||
import events from '#/src/events.js';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['tags']);
|
||||
|
||||
const newTags = ref([]);
|
||||
|
||||
const tags = [
|
||||
{
|
||||
tags: props.item.value.filter((tag) => tag.actorId === null),
|
||||
actor: null,
|
||||
actorId: null,
|
||||
},
|
||||
...props.scene.actors.map((actor) => ({
|
||||
tags: props.item.value.filter((tag) => tag.actorId === actor.id),
|
||||
actor,
|
||||
actorId: actor?.id || null,
|
||||
})),
|
||||
];
|
||||
|
||||
function addTag(newTag, actor) {
|
||||
const actorId = actor?.id || null;
|
||||
|
||||
if (props.edits.tags.some((sceneTag) => sceneTag.id === newTag.id && sceneTag.actorId === actorId)) {
|
||||
events.emit('feedback', {
|
||||
type: 'error',
|
||||
message: 'Tag already added',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newTagWithActorId = {
|
||||
...newTag,
|
||||
actorId,
|
||||
};
|
||||
|
||||
newTags.value = newTags.value.concat(newTagWithActorId);
|
||||
emit('tags', props.edits.tags.concat(newTagWithActorId));
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => {
|
||||
newTags.value = [];
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tags-sections">
|
||||
<div
|
||||
@@ -53,73 +122,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
import events from '#/src/events.js';
|
||||
|
||||
import TagSearch from '#/components/tags/search.vue';
|
||||
|
||||
const newTags = ref([]);
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
scene: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
edits: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['tags']);
|
||||
|
||||
const tags = [
|
||||
{
|
||||
tags: props.item.value.filter((tag) => tag.actorId === null),
|
||||
actor: null,
|
||||
actorId: null,
|
||||
},
|
||||
...props.scene.actors.map((actor) => ({
|
||||
tags: props.item.value.filter((tag) => tag.actorId === actor.id),
|
||||
actor,
|
||||
actorId: actor?.id || null,
|
||||
})),
|
||||
];
|
||||
|
||||
function addTag(newTag, actor) {
|
||||
const actorId = actor?.id || null;
|
||||
|
||||
if (props.edits.tags.some((sceneTag) => sceneTag.id === newTag.id && sceneTag.actorId === actorId)) {
|
||||
events.emit('feedback', {
|
||||
type: 'error',
|
||||
message: 'Tag already added',
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const newTagWithActorId = {
|
||||
...newTag,
|
||||
actorId,
|
||||
};
|
||||
|
||||
newTags.value = newTags.value.concat(newTagWithActorId);
|
||||
emit('tags', props.edits.tags.concat(newTagWithActorId));
|
||||
}
|
||||
|
||||
watch(() => props.scene, () => { newTags.value = []; });
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tags {
|
||||
display: flex;
|
||||
|
||||
Reference in New Issue
Block a user