This commit is contained in:
Markos Gogoulos
2026-03-09 20:09:20 +02:00
parent e130c7403c
commit e0974433a4
7 changed files with 112 additions and 710 deletions

View File

@@ -9,14 +9,15 @@ interface BulkActionsDropdownProps {
}
const BULK_ACTIONS = [
{ value: 'add-remove-coviewers', label: translateString('Add / Remove Co-Viewers'), enabled: true },
{ value: 'add-remove-coeditors', label: translateString('Add / Remove Co-Editors'), enabled: true },
{ value: 'add-remove-coowners', label: translateString('Add / Remove Co-Owners'), enabled: true },
{ value: 'add-remove-coviewers', label: inEmbeddedApp() ? translateString('Share with Co-Viewers') : translateString('Add / Remove Co-Viewers'), enabled: true },
{ value: 'add-remove-coeditors', label: inEmbeddedApp() ? translateString('Share with Co-Editors') : translateString('Add / Remove Co-Editors'), enabled: true },
{ value: 'add-remove-coowners', label: inEmbeddedApp() ? translateString('Share with Co-Owners') : translateString('Add / Remove Co-Owners'), enabled: true },
{ value: 'add-remove-playlist', label: translateString('Add to / Remove from Playlist'), enabled: true },
{ value: 'add-remove-category', label: inEmbeddedApp() ? translateString('Share with Course') : translateString('Add to / Remove from Category'), enabled: true },
{ value: 'add-remove-category', label: inEmbeddedApp() ? translateString('Share with Course Members') : translateString('Add to / Remove from Category'), enabled: true },
{ value: 'add-remove-tags', label: translateString('Add / Remove Tags'), enabled: true },
{ value: 'enable-comments', label: translateString('Enable Comments'), enabled: true },
{ value: 'disable-comments', label: translateString('Disable Comments'), enabled: true },
{ value: 'delete-comments', label: translateString('Delete Comments'), enabled: true },
{ value: 'enable-download', label: translateString('Enable Download'), enabled: true },
{ value: 'disable-download', label: translateString('Disable Download'), enabled: true },
{ value: 'publish-state', label: translateString('Publish State'), enabled: true },

View File

@@ -8,7 +8,7 @@ import { PageStore, ProfilePageStore } from '../utils/stores/';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { MediaListRow } from '../components/MediaListRow';
import { ProfileMediaPage } from './ProfileMediaPage';
import { ProfileMediaPageBase } from './ProfileMediaPage';
class ChannelContactForm extends React.PureComponent {
constructor(props) {
@@ -149,7 +149,7 @@ class ChannelContactForm extends React.PureComponent {
}
}
export class ProfileAboutPage extends ProfileMediaPage {
export class ProfileAboutPage extends ProfileMediaPageBase {
constructor(props) {
super(props, 'author-about');

View File

@@ -7,9 +7,9 @@ import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { ProfileMediaPage } from './ProfileMediaPage';
import { ProfileMediaPageBase } from './ProfileMediaPage';
export class ProfileHistoryPage extends ProfileMediaPage {
export class ProfileHistoryPage extends ProfileMediaPageBase {
constructor(props) {
super(props, 'author-history');

View File

@@ -7,9 +7,9 @@ import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { ProfileMediaPage } from './ProfileMediaPage';
import { ProfileMediaPageBase } from './ProfileMediaPage';
export class ProfileLikedPage extends ProfileMediaPage {
export class ProfileLikedPage extends ProfileMediaPageBase {
constructor(props) {
super(props, 'author-liked');

View File

@@ -8,22 +8,17 @@ import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync';
import { BulkActionConfirmModal } from '../components/BulkActionConfirmModal';
import { BulkActionPermissionModal } from '../components/BulkActionPermissionModal';
import { BulkActionPlaylistModal } from '../components/BulkActionPlaylistModal';
import { BulkActionChangeOwnerModal } from '../components/BulkActionChangeOwnerModal';
import { BulkActionPublishStateModal } from '../components/BulkActionPublishStateModal';
import { BulkActionCategoryModal } from '../components/BulkActionCategoryModal';
import { BulkActionTagModal } from '../components/BulkActionTagModal';
import { BulkActionsModals } from '../components/BulkActionsModals';
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
import { withBulkActions } from '../utils/hoc/withBulkActions';
import { Page } from './_Page';
import '../components/profile-page/ProfilePage.scss';
export class ProfileMediaPage extends Page {
class ProfileMediaPage extends Page {
constructor(props, pageSlug) {
super(props, 'string' === typeof pageSlug ? pageSlug : 'author-home');
@@ -36,15 +31,7 @@ export class ProfileMediaPage extends Page {
title: this.props.title,
query: ProfilePageStore.get('author-query'),
requestUrl: null,
selectedMedia: new Set(),
availableMediaIds: [],
showConfirmModal: false,
pendingAction: null,
confirmMessage: '',
listKey: 0,
notificationMessage: '',
showNotification: false,
notificationType: 'success',
selectedMedia: new Set(), // For select media embed mode only
hiddenFilters: true,
hiddenTags: true,
hiddenSorting: true,
@@ -52,13 +39,6 @@ export class ProfileMediaPage extends Page {
availableTags: [],
selectedTag: 'all',
selectedSort: 'date_added_desc',
showPermissionModal: false,
permissionType: null,
showPlaylistModal: false,
showChangeOwnerModal: false,
showPublishStateModal: false,
showCategoryModal: false,
showTagModal: false,
};
this.authorDataLoad = this.authorDataLoad.bind(this);
@@ -66,20 +46,6 @@ export class ProfileMediaPage extends Page {
this.getCountFunc = this.getCountFunc.bind(this);
this.changeRequestQuery = this.changeRequestQuery.bind(this);
this.handleMediaSelection = this.handleMediaSelection.bind(this);
this.handleBulkAction = this.handleBulkAction.bind(this);
this.handleConfirmCancel = this.handleConfirmCancel.bind(this);
this.handleConfirmProceed = this.handleConfirmProceed.bind(this);
this.clearSelectionAndRefresh = this.clearSelectionAndRefresh.bind(this);
this.clearSelection = this.clearSelection.bind(this);
this.executeEnableComments = this.executeEnableComments.bind(this);
this.executeDisableComments = this.executeDisableComments.bind(this);
this.executeEnableDownload = this.executeEnableDownload.bind(this);
this.executeDisableDownload = this.executeDisableDownload.bind(this);
this.executeCopyMedia = this.executeCopyMedia.bind(this);
this.showNotification = this.showNotification.bind(this);
this.handleSelectAll = this.handleSelectAll.bind(this);
this.handleDeselectAll = this.handleDeselectAll.bind(this);
this.handleItemsUpdate = this.handleItemsUpdate.bind(this);
this.onToggleFiltersClick = this.onToggleFiltersClick.bind(this);
this.onToggleTagsClick = this.onToggleTagsClick.bind(this);
this.onToggleSortingClick = this.onToggleSortingClick.bind(this);
@@ -87,24 +53,6 @@ export class ProfileMediaPage extends Page {
this.onTagSelect = this.onTagSelect.bind(this);
this.onSortSelect = this.onSortSelect.bind(this);
this.onResponseDataLoaded = this.onResponseDataLoaded.bind(this);
this.handlePermissionModalCancel = this.handlePermissionModalCancel.bind(this);
this.handlePermissionModalSuccess = this.handlePermissionModalSuccess.bind(this);
this.handlePermissionModalError = this.handlePermissionModalError.bind(this);
this.handlePlaylistModalCancel = this.handlePlaylistModalCancel.bind(this);
this.handlePlaylistModalSuccess = this.handlePlaylistModalSuccess.bind(this);
this.handlePlaylistModalError = this.handlePlaylistModalError.bind(this);
this.handleChangeOwnerModalCancel = this.handleChangeOwnerModalCancel.bind(this);
this.handleChangeOwnerModalSuccess = this.handleChangeOwnerModalSuccess.bind(this);
this.handleChangeOwnerModalError = this.handleChangeOwnerModalError.bind(this);
this.handlePublishStateModalCancel = this.handlePublishStateModalCancel.bind(this);
this.handlePublishStateModalSuccess = this.handlePublishStateModalSuccess.bind(this);
this.handlePublishStateModalError = this.handlePublishStateModalError.bind(this);
this.handleCategoryModalCancel = this.handleCategoryModalCancel.bind(this);
this.handleCategoryModalSuccess = this.handleCategoryModalSuccess.bind(this);
this.handleCategoryModalError = this.handleCategoryModalError.bind(this);
this.handleTagModalCancel = this.handleTagModalCancel.bind(this);
this.handleTagModalSuccess = this.handleTagModalSuccess.bind(this);
this.handleTagModalError = this.handleTagModalError.bind(this);
ProfilePageStore.on('load-author-data', this.authorDataLoad);
}
@@ -202,42 +150,22 @@ export class ProfileMediaPage extends Page {
}
handleMediaSelection(mediaId, isSelected) {
const isSelectMediaMode = inSelectMediaEmbedMode();
// Only used in select media embed mode; normal mode is handled by bulkActions
this.setState((prevState) => {
const newSelectedMedia = new Set();
// In select media mode, only allow single selection
if (isSelectMediaMode) {
if (isSelected) {
newSelectedMedia.add(mediaId);
console.log('Selected media item:', mediaId);
if (isSelected) {
newSelectedMedia.add(mediaId);
// Send postMessage to parent window (Moodle TinyMCE plugin)
if (window.parent !== window) {
// Construct the embed URL
const baseUrl = window.location.origin;
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
if (window.parent !== window) {
const baseUrl = window.location.origin;
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
// Send message in the format expected by the Moodle plugin
window.parent.postMessage({
type: 'videoSelected',
embedUrl: embedUrl,
videoId: mediaId
}, '*');
console.log('Sent postMessage to parent:', { embedUrl, videoId: mediaId });
}
}
} else {
// Normal mode: allow multiple selection
newSelectedMedia.clear();
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
if (isSelected) {
newSelectedMedia.add(mediaId);
} else {
newSelectedMedia.delete(mediaId);
window.parent.postMessage({
type: 'videoSelected',
embedUrl: embedUrl,
videoId: mediaId
}, '*');
}
}
@@ -245,386 +173,6 @@ export class ProfileMediaPage extends Page {
});
}
handleBulkAction(action) {
const selectedCount = this.state.selectedMedia.size;
if (selectedCount === 0) {
return;
}
if (action === 'delete-media') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to delete') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'enable-comments') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to enable comments to') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'disable-comments') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to disable comments to') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'enable-download') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to enable download for') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'disable-download') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to disable download for') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'copy-media') {
this.setState({
showConfirmModal: true,
pendingAction: action,
confirmMessage:
translateString('You are going to copy') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
});
} else if (action === 'add-remove-coviewers') {
this.setState({
showPermissionModal: true,
permissionType: 'viewer',
});
} else if (action === 'add-remove-coeditors') {
this.setState({
showPermissionModal: true,
permissionType: 'editor',
});
} else if (action === 'add-remove-coowners') {
this.setState({
showPermissionModal: true,
permissionType: 'owner',
});
} else if (action === 'add-remove-playlist') {
this.setState({
showPlaylistModal: true,
});
} else if (action === 'change-owner') {
this.setState({
showChangeOwnerModal: true,
});
} else if (action === 'publish-state') {
this.setState({
showPublishStateModal: true,
});
} else if (action === 'add-remove-category') {
this.setState({
showCategoryModal: true,
});
} else if (action === 'add-remove-tags') {
this.setState({
showTagModal: true,
});
} else {
// Other actions can be implemented later
}
}
handleConfirmCancel() {
this.setState({
showConfirmModal: false,
pendingAction: null,
confirmMessage: '',
});
}
handleConfirmProceed() {
const action = this.state.pendingAction;
this.setState({
showConfirmModal: false,
pendingAction: null,
confirmMessage: '',
});
if (action === 'delete-media') {
this.executeDeleteMedia();
} else if (action === 'enable-comments') {
this.executeEnableComments();
} else if (action === 'disable-comments') {
this.executeDisableComments();
} else if (action === 'enable-download') {
this.executeEnableDownload();
} else if (action === 'disable-download') {
this.executeDisableDownload();
} else if (action === 'copy-media') {
this.executeCopyMedia();
}
}
executeDeleteMedia() {
const selectedIds = Array.from(this.state.selectedMedia);
const selectedCount = selectedIds.length;
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'delete_media',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to delete media');
}
return response.json();
})
.then((data) => {
const message =
selectedCount === 1
? translateString('The media was deleted successfully.')
: translateString('Successfully deleted') + ` ${selectedCount} ` + translateString('media.');
this.showNotification(message);
this.clearSelectionAndRefresh();
})
.catch((error) => {
this.showNotification(translateString('Failed to delete media. Please try again.'), 'error');
this.clearSelectionAndRefresh();
});
}
getCsrfToken() {
const name = 'csrftoken';
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + '=') {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
clearSelectionAndRefresh() {
// Clear selected media and increment listKey to force re-render
this.setState((prevState) => ({
selectedMedia: new Set(),
listKey: prevState.listKey + 1,
}));
}
clearSelection() {
// Clear selected media without refreshing
this.setState({
selectedMedia: new Set(),
});
}
executeEnableComments() {
const selectedIds = Array.from(this.state.selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'enable_comments',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to enable comments');
}
return response.json();
})
.then((data) => {
this.showNotification(translateString('Successfully Enabled comments'));
this.clearSelection();
})
.catch((error) => {
this.showNotification(translateString('Failed to enable comments.'), 'error');
this.clearSelection();
});
}
executeDisableComments() {
const selectedIds = Array.from(this.state.selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'disable_comments',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to disable comments');
}
return response.json();
})
.then((data) => {
this.showNotification(translateString('Successfully Disabled comments'));
this.clearSelection();
})
.catch((error) => {
this.showNotification(translateString('Failed to disable comments.'), 'error');
this.clearSelection();
});
}
executeEnableDownload() {
const selectedIds = Array.from(this.state.selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'enable_download',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to enable download');
}
return response.json();
})
.then((data) => {
this.showNotification(translateString('Successfully Enabled Download'));
this.clearSelection();
})
.catch((error) => {
this.showNotification(translateString('Failed to enable download.'), 'error');
this.clearSelection();
});
}
executeDisableDownload() {
const selectedIds = Array.from(this.state.selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'disable_download',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to disable download');
}
return response.json();
})
.then((data) => {
this.showNotification(translateString('Successfully Disabled Download'));
this.clearSelection();
})
.catch((error) => {
this.showNotification(translateString('Failed to disable download.'), 'error');
this.clearSelection();
});
}
executeCopyMedia() {
const selectedIds = Array.from(this.state.selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
},
body: JSON.stringify({
action: 'copy_media',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to copy media');
}
return response.json();
})
.then((data) => {
this.showNotification(translateString('Successfully Copied'));
this.clearSelectionAndRefresh();
})
.catch((error) => {
this.showNotification(translateString('Failed to copy media.'), 'error');
this.clearSelection();
});
}
showNotification(message, type = 'success') {
this.setState({
notificationMessage: message,
showNotification: true,
notificationType: type,
});
setTimeout(() => {
this.setState({ showNotification: false });
}, 5000);
}
handleItemsUpdate(items) {
// Extract media IDs from loaded items
const mediaIds = items.map((item) => item.friendly_token || item.uid || item.id);
this.setState({ availableMediaIds: mediaIds });
}
handleSelectAll() {
// Select all available media
this.setState({
selectedMedia: new Set(this.state.availableMediaIds),
});
}
handleDeselectAll() {
// Clear all selections
this.setState({
selectedMedia: new Set(),
});
}
onToggleFiltersClick() {
this.setState({
hiddenFilters: !this.state.hiddenFilters,
@@ -651,7 +199,6 @@ export class ProfileMediaPage extends Page {
onTagSelect(tag) {
this.setState({ selectedTag: tag }, () => {
// Apply tag filter
this.onFiltersUpdate({
media_type: this.state.filterArgs.includes('media_type')
? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1]
@@ -673,7 +220,6 @@ export class ProfileMediaPage extends Page {
onSortSelect(sortOption) {
this.setState({ selectedSort: sortOption }, () => {
// Apply sort filter
this.onFiltersUpdate({
media_type: this.state.filterArgs.includes('media_type')
? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1]
@@ -722,19 +268,16 @@ export class ProfileMediaPage extends Page {
break;
}
// Handle duration filter
if (updatedArgs.duration && updatedArgs.duration !== 'all') {
args.duration = updatedArgs.duration;
}
// Handle publish state filter
if (updatedArgs.publish_state && updatedArgs.publish_state !== 'all') {
args.publish_state = updatedArgs.publish_state;
}
switch (updatedArgs.sort_by) {
case 'date_added_desc':
// Default sorting, no need to add parameters
break;
case 'date_added_asc':
args.ordering = 'asc';
@@ -759,7 +302,6 @@ export class ProfileMediaPage extends Page {
break;
}
// Handle tag filter
if (updatedArgs.tag && updatedArgs.tag !== 'all') {
args.t = updatedArgs.tag;
}
@@ -775,10 +317,8 @@ export class ProfileMediaPage extends Page {
this.setState(
{
filterArgs: newArgs.length ? '&' + newArgs.join('&') : '',
selectedMedia: new Set(), // Clear selected items when filter changes
},
function () {
// Update the request URL with new filter args
if (!this.state.author) {
return;
}
@@ -805,137 +345,7 @@ export class ProfileMediaPage extends Page {
);
}
handlePermissionModalCancel() {
this.setState({
showPermissionModal: false,
permissionType: null,
});
}
handlePermissionModalSuccess(message) {
this.showNotification(message);
this.clearSelection();
this.setState({
showPermissionModal: false,
permissionType: null,
});
}
handlePermissionModalError(message) {
this.showNotification(message, 'error');
this.setState({
showPermissionModal: false,
permissionType: null,
});
}
handlePlaylistModalCancel() {
this.setState({
showPlaylistModal: false,
});
}
handlePlaylistModalSuccess(message) {
this.showNotification(message);
this.clearSelection();
this.setState({
showPlaylistModal: false,
});
}
handlePlaylistModalError(message) {
this.showNotification(message, 'error');
this.setState({
showPlaylistModal: false,
});
}
handleChangeOwnerModalCancel() {
this.setState({
showChangeOwnerModal: false,
});
}
handleChangeOwnerModalSuccess(message) {
this.showNotification(message);
this.clearSelectionAndRefresh();
this.setState({
showChangeOwnerModal: false,
});
}
handleChangeOwnerModalError(message) {
this.showNotification(message, 'error');
this.setState({
showChangeOwnerModal: false,
});
}
handlePublishStateModalCancel() {
this.setState({
showPublishStateModal: false,
});
}
handlePublishStateModalSuccess(message) {
this.showNotification(message);
this.clearSelectionAndRefresh();
this.setState({
showPublishStateModal: false,
});
}
handlePublishStateModalError(message) {
this.showNotification(message, 'error');
this.setState({
showPublishStateModal: false,
});
}
handleCategoryModalCancel() {
this.setState({
showCategoryModal: false,
});
}
handleCategoryModalSuccess(message) {
this.showNotification(message);
this.clearSelection();
this.setState({
showCategoryModal: false,
});
}
handleCategoryModalError(message) {
this.showNotification(message, 'error');
this.setState({
showCategoryModal: false,
});
}
handleTagModalCancel() {
this.setState({
showTagModal: false,
});
}
handleTagModalSuccess(message) {
this.showNotification(message);
this.clearSelection();
this.setState({
showTagModal: false,
});
}
handleTagModalError(message) {
this.showNotification(message, 'error');
this.setState({
showTagModal: false,
});
}
onResponseDataLoaded(responseData) {
// Extract tags from response
if (responseData && responseData.tags) {
const tags = responseData.tags
.split(',')
@@ -951,7 +361,6 @@ export class ProfileMediaPage extends Page {
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
const isSelectMediaMode = inSelectMediaEmbedMode();
// Check if any filters are active (excluding default sort and tags)
const hasActiveFilters =
this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
@@ -985,11 +394,11 @@ export class ProfileMediaPage extends Page {
className="items-list-ver"
style={inEmbeddedApp() ? { marginTop: '24px' } : undefined}
showBulkActions={!isSelectMediaMode && isMediaAuthor}
selectedCount={this.state.selectedMedia.size}
totalCount={this.state.availableMediaIds.length}
onBulkAction={this.handleBulkAction}
onSelectAll={this.handleSelectAll}
onDeselectAll={this.handleDeselectAll}
selectedCount={isSelectMediaMode ? this.state.selectedMedia.size : this.props.bulkActions.selectedMedia.size}
totalCount={isSelectMediaMode ? 0 : this.props.bulkActions.availableMediaIds.length}
onBulkAction={this.props.bulkActions.handleBulkAction}
onSelectAll={this.props.bulkActions.handleSelectAll}
onDeselectAll={this.props.bulkActions.handleDeselectAll}
showAddMediaButton={!isSelectMediaMode && isMediaAuthor}
>
<ProfileMediaFilters
@@ -1006,7 +415,7 @@ export class ProfileMediaPage extends Page {
/>
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<LazyLoadItemListAsync
key={`${this.state.requestUrl}-${this.state.listKey}`}
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}
requestUrl={this.state.requestUrl}
hideAuthor={true}
itemsCountCallback={this.state.requestUrl ? this.getCountFunc : null}
@@ -1014,97 +423,43 @@ export class ProfileMediaPage extends Page {
hideDate={!PageStore.get('config-media-item').displayPublishDate}
canEdit={isMediaAuthor}
showSelection={isMediaAuthor || isSelectMediaMode}
hasAnySelection={this.state.selectedMedia.size > 0}
selectedMedia={this.state.selectedMedia}
onMediaSelection={this.handleMediaSelection}
onItemsUpdate={this.handleItemsUpdate}
hasAnySelection={isSelectMediaMode ? this.state.selectedMedia.size > 0 : this.props.bulkActions.selectedMedia.size > 0}
selectedMedia={isSelectMediaMode ? this.state.selectedMedia : this.props.bulkActions.selectedMedia}
onMediaSelection={isSelectMediaMode ? this.handleMediaSelection : this.props.bulkActions.handleMediaSelection}
onItemsUpdate={!isSelectMediaMode ? this.props.bulkActions.handleItemsUpdate : undefined}
onResponseDataLoaded={this.onResponseDataLoaded}
/>
</MediaListWrapper>
</ProfilePagesContent>
) : null,
<BulkActionConfirmModal
key="BulkActionConfirmModal"
isOpen={this.state.showConfirmModal}
message={this.state.confirmMessage}
onCancel={this.handleConfirmCancel}
onProceed={this.handleConfirmProceed}
/>,
<BulkActionPermissionModal
key="BulkActionPermissionModal"
isOpen={this.state.showPermissionModal}
permissionType={this.state.permissionType}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handlePermissionModalCancel}
onSuccess={this.handlePermissionModalSuccess}
onError={this.handlePermissionModalError}
csrfToken={this.getCsrfToken()}
/>,
<BulkActionPlaylistModal
key="BulkActionPlaylistModal"
isOpen={this.state.showPlaylistModal}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handlePlaylistModalCancel}
onSuccess={this.handlePlaylistModalSuccess}
onError={this.handlePlaylistModalError}
csrfToken={this.getCsrfToken()}
username={this.state.author ? this.state.author.username : ''}
/>,
<BulkActionChangeOwnerModal
key="BulkActionChangeOwnerModal"
isOpen={this.state.showChangeOwnerModal}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handleChangeOwnerModalCancel}
onSuccess={this.handleChangeOwnerModalSuccess}
onError={this.handleChangeOwnerModalError}
csrfToken={this.getCsrfToken()}
/>,
<BulkActionPublishStateModal
key="BulkActionPublishStateModal"
isOpen={this.state.showPublishStateModal}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handlePublishStateModalCancel}
onSuccess={this.handlePublishStateModalSuccess}
onError={this.handlePublishStateModalError}
csrfToken={this.getCsrfToken()}
/>,
<BulkActionCategoryModal
key="BulkActionCategoryModal"
isOpen={this.state.showCategoryModal}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handleCategoryModalCancel}
onSuccess={this.handleCategoryModalSuccess}
onError={this.handleCategoryModalError}
csrfToken={this.getCsrfToken()}
/>,
<BulkActionTagModal
key="BulkActionTagModal"
isOpen={this.state.showTagModal}
selectedMediaIds={Array.from(this.state.selectedMedia)}
onCancel={this.handleTagModalCancel}
onSuccess={this.handleTagModalSuccess}
onError={this.handleTagModalError}
csrfToken={this.getCsrfToken()}
/>,
this.state.showNotification ? (
<div
key="SimpleNotification"
style={{
position: 'fixed',
bottom: '20px',
left: '260px',
backgroundColor: this.state.notificationType === 'error' ? '#f44336' : '#4CAF50',
color: 'white',
padding: '16px 24px',
borderRadius: '4px',
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)',
zIndex: 1000,
fontSize: '14px',
fontWeight: '500',
}}
>
{this.state.notificationMessage}
</div>
this.state.author && isMediaAuthor && !isSelectMediaMode ? (
<BulkActionsModals
key="BulkActionsModals"
{...this.props.bulkActions}
selectedMediaIds={Array.from(this.props.bulkActions.selectedMedia)}
csrfToken={this.props.bulkActions.getCsrfToken()}
username={this.state.author.username}
onConfirmCancel={this.props.bulkActions.handleConfirmCancel}
onConfirmProceed={this.props.bulkActions.handleConfirmProceed}
onPermissionModalCancel={this.props.bulkActions.handlePermissionModalCancel}
onPermissionModalSuccess={this.props.bulkActions.handlePermissionModalSuccess}
onPermissionModalError={this.props.bulkActions.handlePermissionModalError}
onPlaylistModalCancel={this.props.bulkActions.handlePlaylistModalCancel}
onPlaylistModalSuccess={this.props.bulkActions.handlePlaylistModalSuccess}
onPlaylistModalError={this.props.bulkActions.handlePlaylistModalError}
onChangeOwnerModalCancel={this.props.bulkActions.handleChangeOwnerModalCancel}
onChangeOwnerModalSuccess={this.props.bulkActions.handleChangeOwnerModalSuccess}
onChangeOwnerModalError={this.props.bulkActions.handleChangeOwnerModalError}
onPublishStateModalCancel={this.props.bulkActions.handlePublishStateModalCancel}
onPublishStateModalSuccess={this.props.bulkActions.handlePublishStateModalSuccess}
onPublishStateModalError={this.props.bulkActions.handlePublishStateModalError}
onCategoryModalCancel={this.props.bulkActions.handleCategoryModalCancel}
onCategoryModalSuccess={this.props.bulkActions.handleCategoryModalSuccess}
onCategoryModalError={this.props.bulkActions.handleCategoryModalError}
onTagModalCancel={this.props.bulkActions.handleTagModalCancel}
onTagModalSuccess={this.props.bulkActions.handleTagModalSuccess}
onTagModalError={this.props.bulkActions.handleTagModalError}
/>
) : null,
];
}
@@ -1112,8 +467,17 @@ export class ProfileMediaPage extends Page {
ProfileMediaPage.propTypes = {
title: PropTypes.string.isRequired,
bulkActions: PropTypes.object.isRequired,
};
ProfileMediaPage.defaultProps = {
title: 'Uploads',
};
// Export the raw class for subclasses to extend
export { ProfileMediaPage as ProfileMediaPageBase };
// Export the HOC-wrapped version as the renderable component
const WrappedProfileMediaPage = withBulkActions(ProfileMediaPage);
export { WrappedProfileMediaPage as ProfileMediaPage };
export default WrappedProfileMediaPage;

View File

@@ -6,9 +6,9 @@ import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListAsync.jsx';
import { ProfileMediaPage } from './ProfileMediaPage';
import { ProfileMediaPageBase } from './ProfileMediaPage';
export class ProfilePlaylistsPage extends ProfileMediaPage {
export class ProfilePlaylistsPage extends ProfileMediaPageBase {
constructor(props) {
super(props, 'author-playlists');

View File

@@ -111,6 +111,10 @@ export function useBulkActions() {
setShowConfirmModal(true);
setPendingAction(action);
setConfirmMessage(translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'));
} else if (action === 'delete-comments') {
setShowConfirmModal(true);
setPendingAction(action);
setConfirmMessage(translateString('You are going to delete all comments from') + ` ${selectedCount} ` + translateString('media, are you sure?'));
} else if (action === 'enable-download') {
setShowConfirmModal(true);
setPendingAction(action);
@@ -165,6 +169,8 @@ export function useBulkActions() {
executeEnableComments();
} else if (action === 'disable-comments') {
executeDisableComments();
} else if (action === 'delete-comments') {
executeDeleteComments();
} else if (action === 'enable-download') {
executeEnableDownload();
} else if (action === 'disable-download') {
@@ -271,6 +277,37 @@ export function useBulkActions() {
});
};
// Execute delete comments
const executeDeleteComments = () => {
const selectedIds = Array.from(selectedMedia);
fetch('/api/v1/media/user/bulk_actions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCsrfToken(),
},
body: JSON.stringify({
action: 'delete_comments',
media_ids: selectedIds,
}),
})
.then((response) => {
if (!response.ok) {
throw new Error('Failed to delete comments');
}
return response.json();
})
.then((data) => {
showNotificationMessage(translateString('Successfully deleted comments'));
clearSelection();
})
.catch((error) => {
showNotificationMessage(translateString('Failed to delete comments.'), 'error');
clearSelection();
});
};
// Execute enable download
const executeEnableDownload = () => {
const selectedIds = Array.from(selectedMedia);