mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Compare commits
6 Commits
f65338562e
...
02eb712a50
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
02eb712a50 | ||
|
|
8c73633429 | ||
|
|
496285e9e1 | ||
|
|
55c5b0be12 | ||
|
|
3c74badaec | ||
|
|
030e3cbe68 |
@ -1 +1 @@
|
|||||||
VERSION = "7.1.0"
|
VERSION = "7.3.0"
|
||||||
|
|||||||
@ -357,6 +357,10 @@ class Media(models.Model):
|
|||||||
a_tags,
|
a_tags,
|
||||||
b_tags,
|
b_tags,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
for subtitle in self.subtitles.all():
|
||||||
|
items.append(subtitle.subtitle_text)
|
||||||
|
|
||||||
items = [item for item in items if item]
|
items = [item for item in items if item]
|
||||||
text = " ".join(items)
|
text = " ".join(items)
|
||||||
text = " ".join([token for token in text.lower().split(" ") if token not in STOP_WORDS])
|
text = " ".join([token for token in text.lower().split(" ") if token not in STOP_WORDS])
|
||||||
@ -406,11 +410,11 @@ class Media(models.Model):
|
|||||||
self.media_type = "image"
|
self.media_type = "image"
|
||||||
elif kind == "pdf":
|
elif kind == "pdf":
|
||||||
self.media_type = "pdf"
|
self.media_type = "pdf"
|
||||||
|
if self.media_type in ["image", "pdf"]:
|
||||||
if self.media_type in ["audio", "image", "pdf"]:
|
|
||||||
self.encoding_status = "success"
|
self.encoding_status = "success"
|
||||||
else:
|
else:
|
||||||
ret = helpers.media_file_info(self.media_file.path)
|
ret = helpers.media_file_info(self.media_file.path)
|
||||||
|
|
||||||
if ret.get("fail"):
|
if ret.get("fail"):
|
||||||
self.media_type = ""
|
self.media_type = ""
|
||||||
self.encoding_status = "fail"
|
self.encoding_status = "fail"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
import pysubs2
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -73,6 +74,17 @@ class Subtitle(models.Model):
|
|||||||
raise Exception("Could not convert to srt")
|
raise Exception("Could not convert to srt")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subtitle_text(self):
|
||||||
|
sub = pysubs2.load(self.subtitle_file.path, encoding="utf-8")
|
||||||
|
text = ' '.join([line.text for line in sub])
|
||||||
|
text = text.replace("\\N", " ")
|
||||||
|
text = text.replace("-", " ")
|
||||||
|
text = text.replace(".", " ")
|
||||||
|
text = text.replace(" ", " ")
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
class TranscriptionRequest(models.Model):
|
class TranscriptionRequest(models.Model):
|
||||||
# Whisper transcription request
|
# Whisper transcription request
|
||||||
|
|||||||
@ -1003,7 +1003,6 @@ def video_trim_task(self, trim_request_id):
|
|||||||
|
|
||||||
timestamps_encodings = get_trim_timestamps(trim_request.media.trim_video_path, trim_request.timestamps)
|
timestamps_encodings = get_trim_timestamps(trim_request.media.trim_video_path, trim_request.timestamps)
|
||||||
timestamps_original = get_trim_timestamps(trim_request.media.media_file.path, trim_request.timestamps)
|
timestamps_original = get_trim_timestamps(trim_request.media.media_file.path, trim_request.timestamps)
|
||||||
|
|
||||||
if not timestamps_encodings:
|
if not timestamps_encodings:
|
||||||
trim_request.status = "fail"
|
trim_request.status = "fail"
|
||||||
trim_request.save(update_fields=["status"])
|
trim_request.save(update_fields=["status"])
|
||||||
|
|||||||
@ -174,15 +174,15 @@ class MediaList(APIView):
|
|||||||
media = Media.objects.none()
|
media = Media.objects.none()
|
||||||
else:
|
else:
|
||||||
base_queryset = Media.objects.prefetch_related("user", "tags")
|
base_queryset = Media.objects.prefetch_related("user", "tags")
|
||||||
user_media_filters = {'permissions__user': request.user}
|
|
||||||
media = base_queryset.filter(**user_media_filters)
|
# Build OR conditions similar to _get_media_queryset
|
||||||
|
conditions = Q(permissions__user=request.user)
|
||||||
|
|
||||||
if getattr(settings, 'USE_RBAC', False):
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
rbac_categories = request.user.get_rbac_categories_as_member()
|
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||||
rbac_filters = {'category__in': rbac_categories}
|
conditions |= Q(category__in=rbac_categories)
|
||||||
|
|
||||||
rbac_media = base_queryset.filter(**rbac_filters)
|
media = base_queryset.filter(conditions).distinct()
|
||||||
media = media.union(rbac_media)
|
|
||||||
elif author_param:
|
elif author_param:
|
||||||
user_queryset = User.objects.all()
|
user_queryset = User.objects.all()
|
||||||
user = get_object_or_404(user_queryset, username=author_param)
|
user = get_object_or_404(user_queryset, username=author_param)
|
||||||
@ -221,13 +221,12 @@ class MediaList(APIView):
|
|||||||
if publish_state and publish_state in ['private', 'public', 'unlisted']:
|
if publish_state and publish_state in ['private', 'public', 'unlisted']:
|
||||||
media = media.filter(state=publish_state)
|
media = media.filter(state=publish_state)
|
||||||
|
|
||||||
if show_param == "shared_with_me":
|
|
||||||
media = media[:1000] # limit to 1000 results
|
|
||||||
already_sorted = True
|
|
||||||
|
|
||||||
if not already_sorted:
|
if not already_sorted:
|
||||||
media = media.order_by(f"{ordering}{sort_by}")
|
media = media.order_by(f"{ordering}{sort_by}")
|
||||||
|
|
||||||
|
if show_param == "shared_with_me":
|
||||||
|
media = media[:1000] # limit to 1000 results
|
||||||
|
|
||||||
paginator = pagination_class()
|
paginator = pagination_class()
|
||||||
|
|
||||||
page = paginator.paginate_queryset(media, request)
|
page = paginator.paginate_queryset(media, request)
|
||||||
|
|||||||
@ -430,7 +430,7 @@ def edit_video(request):
|
|||||||
return HttpResponseRedirect("/")
|
return HttpResponseRedirect("/")
|
||||||
|
|
||||||
if media.media_type not in ["video", "audio"]:
|
if media.media_type not in ["video", "audio"]:
|
||||||
messages.add_message(request, messages.INFO, "Media is not video")
|
messages.add_message(request, messages.INFO, "Media is not video or audio")
|
||||||
return HttpResponseRedirect(media.get_absolute_url())
|
return HttpResponseRedirect(media.get_absolute_url())
|
||||||
|
|
||||||
if not settings.ALLOW_VIDEO_TRIMMER:
|
if not settings.ALLOW_VIDEO_TRIMMER:
|
||||||
|
|||||||
@ -87,7 +87,7 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}`);
|
const response = await fetch(`/api/v1/users?name=${encodeURIComponent(name)}&exclude_self=True`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(translateString('Failed to search users'));
|
throw new Error(translateString('Failed to search users'));
|
||||||
}
|
}
|
||||||
@ -110,6 +110,12 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
|||||||
clearTimeout(searchTimeout);
|
clearTimeout(searchTimeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only search if 3 or more characters
|
||||||
|
if (value.trim().length < 3) {
|
||||||
|
setSearchResults([]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Set new timeout for debounced search
|
// Set new timeout for debounced search
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
searchUsers(value);
|
searchUsers(value);
|
||||||
@ -231,7 +237,7 @@ export const BulkActionPermissionModal: React.FC<BulkActionPermissionModalProps>
|
|||||||
<div className="search-box">
|
<div className="search-box">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder={translateString('Search users to add...')}
|
placeholder={translateString('Search users to add (min 3 characters)...')}
|
||||||
value={addSearchTerm}
|
value={addSearchTerm}
|
||||||
onChange={(e) => handleAddSearchChange(e.target.value)}
|
onChange={(e) => handleAddSearchChange(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -696,6 +696,11 @@ a.item-edit-link {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show all checkboxes when any item has a selection
|
||||||
|
&.has-any-selection .item-selection-checkbox {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Add hover shadow when any selection is active
|
// Add hover shadow when any selection is active
|
||||||
&.has-any-selection:not(.selected):hover {
|
&.has-any-selection:not(.selected):hover {
|
||||||
.item-content {
|
.item-content {
|
||||||
|
|||||||
@ -27,15 +27,33 @@ export function MediaItem(props) {
|
|||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||||
|
|
||||||
|
const handleItemClick = (e) => {
|
||||||
|
// If there's any selection active, clicking the item should toggle selection
|
||||||
|
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||||
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
|
e.target.closest('.item-edit-icon') ||
|
||||||
|
e.target.closest('.item-view-icon')) {
|
||||||
|
return; // Let these elements handle their own clicks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent all other clicks and toggle selection
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={finalClassname}>
|
<div className={finalClassname} onClick={handleItemClick}>
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
{props.showSelection && (
|
{props.showSelection && (
|
||||||
<div className="item-selection-checkbox">
|
<div className="item-selection-checkbox" onClick={(e) => e.stopPropagation()}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={props.isSelected || false}
|
checked={props.isSelected || false}
|
||||||
onChange={(e) => { props.onCheckboxChange && props.onCheckboxChange(e); }}
|
onChange={(e) => { props.onCheckboxChange && props.onCheckboxChange(e); }}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
aria-label="Select media"
|
aria-label="Select media"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -70,8 +70,25 @@ export function MediaItemAudio(props) {
|
|||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||||
|
|
||||||
|
const handleItemClick = (e) => {
|
||||||
|
// If there's any selection active, clicking the item should toggle selection
|
||||||
|
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||||
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
|
e.target.closest('.item-edit-icon') ||
|
||||||
|
e.target.closest('.item-view-icon')) {
|
||||||
|
return; // Let these elements handle their own clicks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent all other clicks and toggle selection
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={finalClassname}>
|
<div className={finalClassname} onClick={handleItemClick}>
|
||||||
{playlistOrderNumberComponent()}
|
{playlistOrderNumberComponent()}
|
||||||
|
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
|
|||||||
@ -77,8 +77,25 @@ export function MediaItemVideo(props) {
|
|||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||||
|
|
||||||
|
const handleItemClick = (e) => {
|
||||||
|
// If there's any selection active, clicking the item should toggle selection
|
||||||
|
if (props.hasAnySelection && props.onCheckboxChange) {
|
||||||
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
|
e.target.closest('.item-edit-icon') ||
|
||||||
|
e.target.closest('.item-view-icon')) {
|
||||||
|
return; // Let these elements handle their own clicks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent all other clicks and toggle selection
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
props.onCheckboxChange({ target: { checked: !props.isSelected } });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={finalClassname}>
|
<div className={finalClassname} onClick={handleItemClick}>
|
||||||
{playlistOrderNumberComponent()}
|
{playlistOrderNumberComponent()}
|
||||||
|
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
|
|||||||
@ -414,10 +414,22 @@ class NavMenuInlineTabs extends React.PureComponent {
|
|||||||
) : null}
|
) : null}
|
||||||
{this.props.onToggleSortingClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
{this.props.onToggleSortingClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
||||||
<li className="media-sorting-toggle">
|
<li className="media-sorting-toggle">
|
||||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={this.props.onToggleSortingClick} title={translateString('Sort By')}>
|
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.props.onToggleSortingClick} title={translateString('Sort By')}>
|
||||||
<CircleIconButton buttonShadow={false}>
|
<CircleIconButton buttonShadow={false}>
|
||||||
<i className="material-icons">swap_vert</i>
|
<i className="material-icons">swap_vert</i>
|
||||||
</CircleIconButton>
|
</CircleIconButton>
|
||||||
|
{this.props.hasActiveSort ? (
|
||||||
|
<span style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '8px',
|
||||||
|
right: '8px',
|
||||||
|
width: '8px',
|
||||||
|
height: '8px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: 'var(--default-theme-color)',
|
||||||
|
border: '2px solid white',
|
||||||
|
}}></span>
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
) : null}
|
) : null}
|
||||||
@ -438,6 +450,7 @@ NavMenuInlineTabs.propTypes = {
|
|||||||
onToggleSortingClick: PropTypes.func,
|
onToggleSortingClick: PropTypes.func,
|
||||||
hasActiveFilters: PropTypes.bool,
|
hasActiveFilters: PropTypes.bool,
|
||||||
hasActiveTags: PropTypes.bool,
|
hasActiveTags: PropTypes.bool,
|
||||||
|
hasActiveSort: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
function AddBannerButton(props) {
|
function AddBannerButton(props) {
|
||||||
@ -663,6 +676,7 @@ export default function ProfilePagesHeader(props) {
|
|||||||
onToggleSortingClick={props.onToggleSortingClick}
|
onToggleSortingClick={props.onToggleSortingClick}
|
||||||
hasActiveFilters={props.hasActiveFilters}
|
hasActiveFilters={props.hasActiveFilters}
|
||||||
hasActiveTags={props.hasActiveTags}
|
hasActiveTags={props.hasActiveTags}
|
||||||
|
hasActiveSort={props.hasActiveSort}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -678,6 +692,7 @@ ProfilePagesHeader.propTypes = {
|
|||||||
onToggleSortingClick: PropTypes.func,
|
onToggleSortingClick: PropTypes.func,
|
||||||
hasActiveFilters: PropTypes.bool,
|
hasActiveFilters: PropTypes.bool,
|
||||||
hasActiveTags: PropTypes.bool,
|
hasActiveTags: PropTypes.bool,
|
||||||
|
hasActiveSort: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
ProfilePagesHeader.defaultProps = {
|
ProfilePagesHeader.defaultProps = {
|
||||||
|
|||||||
@ -67,6 +67,9 @@ export function ProfileMediaFilters(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onFilterSelect(ev) {
|
function onFilterSelect(ev) {
|
||||||
|
const filterType = ev.currentTarget.getAttribute('filter');
|
||||||
|
const clickedValue = ev.currentTarget.getAttribute('value');
|
||||||
|
|
||||||
const args = {
|
const args = {
|
||||||
media_type: mediaTypeFilter,
|
media_type: mediaTypeFilter,
|
||||||
upload_date: uploadDateFilter,
|
upload_date: uploadDateFilter,
|
||||||
@ -76,34 +79,35 @@ export function ProfileMediaFilters(props) {
|
|||||||
tag: props.selectedTag || tagFilter,
|
tag: props.selectedTag || tagFilter,
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (ev.currentTarget.getAttribute('filter')) {
|
switch (filterType) {
|
||||||
case 'media_type':
|
case 'media_type':
|
||||||
args.media_type = ev.currentTarget.getAttribute('value');
|
// If clicking the currently selected filter, deselect it (set to 'all')
|
||||||
|
args.media_type = clickedValue === mediaTypeFilter ? 'all' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_media_type(args.media_type);
|
setFilter_media_type(args.media_type);
|
||||||
break;
|
break;
|
||||||
case 'upload_date':
|
case 'upload_date':
|
||||||
args.upload_date = ev.currentTarget.getAttribute('value');
|
args.upload_date = clickedValue === uploadDateFilter ? 'all' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_upload_date(args.upload_date);
|
setFilter_upload_date(args.upload_date);
|
||||||
break;
|
break;
|
||||||
case 'duration':
|
case 'duration':
|
||||||
args.duration = ev.currentTarget.getAttribute('value');
|
args.duration = clickedValue === durationFilter ? 'all' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_duration(args.duration);
|
setFilter_duration(args.duration);
|
||||||
break;
|
break;
|
||||||
case 'publish_state':
|
case 'publish_state':
|
||||||
args.publish_state = ev.currentTarget.getAttribute('value');
|
args.publish_state = clickedValue === publishStateFilter ? 'all' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_publish_state(args.publish_state);
|
setFilter_publish_state(args.publish_state);
|
||||||
break;
|
break;
|
||||||
case 'sort_by':
|
case 'sort_by':
|
||||||
args.sort_by = ev.currentTarget.getAttribute('value');
|
args.sort_by = clickedValue === sortByFilter ? 'date_added_desc' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_sort_by(args.sort_by);
|
setFilter_sort_by(args.sort_by);
|
||||||
break;
|
break;
|
||||||
case 'tag':
|
case 'tag':
|
||||||
args.tag = ev.currentTarget.getAttribute('value');
|
args.tag = clickedValue === tagFilter ? 'all' : clickedValue;
|
||||||
props.onFiltersUpdate(args);
|
props.onFiltersUpdate(args);
|
||||||
setFilter_tag(args.tag);
|
setFilter_tag(args.tag);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -26,8 +26,10 @@ export function ProfileMediaTags(props) {
|
|||||||
|
|
||||||
function onFilterSelect(ev) {
|
function onFilterSelect(ev) {
|
||||||
const tag = ev.currentTarget.getAttribute('value');
|
const tag = ev.currentTarget.getAttribute('value');
|
||||||
setFilter_tag(tag);
|
// If clicking the currently selected tag, deselect it (set to 'all')
|
||||||
props.onTagSelect(tag);
|
const newTag = tag === tagFilter ? 'all' : tag;
|
||||||
|
setFilter_tag(newTag);
|
||||||
|
props.onTagSelect(newTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -605,7 +605,7 @@ export class ProfileMediaPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTagSelect(tag) {
|
onTagSelect(tag) {
|
||||||
this.setState({ selectedTag: tag, hiddenTags: true }, () => {
|
this.setState({ selectedTag: tag }, () => {
|
||||||
// Apply tag filter
|
// Apply tag filter
|
||||||
this.onFiltersUpdate({
|
this.onFiltersUpdate({
|
||||||
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
||||||
@ -617,7 +617,7 @@ export class ProfileMediaPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSortSelect(sortOption) {
|
onSortSelect(sortOption) {
|
||||||
this.setState({ selectedSort: sortOption, hiddenSorting: true }, () => {
|
this.setState({ selectedSort: sortOption }, () => {
|
||||||
// Apply sort filter
|
// Apply sort filter
|
||||||
this.onFiltersUpdate({
|
this.onFiltersUpdate({
|
||||||
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
media_type: this.state.filterArgs.includes('media_type') ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1] : null,
|
||||||
@ -884,12 +884,15 @@ export class ProfileMediaPage extends Page {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const hasActiveTags = this.state.selectedTag && this.state.selectedTag !== 'all';
|
const hasActiveTags = this.state.selectedTag && this.state.selectedTag !== 'all';
|
||||||
|
const hasActiveSort = this.state.selectedSort && this.state.selectedSort !== 'date_added_desc';
|
||||||
|
|
||||||
console.log('Filter Debug:', {
|
console.log('Filter Debug:', {
|
||||||
filterArgs: this.state.filterArgs,
|
filterArgs: this.state.filterArgs,
|
||||||
selectedTag: this.state.selectedTag,
|
selectedTag: this.state.selectedTag,
|
||||||
|
selectedSort: this.state.selectedSort,
|
||||||
hasActiveFilters,
|
hasActiveFilters,
|
||||||
hasActiveTags
|
hasActiveTags,
|
||||||
|
hasActiveSort
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@ -904,6 +907,7 @@ export class ProfileMediaPage extends Page {
|
|||||||
onToggleSortingClick={this.onToggleSortingClick}
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
hasActiveFilters={hasActiveFilters}
|
hasActiveFilters={hasActiveFilters}
|
||||||
hasActiveTags={hasActiveTags}
|
hasActiveTags={hasActiveTags}
|
||||||
|
hasActiveSort={hasActiveSort}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
|
|||||||
@ -151,17 +151,23 @@ export class ProfileSharedByMePage extends Page {
|
|||||||
onToggleFiltersClick() {
|
onToggleFiltersClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
hiddenFilters: !this.state.hiddenFilters,
|
||||||
|
hiddenTags: true,
|
||||||
|
hiddenSorting: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleTagsClick() {
|
onToggleTagsClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
hiddenTags: !this.state.hiddenTags,
|
hiddenTags: !this.state.hiddenTags,
|
||||||
|
hiddenSorting: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleSortingClick() {
|
onToggleSortingClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
|
hiddenTags: true,
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
hiddenSorting: !this.state.hiddenSorting,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -290,6 +296,12 @@ export class ProfileSharedByMePage extends Page {
|
|||||||
|
|
||||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
|
|
||||||
|
// Check if any filters are active
|
||||||
|
const hasActiveFilters = this.state.filterArgs && (
|
||||||
|
this.state.filterArgs.includes('media_type=') ||
|
||||||
|
this.state.filterArgs.includes('upload_date=')
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader
|
<ProfilePagesHeader
|
||||||
@ -300,6 +312,9 @@ export class ProfileSharedByMePage extends Page {
|
|||||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
onToggleTagsClick={this.onToggleTagsClick}
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
onToggleSortingClick={this.onToggleSortingClick}
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
|
|||||||
@ -151,17 +151,23 @@ export class ProfileSharedWithMePage extends Page {
|
|||||||
onToggleFiltersClick() {
|
onToggleFiltersClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
hiddenFilters: !this.state.hiddenFilters,
|
hiddenFilters: !this.state.hiddenFilters,
|
||||||
|
hiddenTags: true,
|
||||||
|
hiddenSorting: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleTagsClick() {
|
onToggleTagsClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
hiddenTags: !this.state.hiddenTags,
|
hiddenTags: !this.state.hiddenTags,
|
||||||
|
hiddenSorting: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleSortingClick() {
|
onToggleSortingClick() {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
hiddenFilters: true,
|
||||||
|
hiddenTags: true,
|
||||||
hiddenSorting: !this.state.hiddenSorting,
|
hiddenSorting: !this.state.hiddenSorting,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -290,6 +296,12 @@ export class ProfileSharedWithMePage extends Page {
|
|||||||
|
|
||||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
|
|
||||||
|
// Check if any filters are active
|
||||||
|
const hasActiveFilters = this.state.filterArgs && (
|
||||||
|
this.state.filterArgs.includes('media_type=') ||
|
||||||
|
this.state.filterArgs.includes('upload_date=')
|
||||||
|
);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesHeader
|
<ProfilePagesHeader
|
||||||
@ -300,6 +312,9 @@ export class ProfileSharedWithMePage extends Page {
|
|||||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||||
onToggleTagsClick={this.onToggleTagsClick}
|
onToggleTagsClick={this.onToggleTagsClick}
|
||||||
onToggleSortingClick={this.onToggleSortingClick}
|
onToggleSortingClick={this.onToggleSortingClick}
|
||||||
|
hasActiveFilters={hasActiveFilters}
|
||||||
|
hasActiveTags={this.state.selectedTag !== 'all'}
|
||||||
|
hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
|
||||||
/>
|
/>
|
||||||
) : null,
|
) : null,
|
||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -206,6 +206,7 @@ class UserList(APIView):
|
|||||||
manual_parameters=[
|
manual_parameters=[
|
||||||
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
|
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
|
||||||
openapi.Parameter(name='name', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Search by name or username'),
|
openapi.Parameter(name='name', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='Search by name or username'),
|
||||||
|
openapi.Parameter(name='exclude_self', type=openapi.TYPE_BOOLEAN, in_=openapi.IN_QUERY, description='Exclude current user from results'),
|
||||||
],
|
],
|
||||||
tags=['Users'],
|
tags=['Users'],
|
||||||
operation_summary='List users',
|
operation_summary='List users',
|
||||||
@ -226,6 +227,11 @@ class UserList(APIView):
|
|||||||
if name:
|
if name:
|
||||||
users = users.filter(Q(name__icontains=name) | Q(username__icontains=name))
|
users = users.filter(Q(name__icontains=name) | Q(username__icontains=name))
|
||||||
|
|
||||||
|
# Exclude current user if requested
|
||||||
|
exclude_self = request.GET.get("exclude_self", "") == "True"
|
||||||
|
if exclude_self and request.user.is_authenticated:
|
||||||
|
users = users.exclude(id=request.user.id)
|
||||||
|
|
||||||
if settings.USERS_NEEDS_TO_BE_APPROVED:
|
if settings.USERS_NEEDS_TO_BE_APPROVED:
|
||||||
is_approved = request.GET.get("is_approved")
|
is_approved = request.GET.get("is_approved")
|
||||||
if is_approved == "true":
|
if is_approved == "true":
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user