feat(socialnetwork): enhance folder and video management with user visibility options
- Added functionality to manage selected users for adult folders and erotic videos, allowing for more granular visibility control. - Introduced new endpoints and methods in the SocialNetworkController and SocialNetworkService to handle selected users. - Updated the frontend components to include input fields for selected users in CreateFolderDialog, EditImageDialog, and EroticPicturesView. - Enhanced the routing to support fetching erotic folders and videos by username, improving user experience in profile views.
This commit is contained in:
@@ -30,6 +30,15 @@
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div v-if="requiresSelectedUsers" class="form-group">
|
||||
<label for="selectedUsers">{{ $t("socialnetwork.gallery.visibility.selected-users") }}</label>
|
||||
<input
|
||||
id="selectedUsers"
|
||||
type="text"
|
||||
v-model="selectedUsernamesText"
|
||||
placeholder="anna, bert, clara"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
</template>
|
||||
@@ -55,6 +64,7 @@ export default {
|
||||
visibilityOptions: [],
|
||||
allVisibilityOptions: [],
|
||||
selectedVisibility: [],
|
||||
selectedUsernamesText: '',
|
||||
parentFolder: {id: null, name: ''},
|
||||
folderId: 0,
|
||||
eroticMode: false
|
||||
@@ -65,6 +75,9 @@ export default {
|
||||
buttons() {
|
||||
return [{ text: this.$t("socialnetwork.gallery.create_folder"), action: this.createFolder }];
|
||||
},
|
||||
requiresSelectedUsers() {
|
||||
return this.selectedVisibility.some(option => option?.description === 'selected-users');
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadVisibilityOptions();
|
||||
@@ -79,9 +92,11 @@ export default {
|
||||
this.selectedVisibility = this.visibilityOptions.filter(option =>
|
||||
folder.visibilityTypeIds.includes(option.id)
|
||||
);
|
||||
this.selectedUsernamesText = (folder.selectedUsers || []).join(', ');
|
||||
} else {
|
||||
this.folderTitle = '';
|
||||
this.selectedVisibility = [];
|
||||
this.selectedUsernamesText = '';
|
||||
}
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
@@ -109,6 +124,10 @@ export default {
|
||||
name: this.folderTitle,
|
||||
parentId: this.parentFolder.id,
|
||||
visibilities: this.selectedVisibility.map(item => item.id),
|
||||
selectedUsers: this.selectedUsernamesText
|
||||
.split(',')
|
||||
.map(value => value.trim())
|
||||
.filter(Boolean),
|
||||
};
|
||||
try {
|
||||
const basePath = this.eroticMode ? '/api/socialnetwork/erotic/folders' : '/api/socialnetwork/folders';
|
||||
|
||||
@@ -28,6 +28,15 @@
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div v-if="requiresSelectedUsers" class="form-group">
|
||||
<label for="selectedUsers">{{ $t('socialnetwork.gallery.visibility.selected-users') }}</label>
|
||||
<input
|
||||
id="selectedUsers"
|
||||
type="text"
|
||||
v-model="selectedUsernamesText"
|
||||
placeholder="anna, bert, clara"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogWidget>
|
||||
</template>
|
||||
@@ -48,6 +57,7 @@ export default {
|
||||
imageTitle: '',
|
||||
selectedVisibilities: [],
|
||||
visibilityOptions: [],
|
||||
selectedUsernamesText: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -57,12 +67,16 @@ export default {
|
||||
{ text: this.$t('socialnetwork.gallery.imagedialog.close'), action: this.closeDialog }
|
||||
];
|
||||
},
|
||||
requiresSelectedUsers() {
|
||||
return this.selectedVisibilities.some(option => option?.description === 'selected-users');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
open(image) {
|
||||
this.image = image;
|
||||
this.imageTitle = image.title;
|
||||
this.selectedVisibilities = image.visibilities || [];
|
||||
this.selectedUsernamesText = (image.selectedUsers || []).join(', ');
|
||||
this.$refs.dialog.open();
|
||||
},
|
||||
closeDialog() {
|
||||
@@ -73,6 +87,10 @@ export default {
|
||||
...this.image,
|
||||
title: this.imageTitle,
|
||||
visibilities: this.selectedVisibilities,
|
||||
selectedUsers: this.selectedUsernamesText
|
||||
.split(',')
|
||||
.map(value => value.trim())
|
||||
.filter(Boolean),
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -23,6 +23,14 @@
|
||||
<td>{{ generateValue(key, value) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div v-if="canOpenEroticPictures" class="adult-actions">
|
||||
<button type="button" @click="openEroticPictures">
|
||||
{{ $t('socialnetwork.erotic.picturesTitle') }}
|
||||
</button>
|
||||
<button type="button" @click="openEroticVideos">
|
||||
{{ $t('socialnetwork.erotic.videosTitle') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content images-tab" v-if="activeTab === 'images'">
|
||||
<div v-if="folders.length === 0">{{ $t('socialnetwork.profile.noFolders') }}</div>
|
||||
@@ -98,6 +106,7 @@ import { Editor, EditorContent } from '@tiptap/vue-3'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import DOMPurify from 'dompurify';
|
||||
import { showError } from '@/utils/feedback.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'UserProfileDialog',
|
||||
@@ -106,6 +115,16 @@ export default {
|
||||
FolderItem,
|
||||
EditorContent,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
canOpenEroticPictures() {
|
||||
return Boolean(
|
||||
this.userProfile?.username &&
|
||||
this.user?.username &&
|
||||
this.userProfile.username !== this.user.username
|
||||
);
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isTitleTranslated: true,
|
||||
@@ -251,6 +270,24 @@ export default {
|
||||
openImageDialog(image) {
|
||||
this.$root.$refs.showImageDialog.open(image);
|
||||
},
|
||||
openEroticPictures() {
|
||||
this.closeDialog();
|
||||
this.$router.push({
|
||||
name: 'EroticPictures',
|
||||
query: {
|
||||
username: this.userProfile.username
|
||||
}
|
||||
});
|
||||
},
|
||||
openEroticVideos() {
|
||||
this.closeDialog();
|
||||
this.$router.push({
|
||||
name: 'EroticVideos',
|
||||
query: {
|
||||
username: this.userProfile.username
|
||||
}
|
||||
});
|
||||
},
|
||||
toggleInputSection() {
|
||||
this.showInputSection = !this.showInputSection;
|
||||
},
|
||||
@@ -468,6 +505,10 @@ export default {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.adult-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
@@ -20,15 +20,16 @@
|
||||
:isLastItem="true"
|
||||
:depth="0"
|
||||
:parentsWithChildren="[false]"
|
||||
:noActionItems="isForeignView"
|
||||
@edit-folder="openEditFolderDialog"
|
||||
@delete-folder="deleteFolder"
|
||||
/>
|
||||
</ul>
|
||||
<button @click="openCreateFolderDialog">{{ $t('socialnetwork.gallery.create_folder') }}</button>
|
||||
<button v-if="!isForeignView" @click="openCreateFolderDialog">{{ $t('socialnetwork.gallery.create_folder') }}</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="upload-section surface-card">
|
||||
<div v-if="!isForeignView" class="upload-section surface-card">
|
||||
<div class="upload-header" @click="toggleUploadSection">
|
||||
<span><i class="icon-upload-toggle">{{ isUploadVisible ? '▲' : '▼' }}</i></span>
|
||||
<h3>{{ $t('socialnetwork.erotic.uploadTitle') }}</h3>
|
||||
@@ -72,6 +73,15 @@
|
||||
</template>
|
||||
</multiselect>
|
||||
</div>
|
||||
<div v-if="requiresSelectedUsers" class="form-group">
|
||||
<label for="selectedUsers">{{ $t('socialnetwork.gallery.visibility.selected-users') }}</label>
|
||||
<input
|
||||
id="selectedUsers"
|
||||
v-model="selectedUsernamesText"
|
||||
type="text"
|
||||
placeholder="anna, bert, clara"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="upload-button">
|
||||
{{ $t('socialnetwork.gallery.upload.upload_button') }}
|
||||
@@ -127,6 +137,7 @@ import FolderItem from '../../components/FolderItem.vue';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
import { EventBus } from '@/utils/eventBus.js';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -143,22 +154,31 @@ export default {
|
||||
isUploadVisible: true,
|
||||
visibilityOptions: [],
|
||||
selectedVisibilities: [],
|
||||
selectedUsernamesText: '',
|
||||
imagePreview: null,
|
||||
reportTarget: { type: null, id: null },
|
||||
reportReason: 'other',
|
||||
reportNote: '',
|
||||
viewUsername: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
reportReasonOptions() {
|
||||
return ['suspected_minor', 'non_consensual', 'violence', 'harassment', 'spam', 'other'].map(value => ({
|
||||
value,
|
||||
label: this.$t(`socialnetwork.erotic.reportReasons.${value}`)
|
||||
}));
|
||||
},
|
||||
isForeignView() {
|
||||
return Boolean(this.viewUsername && this.viewUsername !== this.user?.username);
|
||||
},
|
||||
requiresSelectedUsers() {
|
||||
return this.selectedVisibilities.some(option => option?.description === 'selected-users');
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadFolders();
|
||||
await this.initializeView();
|
||||
await this.loadImageVisibilities();
|
||||
if (this.folders) {
|
||||
this.selectFolder(this.folders);
|
||||
@@ -169,8 +189,14 @@ export default {
|
||||
EventBus.off('folderCreated', this.loadFolders);
|
||||
},
|
||||
methods: {
|
||||
async initializeView() {
|
||||
this.viewUsername = String(this.$route.query.username || '').trim();
|
||||
await this.loadFolders();
|
||||
},
|
||||
async loadFolders() {
|
||||
const response = await apiClient.get('/api/socialnetwork/erotic/folders');
|
||||
const response = this.isForeignView
|
||||
? await apiClient.get(`/api/socialnetwork/profile/erotic/folders/${this.viewUsername}`)
|
||||
: await apiClient.get('/api/socialnetwork/erotic/folders');
|
||||
this.folders = response.data;
|
||||
},
|
||||
async loadImageVisibilities() {
|
||||
@@ -223,6 +249,9 @@ export default {
|
||||
formData.append('folderId', this.selectedFolder.id);
|
||||
formData.append('title', this.imageTitle);
|
||||
formData.append('visibility', JSON.stringify(this.selectedVisibilities.map((v) => v.id)));
|
||||
formData.append('selectedUsers', JSON.stringify(
|
||||
this.selectedUsernamesText.split(',').map(value => value.trim()).filter(Boolean)
|
||||
));
|
||||
|
||||
await apiClient.post('/api/socialnetwork/erotic/images', formData, {
|
||||
headers: {
|
||||
@@ -234,6 +263,7 @@ export default {
|
||||
this.fileToUpload = null;
|
||||
this.imagePreview = null;
|
||||
this.selectedVisibilities = this.visibilityOptions.filter(option => option.description === 'adults');
|
||||
this.selectedUsernamesText = '';
|
||||
},
|
||||
async fetchImage(image) {
|
||||
if (image.isModeratedHidden) {
|
||||
@@ -250,6 +280,10 @@ export default {
|
||||
this.isUploadVisible = !this.isUploadVisible;
|
||||
},
|
||||
openImageDialog(image) {
|
||||
if (this.isForeignView) {
|
||||
this.$root.$refs.showImageDialog.open(image);
|
||||
return;
|
||||
}
|
||||
this.$root.$refs.editImageDialog.open(image);
|
||||
},
|
||||
startReport(type, id) {
|
||||
@@ -280,6 +314,7 @@ export default {
|
||||
const response = await apiClient.put(`/api/socialnetwork/erotic/images/${updatedImage.id}`, {
|
||||
title: updatedImage.title,
|
||||
visibilities: updatedImage.visibilities,
|
||||
selectedUsers: updatedImage.selectedUsers || [],
|
||||
});
|
||||
this.images = response.data.map((image) => ({
|
||||
...image,
|
||||
@@ -301,6 +336,16 @@ export default {
|
||||
// Separate delete flow for adult folders is intentionally not enabled yet.
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
'$route.query.username': {
|
||||
async handler() {
|
||||
await this.initializeView();
|
||||
if (this.folders) {
|
||||
await this.selectFolder(this.folders);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
<section class="erotic-videos-hero surface-card">
|
||||
<div>
|
||||
<span class="erotic-videos-eyebrow">{{ $t('socialnetwork.erotic.eyebrow') }}</span>
|
||||
<h2>{{ $t('socialnetwork.erotic.videosTitle') }}</h2>
|
||||
<p>{{ $t('socialnetwork.erotic.videosIntro') }}</p>
|
||||
<h2>{{ isForeignView ? `${$t('socialnetwork.erotic.videosTitle')} · ${viewUsername}` : $t('socialnetwork.erotic.videosTitle') }}</h2>
|
||||
<p>{{ isForeignView ? 'Freigegebene Videos aus dem Erwachsenenbereich.' : $t('socialnetwork.erotic.videosIntro') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="erotic-videos-workspace">
|
||||
<aside class="erotic-videos-sidebar">
|
||||
<section class="erotic-videos-upload surface-card">
|
||||
<section v-if="!isForeignView" class="erotic-videos-upload surface-card">
|
||||
<div class="erotic-videos-upload__header">
|
||||
<h3>{{ $t('socialnetwork.erotic.videoUploadTitle') }}</h3>
|
||||
<p>{{ $t('socialnetwork.erotic.videoUploadHint') }}</p>
|
||||
@@ -27,6 +27,34 @@
|
||||
<span>{{ $t('socialnetwork.erotic.videoDescription') }}</span>
|
||||
<textarea v-model="description" rows="4" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.gallery.upload.visibility') }}</span>
|
||||
<multiselect
|
||||
v-model="selectedVisibilities"
|
||||
:options="visibilityOptions"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
label="description"
|
||||
:placeholder="$t('socialnetwork.gallery.upload.selectvisibility')"
|
||||
:track-by="'id'"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span v-if="option && option.description">
|
||||
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #tag="{ option, remove }">
|
||||
<span v-if="option && option.description" class="multiselect__tag">
|
||||
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
|
||||
<span @click="remove(option)">×</span>
|
||||
</span>
|
||||
</template>
|
||||
</multiselect>
|
||||
</label>
|
||||
<label v-if="requiresSelectedUsers">
|
||||
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
||||
<input v-model="selectedUsernamesText" type="text" placeholder="anna, bert, clara" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.videoFile') }}</span>
|
||||
<input type="file" accept="video/mp4,video/webm,video/ogg,video/quicktime" required @change="onFileChange" />
|
||||
@@ -43,7 +71,7 @@
|
||||
<h3>Bibliothek</h3>
|
||||
<div class="erotic-videos-panel__list">
|
||||
<div class="erotic-videos-panel__item">
|
||||
<span>{{ $t('socialnetwork.erotic.myVideos') }}</span>
|
||||
<span>{{ isForeignView ? 'Freigegebene Videos' : $t('socialnetwork.erotic.myVideos') }}</span>
|
||||
<strong>{{ videos.length }}</strong>
|
||||
</div>
|
||||
<div class="erotic-videos-panel__item">
|
||||
@@ -64,10 +92,10 @@
|
||||
<section class="erotic-videos-panel surface-card">
|
||||
<h3>Hinweise</h3>
|
||||
<ul class="erotic-videos-checklist">
|
||||
<li>{{ $t('socialnetwork.erotic.videoUploadHint') }}</li>
|
||||
<li>Nach dem Upload erscheint dein Video direkt in deiner Bibliothek und kann dort weiter gepflegt werden.</li>
|
||||
<li>{{ isForeignView ? 'Du siehst hier nur Videos, die dir für den Erwachsenenbereich freigegeben wurden.' : $t('socialnetwork.erotic.videoUploadHint') }}</li>
|
||||
<li>Freunde sehen Inhalte nur dann, wenn sie volljährig und für den Erwachsenenbereich freigeschaltet sind.</li>
|
||||
<li>Gezielt freigegebene Personen müssen ebenfalls volljährig und freigeschaltet sein.</li>
|
||||
<li>Nutze {{ $t('socialnetwork.erotic.reportAction') }} direkt am jeweiligen Eintrag, wenn Inhalte geprüft werden sollen.</li>
|
||||
<li>Präzise Titel und Beschreibungen erleichtern Einordnung, Verwaltung und Moderation.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</aside>
|
||||
@@ -75,15 +103,15 @@
|
||||
<section class="erotic-videos-library surface-card">
|
||||
<div class="erotic-videos-library__header">
|
||||
<div>
|
||||
<h3>{{ $t('socialnetwork.erotic.myVideos') }}</h3>
|
||||
<p>Eigene Uploads, Status und Meldungen an einem Ort.</p>
|
||||
<h3>{{ isForeignView ? 'Freigegebene Videos' : $t('socialnetwork.erotic.myVideos') }}</h3>
|
||||
<p>{{ isForeignView ? 'Sichtbare Videos aus freigegebenen Erwachsenenbereichen.' : 'Eigene Uploads, Freigaben und Meldungen an einem Ort.' }}</p>
|
||||
</div>
|
||||
<span class="erotic-videos-library__count">{{ videos.length }}</span>
|
||||
</div>
|
||||
|
||||
<div v-if="videos.length === 0" class="erotic-videos-empty">
|
||||
<strong>{{ $t('socialnetwork.erotic.noVideos') }}</strong>
|
||||
<span>Lege links dein erstes Video an und verwalte es danach hier in der Bibliothek.</span>
|
||||
<span>{{ isForeignView ? 'Für dich sind aktuell keine freigegebenen Videos vorhanden.' : 'Lege links dein erstes Video an und verwalte es danach hier in der Bibliothek.' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="erotic-videos-library__scroll">
|
||||
@@ -97,10 +125,61 @@
|
||||
<strong>{{ video.title || 'Ohne Titel' }}</strong>
|
||||
<span v-if="video.createdAtLabel">{{ video.createdAtLabel }}</span>
|
||||
</div>
|
||||
<div v-if="video.visibilities?.length" class="erotic-videos-card__visibility">
|
||||
<span v-for="visibility in video.visibilities" :key="`${video.id}-${visibility.id}`" class="erotic-videos-card__visibility-badge">
|
||||
{{ $t(`socialnetwork.gallery.visibility.${visibility.description}`) }}
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="video.isModeratedHidden" class="erotic-videos-card__badge">
|
||||
{{ $t('socialnetwork.erotic.moderationHidden') }}
|
||||
</span>
|
||||
<p v-if="video.description">{{ video.description }}</p>
|
||||
<div v-if="!isForeignView" class="erotic-videos-card__edit">
|
||||
<button type="button" class="secondary" @click="toggleEditor(video.id)">
|
||||
{{ editingVideoId === video.id ? 'Bearbeitung schließen' : 'Freigaben bearbeiten' }}
|
||||
</button>
|
||||
<div v-if="editingVideoId === video.id" class="erotic-videos-editor">
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.gallery.upload.image_title') }}</span>
|
||||
<input v-model="editForm.title" type="text" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.erotic.videoDescription') }}</span>
|
||||
<textarea v-model="editForm.description" rows="3" />
|
||||
</label>
|
||||
<label>
|
||||
<span>{{ $t('socialnetwork.gallery.upload.visibility') }}</span>
|
||||
<multiselect
|
||||
v-model="editForm.visibilities"
|
||||
:options="visibilityOptions"
|
||||
:multiple="true"
|
||||
:close-on-select="false"
|
||||
label="description"
|
||||
:placeholder="$t('socialnetwork.gallery.upload.selectvisibility')"
|
||||
:track-by="'id'"
|
||||
>
|
||||
<template #option="{ option }">
|
||||
<span v-if="option && option.description">
|
||||
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
|
||||
</span>
|
||||
</template>
|
||||
<template #tag="{ option, remove }">
|
||||
<span v-if="option && option.description" class="multiselect__tag">
|
||||
{{ $t(`socialnetwork.gallery.visibility.${option.description}`) }}
|
||||
<span @click="remove(option)">×</span>
|
||||
</span>
|
||||
</template>
|
||||
</multiselect>
|
||||
</label>
|
||||
<label v-if="editRequiresSelectedUsers">
|
||||
<span>{{ $t('socialnetwork.gallery.visibility.selected-users') }}</span>
|
||||
<input v-model="editForm.selectedUsernamesText" type="text" placeholder="anna, bert, clara" />
|
||||
</label>
|
||||
<div class="erotic-videos-editor__actions">
|
||||
<button type="button" @click="saveVideo(video.id)">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="erotic-videos-card__actions">
|
||||
<button type="button" class="secondary" @click="startReport('video', video.id)">
|
||||
{{ $t('socialnetwork.erotic.reportAction') }}
|
||||
@@ -130,28 +209,55 @@
|
||||
|
||||
<script>
|
||||
import apiClient from '@/utils/axios.js';
|
||||
import Multiselect from 'vue-multiselect';
|
||||
import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { showApiError, showSuccess } from '@/utils/feedback.js';
|
||||
|
||||
export default {
|
||||
name: 'EroticVideosView',
|
||||
components: {
|
||||
Multiselect
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
videos: [],
|
||||
fileToUpload: null,
|
||||
title: '',
|
||||
description: '',
|
||||
visibilityOptions: [],
|
||||
selectedVisibilities: [],
|
||||
selectedUsernamesText: '',
|
||||
editingVideoId: null,
|
||||
editForm: {
|
||||
title: '',
|
||||
description: '',
|
||||
visibilities: [],
|
||||
selectedUsernamesText: ''
|
||||
},
|
||||
reportTarget: { type: null, id: null },
|
||||
reportReason: 'other',
|
||||
reportNote: ''
|
||||
reportNote: '',
|
||||
viewUsername: ''
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['user']),
|
||||
reportReasonOptions() {
|
||||
return ['suspected_minor', 'non_consensual', 'violence', 'harassment', 'spam', 'other'].map(value => ({
|
||||
value,
|
||||
label: this.$t(`socialnetwork.erotic.reportReasons.${value}`)
|
||||
}));
|
||||
},
|
||||
isForeignView() {
|
||||
return Boolean(this.viewUsername && this.viewUsername !== this.user?.username);
|
||||
},
|
||||
requiresSelectedUsers() {
|
||||
return this.selectedVisibilities.some(option => option?.description === 'selected-users');
|
||||
},
|
||||
editRequiresSelectedUsers() {
|
||||
return this.editForm.visibilities.some(option => option?.description === 'selected-users');
|
||||
},
|
||||
visibleVideosCount() {
|
||||
return this.videos.filter((video) => !video.isModeratedHidden).length;
|
||||
},
|
||||
@@ -163,15 +269,29 @@ export default {
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.loadVideos();
|
||||
await this.initializeView();
|
||||
await this.loadImageVisibilities();
|
||||
},
|
||||
beforeUnmount() {
|
||||
this.releaseVideoUrls();
|
||||
},
|
||||
methods: {
|
||||
async initializeView() {
|
||||
this.viewUsername = String(this.$route.query.username || '').trim();
|
||||
await this.loadVideos();
|
||||
},
|
||||
async loadImageVisibilities() {
|
||||
const response = await apiClient.get('/api/socialnetwork/imagevisibilities');
|
||||
this.visibilityOptions = response.data.filter(option => option.description !== 'everyone');
|
||||
if (!this.selectedVisibilities.length) {
|
||||
this.selectedVisibilities = this.visibilityOptions.filter(option => option.description === 'adults');
|
||||
}
|
||||
},
|
||||
async loadVideos() {
|
||||
this.releaseVideoUrls();
|
||||
const response = await apiClient.get('/api/socialnetwork/erotic/videos');
|
||||
const response = this.isForeignView
|
||||
? await apiClient.get(`/api/socialnetwork/profile/erotic/videos/${this.viewUsername}`)
|
||||
: await apiClient.get('/api/socialnetwork/erotic/videos');
|
||||
this.videos = await Promise.all(response.data.map(async (video) => ({
|
||||
...video,
|
||||
url: video.isModeratedHidden ? null : await this.fetchVideoUrl(video.hash),
|
||||
@@ -200,12 +320,45 @@ export default {
|
||||
formData.append('video', this.fileToUpload);
|
||||
formData.append('title', this.title);
|
||||
formData.append('description', this.description);
|
||||
formData.append('visibility', JSON.stringify(this.selectedVisibilities.map((visibility) => visibility.id)));
|
||||
formData.append('selectedUsers', JSON.stringify(
|
||||
this.selectedUsernamesText.split(',').map(value => value.trim()).filter(Boolean)
|
||||
));
|
||||
await apiClient.post('/api/socialnetwork/erotic/videos', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
});
|
||||
this.fileToUpload = null;
|
||||
this.title = '';
|
||||
this.description = '';
|
||||
this.selectedUsernamesText = '';
|
||||
this.selectedVisibilities = this.visibilityOptions.filter(option => option.description === 'adults');
|
||||
await this.loadVideos();
|
||||
},
|
||||
toggleEditor(videoId) {
|
||||
if (this.editingVideoId === videoId) {
|
||||
this.editingVideoId = null;
|
||||
return;
|
||||
}
|
||||
const video = this.videos.find(entry => entry.id === videoId);
|
||||
if (!video) return;
|
||||
this.editingVideoId = videoId;
|
||||
this.editForm = {
|
||||
title: video.title || '',
|
||||
description: video.description || '',
|
||||
visibilities: this.visibilityOptions.filter(option =>
|
||||
(video.visibilities || []).some(visibility => visibility.id === option.id)
|
||||
),
|
||||
selectedUsernamesText: (video.selectedUsers || []).join(', ')
|
||||
};
|
||||
},
|
||||
async saveVideo(videoId) {
|
||||
await apiClient.put(`/api/socialnetwork/erotic/videos/${videoId}`, {
|
||||
title: this.editForm.title,
|
||||
description: this.editForm.description,
|
||||
visibilities: this.editForm.visibilities,
|
||||
selectedUsers: this.editForm.selectedUsernamesText.split(',').map(value => value.trim()).filter(Boolean)
|
||||
});
|
||||
this.editingVideoId = null;
|
||||
await this.loadVideos();
|
||||
},
|
||||
startReport(type, id) {
|
||||
@@ -232,6 +385,13 @@ export default {
|
||||
showApiError(this, error, this.$t('socialnetwork.erotic.reportError'));
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'$route.query.username': {
|
||||
async handler() {
|
||||
await this.initializeView();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -301,7 +461,8 @@ export default {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.erotic-videos-form label {
|
||||
.erotic-videos-form label,
|
||||
.erotic-videos-editor label {
|
||||
display: grid;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
@@ -405,6 +566,22 @@ export default {
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.erotic-videos-card__visibility {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
|
||||
.erotic-videos-card__visibility-badge {
|
||||
display: inline-flex;
|
||||
padding: 0.2rem 0.55rem;
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(66, 99, 78, 0.12);
|
||||
color: #42634e;
|
||||
font-size: 0.74rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.erotic-videos-card__hidden {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
@@ -434,6 +611,9 @@ export default {
|
||||
}
|
||||
|
||||
.erotic-videos-card__actions,
|
||||
.erotic-videos-card__edit,
|
||||
.erotic-videos-editor,
|
||||
.erotic-videos-editor__actions,
|
||||
.erotic-report-form,
|
||||
.erotic-report-form__actions {
|
||||
display: grid;
|
||||
@@ -455,9 +635,6 @@ export default {
|
||||
@media (max-width: 980px) {
|
||||
.erotic-videos-workspace {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.erotic-videos-workspace {
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user