mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-05-04 20:02:43 -04:00
all
This commit is contained in:
+1
-1
@@ -1 +1 @@
|
||||
VERSION = "8.998"
|
||||
VERSION = "8.999"
|
||||
|
||||
+1
-12
@@ -305,21 +305,10 @@ class MediaPublishForm(forms.ModelForm):
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
state = cleaned_data.get("state")
|
||||
shared = cleaned_data.get("shared")
|
||||
|
||||
if self.was_shared and not shared and not cleaned_data.get('confirm_state'):
|
||||
if state == 'private':
|
||||
error_parts = []
|
||||
rbac_cat_titles = list(self.instance.category.filter(is_rbac_category=True).values_list('title', flat=True))
|
||||
if rbac_cat_titles:
|
||||
error_parts.append(f"shared with users that have access to categories: {', '.join(rbac_cat_titles)}")
|
||||
if self.instance.permissions.exists():
|
||||
error_parts.append("shared by me with other users (visible in 'Shared by me' page)")
|
||||
detail = f" Currently this media is {' and '.join(error_parts)}." if error_parts else ""
|
||||
self.add_error('confirm_state', f"I understand that this will remove all sharing.{detail}")
|
||||
else:
|
||||
self.add_error('confirm_state', "I understand that unchecking Shared will affect existing sharing settings.")
|
||||
self.add_error('confirm_state', "I understand that unchecking Shared will remove all existing sharing for this media.")
|
||||
|
||||
return cleaned_data
|
||||
|
||||
|
||||
+10
-1
@@ -506,7 +506,16 @@ class MediaBulkUserActions(APIView):
|
||||
|
||||
m.save(update_fields=["state", "listable"])
|
||||
|
||||
if state == "private":
|
||||
shared = request.data.get('shared', None)
|
||||
|
||||
if shared is True:
|
||||
for m in media:
|
||||
MediaPermission.objects.get_or_create(
|
||||
media=m,
|
||||
user=request.user,
|
||||
defaults={'owner_user': request.user, 'permission': 'owner'},
|
||||
)
|
||||
elif shared is False or (shared is None and state == 'private'):
|
||||
MediaPermission.objects.filter(media__in=media).delete()
|
||||
for m in media:
|
||||
rbac_cats = m.category.filter(is_rbac_category=True)
|
||||
|
||||
@@ -159,6 +159,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
.shared-selector {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
&-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
input[type='checkbox'] {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&-note {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #777;
|
||||
|
||||
&--warn {
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #aaa;
|
||||
|
||||
&--warn {
|
||||
color: #f59e0b;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.publish-state-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import './BulkActionPublishStateModal.scss';
|
||||
import { translateString } from '../utils/helpers/';
|
||||
|
||||
@@ -32,14 +32,23 @@ export const BulkActionPublishStateModal: React.FC<BulkActionPublishStateModalPr
|
||||
const defaultState = availableStates[0].value;
|
||||
|
||||
const [selectedState, setSelectedState] = useState(defaultState);
|
||||
const [shared, setShared] = useState<boolean | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const sharedRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setSelectedState(defaultState);
|
||||
setShared(null);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (sharedRef.current) {
|
||||
sharedRef.current.indeterminate = shared === null;
|
||||
}
|
||||
}, [shared]);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!selectedState) {
|
||||
onError(translateString('Please select a publish state'));
|
||||
@@ -49,17 +58,22 @@ export const BulkActionPublishStateModal: React.FC<BulkActionPublishStateModalPr
|
||||
setIsProcessing(true);
|
||||
|
||||
try {
|
||||
const body: Record<string, unknown> = {
|
||||
action: 'set_state',
|
||||
media_ids: selectedMediaIds,
|
||||
state: selectedState,
|
||||
};
|
||||
if (shared !== null) {
|
||||
body.shared = shared;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/v1/media/user/bulk_actions', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
action: 'set_state',
|
||||
media_ids: selectedMediaIds,
|
||||
state: selectedState,
|
||||
}),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -79,9 +93,12 @@ export const BulkActionPublishStateModal: React.FC<BulkActionPublishStateModalPr
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
// Note: We don't check hasStateChanged because the modal doesn't know the actual
|
||||
// current state of the selected media. Users should be able to set any state.
|
||||
// If the state is already the same, the backend will handle it gracefully.
|
||||
const sharedNote =
|
||||
shared === null
|
||||
? translateString('Sharing status will not be changed.')
|
||||
: shared
|
||||
? translateString('Selected media will be marked as shared.')
|
||||
: translateString('Sharing will be removed from all selected media.');
|
||||
|
||||
return (
|
||||
<div className="publish-state-modal-overlay">
|
||||
@@ -109,6 +126,22 @@ export const BulkActionPublishStateModal: React.FC<BulkActionPublishStateModalPr
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="shared-selector">
|
||||
<label className="shared-selector-label">
|
||||
<input
|
||||
ref={sharedRef}
|
||||
type="checkbox"
|
||||
checked={shared === true}
|
||||
onChange={(e) => setShared(e.target.checked)}
|
||||
disabled={isProcessing}
|
||||
/>
|
||||
{translateString('Shared')}
|
||||
</label>
|
||||
<p className={`shared-selector-note${shared === false ? ' shared-selector-note--warn' : ''}`}>
|
||||
{sharedNote}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="publish-state-modal-footer">
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user