mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Compare commits
16 Commits
1f4ed59127
...
930b80079e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
930b80079e | ||
|
|
48fe482897 | ||
|
|
28c1d4ee44 | ||
|
|
61925bbd6e | ||
|
|
2ea45bfd78 | ||
|
|
004584de03 | ||
|
|
9372398ab5 | ||
|
|
b3d9776985 | ||
|
|
0f6d965f54 | ||
|
|
17b8c60450 | ||
|
|
cd173fc38e | ||
|
|
c39e8e26dd | ||
|
|
a40232e43b | ||
|
|
870274e676 | ||
|
|
d876084e5c | ||
|
|
f48166b427 |
@ -1 +1 @@
|
||||
VERSION = "6.7.103"
|
||||
VERSION = "6.9.103"
|
||||
|
||||
@ -205,7 +205,11 @@ class SubtitleAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class VideoTrimRequestAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
list_display = ["media", "status", "add_date", "video_action", "media_trim_style", "timestamps"]
|
||||
list_filter = ["status", "video_action", "media_trim_style", "add_date"]
|
||||
search_fields = ["media__title"]
|
||||
readonly_fields = ("add_date",)
|
||||
ordering = ("-add_date",)
|
||||
|
||||
|
||||
class EncodingAdmin(admin.ModelAdmin):
|
||||
@ -224,7 +228,11 @@ class EncodingAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
class TranscriptionRequestAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
list_display = ["media", "add_date", "status", "translate_to_english"]
|
||||
list_filter = ["status", "translate_to_english", "add_date"]
|
||||
search_fields = ["media__title"]
|
||||
readonly_fields = ("add_date", "logs")
|
||||
ordering = ("-add_date",)
|
||||
|
||||
|
||||
class PageAdminForm(forms.ModelForm):
|
||||
|
||||
@ -80,5 +80,9 @@ class TranscriptionRequest(models.Model):
|
||||
translate_to_english = models.BooleanField(default=False)
|
||||
logs = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Caption Request"
|
||||
verbose_name_plural = "Caption Requests"
|
||||
|
||||
def __str__(self):
|
||||
return f"Transcription request for {self.media.title} - {self.status}"
|
||||
|
||||
@ -77,6 +77,10 @@ class VideoTrimRequest(models.Model):
|
||||
media_trim_style = models.CharField(max_length=20, choices=TRIM_STYLE_CHOICES, default="no_encoding")
|
||||
timestamps = models.JSONField(null=False, blank=False, help_text="Timestamps for trimming")
|
||||
|
||||
class Meta:
|
||||
verbose_name = "Trim Request"
|
||||
verbose_name_plural = "Trim Requests"
|
||||
|
||||
def __str__(self):
|
||||
return f"Trim request for {self.media.title} ({self.status})"
|
||||
|
||||
|
||||
@ -193,8 +193,11 @@ class MediaList(APIView):
|
||||
already_sorted = True
|
||||
|
||||
else:
|
||||
media = self._get_media_queryset(request)
|
||||
already_sorted = True
|
||||
if is_mediacms_editor(self.request.user):
|
||||
media = Media.objects.prefetch_related("user", "tags")
|
||||
else:
|
||||
media = self._get_media_queryset(request)
|
||||
already_sorted = True
|
||||
|
||||
if tag:
|
||||
media = media.filter(tags__title=tag)
|
||||
@ -1010,11 +1013,15 @@ class MediaSearch(APIView):
|
||||
return Response(ret, status=status.HTTP_200_OK)
|
||||
|
||||
if request.user.is_authenticated:
|
||||
basic_query = Q(listable=True) | Q(permissions__user=request.user)
|
||||
if is_mediacms_editor(self.request.user):
|
||||
media = Media.objects.prefetch_related("user", "tags")
|
||||
basic_query = Q()
|
||||
else:
|
||||
basic_query = Q(listable=True) | Q(permissions__user=request.user) | Q(user=request.user)
|
||||
|
||||
if getattr(settings, 'USE_RBAC', False):
|
||||
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||
basic_query |= Q(category__in=rbac_categories)
|
||||
if getattr(settings, 'USE_RBAC', False):
|
||||
rbac_categories = request.user.get_rbac_categories_as_member()
|
||||
basic_query |= Q(category__in=rbac_categories)
|
||||
|
||||
else:
|
||||
basic_query = Q(listable=True)
|
||||
|
||||
@ -170,181 +170,183 @@
|
||||
}
|
||||
}
|
||||
|
||||
.available-categories {
|
||||
margin-top: 16px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #333;
|
||||
border-color: #555;
|
||||
.category-modal {
|
||||
.available-categories {
|
||||
margin-top: 16px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&.scrollable {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
.category-list {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
.dark_theme & {
|
||||
background-color: #333;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
&.scrollable {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.dark_theme & {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #aaa;
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background: #555;
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
|
||||
.dark_theme & {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
background: #aaa;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background: #555;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
.category-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #2a2a2a;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
.dark_theme & {
|
||||
background-color: #2a2a2a;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f7ff;
|
||||
border-color: var(--default-theme-color, #009933);
|
||||
&:hover {
|
||||
background-color: #f0f7ff;
|
||||
border-color: var(--default-theme-color, #009933);
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #3a3a3a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.marked-for-removal {
|
||||
background-color: #ffe0e0;
|
||||
border-color: #ffaaaa;
|
||||
opacity: 0.7;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #3a3a3a;
|
||||
background-color: #4a2a2a;
|
||||
border-color: #aa5555;
|
||||
}
|
||||
|
||||
span {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn,
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
color: var(--default-theme-color, #009933);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 153, 51, 0.1);
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #66bb66;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 187, 102, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.marked-for-removal {
|
||||
background-color: #ffe0e0;
|
||||
border-color: #ffaaaa;
|
||||
opacity: 0.7;
|
||||
.remove-btn {
|
||||
color: #dc3545;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
color: #c82333;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #4a2a2a;
|
||||
border-color: #aa5555;
|
||||
}
|
||||
color: #ff6b6b;
|
||||
|
||||
span {
|
||||
text-decoration: line-through;
|
||||
&:hover {
|
||||
background-color: rgba(255, 107, 107, 0.2);
|
||||
color: #ff8787;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.empty-message,
|
||||
.loading-message {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
|
||||
.add-btn,
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
color: var(--default-theme-color, #009933);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 153, 51, 0.1);
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #66bb66;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 187, 102, 0.2);
|
||||
.dark_theme & {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
color: #dc3545;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
color: #c82333;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #ff6b6b;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 107, 107, 0.2);
|
||||
color: #ff8787;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-message,
|
||||
.loading-message {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
|
||||
.dark_theme & {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.category-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@ -144,16 +144,17 @@
|
||||
}
|
||||
|
||||
.search-results {
|
||||
position: fixed;
|
||||
left: auto;
|
||||
right: auto;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
margin-top: 4px;
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
z-index: 10001;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
|
||||
.dark_theme & {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import './BulkActionChangeOwnerModal.scss';
|
||||
import { translateString } from '../utils/helpers/';
|
||||
|
||||
@ -29,8 +29,6 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
const [selectedUser, setSelectedUser] = useState<User | null>(null);
|
||||
const [isProcessing, setIsProcessing] = useState(false);
|
||||
const [searchTimeout, setSearchTimeout] = useState<NodeJS.Timeout | null>(null);
|
||||
const [dropdownPosition, setDropdownPosition] = useState({ top: 0, left: 0, width: 0 });
|
||||
const searchBoxRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
@ -41,17 +39,6 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
const updateDropdownPosition = () => {
|
||||
if (searchBoxRef.current) {
|
||||
const rect = searchBoxRef.current.getBoundingClientRect();
|
||||
setDropdownPosition({
|
||||
top: rect.bottom,
|
||||
left: rect.left,
|
||||
width: rect.width,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const searchUsers = async (name: string) => {
|
||||
if (!name.trim()) {
|
||||
setSearchResults([]);
|
||||
@ -66,7 +53,6 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
|
||||
const data = await response.json();
|
||||
setSearchResults(data.results || data);
|
||||
updateDropdownPosition();
|
||||
} catch (error) {
|
||||
console.error('Error searching users:', error);
|
||||
}
|
||||
@ -144,7 +130,7 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
</div>
|
||||
|
||||
<div className="change-owner-modal-content">
|
||||
<div className="search-box-wrapper" ref={searchBoxRef}>
|
||||
<div className="search-box-wrapper">
|
||||
<div className="search-box">
|
||||
<input
|
||||
type="text"
|
||||
@ -153,6 +139,19 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
onChange={(e) => handleSearchChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
{searchResults.length > 0 && (
|
||||
<div className="search-results">
|
||||
{searchResults.slice(0, 10).map((user) => (
|
||||
<div
|
||||
key={user.username}
|
||||
className="search-result-item"
|
||||
onClick={() => handleUserSelect(user)}
|
||||
>
|
||||
{user.name} - {user.username}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{selectedUser && (
|
||||
@ -175,27 +174,6 @@ export const BulkActionChangeOwnerModal: React.FC<BulkActionChangeOwnerModalProp
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{searchResults.length > 0 && (
|
||||
<div
|
||||
className="search-results"
|
||||
style={{
|
||||
top: `${dropdownPosition.top}px`,
|
||||
left: `${dropdownPosition.left}px`,
|
||||
width: `${dropdownPosition.width}px`,
|
||||
}}
|
||||
>
|
||||
{searchResults.slice(0, 10).map((user) => (
|
||||
<div
|
||||
key={user.username}
|
||||
className="search-result-item"
|
||||
onClick={() => handleUserSelect(user)}
|
||||
>
|
||||
{user.name} - {user.username}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -170,181 +170,183 @@
|
||||
}
|
||||
}
|
||||
|
||||
.available-tags {
|
||||
margin-top: 16px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tag-list {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #333;
|
||||
border-color: #555;
|
||||
.tag-modal {
|
||||
.available-tags {
|
||||
margin-top: 16px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
&.scrollable {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
.tag-list {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 100px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
.dark_theme & {
|
||||
background-color: #333;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
&.scrollable {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
.dark_theme & {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #aaa;
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background: #555;
|
||||
&::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
border-radius: 4px;
|
||||
|
||||
.dark_theme & {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
background: #aaa;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background: #555;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tag-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
.tag-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #2a2a2a;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
.dark_theme & {
|
||||
background-color: #2a2a2a;
|
||||
border-color: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
&.clickable {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #f0f7ff;
|
||||
border-color: var(--default-theme-color, #009933);
|
||||
&:hover {
|
||||
background-color: #f0f7ff;
|
||||
border-color: var(--default-theme-color, #009933);
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #3a3a3a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.marked-for-removal {
|
||||
background-color: #ffe0e0;
|
||||
border-color: #ffaaaa;
|
||||
opacity: 0.7;
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #3a3a3a;
|
||||
background-color: #4a2a2a;
|
||||
border-color: #aa5555;
|
||||
}
|
||||
|
||||
span {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn,
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
color: var(--default-theme-color, #009933);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 153, 51, 0.1);
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #66bb66;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 187, 102, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.marked-for-removal {
|
||||
background-color: #ffe0e0;
|
||||
border-color: #ffaaaa;
|
||||
opacity: 0.7;
|
||||
.remove-btn {
|
||||
color: #dc3545;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
color: #c82333;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background-color: #4a2a2a;
|
||||
border-color: #aa5555;
|
||||
}
|
||||
color: #ff6b6b;
|
||||
|
||||
span {
|
||||
text-decoration: line-through;
|
||||
&:hover {
|
||||
background-color: rgba(255, 107, 107, 0.2);
|
||||
color: #ff8787;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
.empty-message,
|
||||
.loading-message {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
|
||||
.add-btn,
|
||||
.remove-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
transition: all 0.2s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
color: var(--default-theme-color, #009933);
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 153, 51, 0.1);
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #66bb66;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(102, 187, 102, 0.2);
|
||||
.dark_theme & {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
color: #dc3545;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
color: #c82333;
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
color: #ff6b6b;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 107, 107, 0.2);
|
||||
color: #ff8787;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.empty-message,
|
||||
.loading-message {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
|
||||
.dark_theme & {
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.tag-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
|
||||
@ -7,22 +7,22 @@
|
||||
.bulk-actions-select {
|
||||
width: auto;
|
||||
max-width: 220px;
|
||||
height: 44px;
|
||||
padding: 0 32px 0 10px;
|
||||
font-size: 14px;
|
||||
height: 36px;
|
||||
padding: 0 28px 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
background-color: #f0f0f0;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23333' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 8px center;
|
||||
background-size: 16px;
|
||||
background-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
|
||||
|
||||
&:hover {
|
||||
background-color: #e8e8e8;
|
||||
|
||||
@ -11,17 +11,8 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0;
|
||||
padding: 16px 16px 0;
|
||||
gap: 20px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
@media (min-width: 710px) {
|
||||
padding: 20px 24px 0;
|
||||
}
|
||||
|
||||
@media (min-width: 476px) {
|
||||
padding: 16px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -34,18 +34,18 @@ export const MediaListWrapper: React.FC<MediaListWrapperProps> = ({
|
||||
onDeselectAll = () => {},
|
||||
}) => (
|
||||
<div className={(className ? className + ' ' : '') + 'media-list-wrapper'} style={style}>
|
||||
{showBulkActions && (
|
||||
<div className="bulk-actions-container">
|
||||
<BulkActionsDropdown selectedCount={selectedCount} onActionSelect={onBulkAction} />
|
||||
<SelectAllCheckbox
|
||||
totalCount={totalCount}
|
||||
selectedCount={selectedCount}
|
||||
onSelectAll={onSelectAll}
|
||||
onDeselectAll={onDeselectAll}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<MediaListRow title={title} viewAllLink={viewAllLink} viewAllText={viewAllText}>
|
||||
{showBulkActions && (
|
||||
<div className="bulk-actions-container">
|
||||
<BulkActionsDropdown selectedCount={selectedCount} onActionSelect={onBulkAction} />
|
||||
<SelectAllCheckbox
|
||||
totalCount={totalCount}
|
||||
selectedCount={selectedCount}
|
||||
onSelectAll={onSelectAll}
|
||||
onDeselectAll={onDeselectAll}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children || null}
|
||||
</MediaListRow>
|
||||
</div>
|
||||
|
||||
@ -43,7 +43,7 @@ export const SelectAllCheckbox: React.FC<SelectAllCheckboxProps> = ({
|
||||
disabled={isDisabled}
|
||||
aria-label={translateString('Select all media')}
|
||||
/>
|
||||
<span className="checkbox-label-text">{translateString('Select all')}</span>
|
||||
<span className="checkbox-label-text">{translateString('All')}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -78,6 +78,7 @@ export function listItemProps(props, item, index) {
|
||||
const url = {
|
||||
view: itemPageLink(props, item),
|
||||
edit: props.canEdit ? item.url.replace('view?m=', 'edit?m=') : null,
|
||||
publish: props.canEdit ? item.url.replace('view?m=', 'publish?m=') : null,
|
||||
};
|
||||
|
||||
if (window.MediaCMS.site.devEnv && -1 < url.view.indexOf('view?')) {
|
||||
@ -321,6 +322,7 @@ export function ListItem(props) {
|
||||
|
||||
if (props.canEdit) {
|
||||
args.editLink = props.url.edit;
|
||||
args.publishLink = props.url.publish;
|
||||
}
|
||||
|
||||
if (props.taxonomyPage.current) {
|
||||
|
||||
@ -28,14 +28,14 @@ export function MediaItem(props) {
|
||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
||||
|
||||
const handleItemClick = (e) => {
|
||||
// Only handle clicks when selection mode is active
|
||||
if (props.showSelection) {
|
||||
// Only handle clicks when selection mode is active AND at least one item is selected
|
||||
if (props.showSelection && props.hasAnySelection) {
|
||||
// Check if click was on the checkbox (already handled)
|
||||
if (e.target.type === 'checkbox' || e.target.closest('.item-selection-checkbox')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if click was on the edit icon or view icon
|
||||
// Check if click was on the edit icon or publish icon
|
||||
if (e.target.closest('.item-edit-icon') || e.target.closest('.item-view-icon')) {
|
||||
return;
|
||||
}
|
||||
@ -55,12 +55,11 @@ export function MediaItem(props) {
|
||||
<div className={finalClassname} onClick={handleItemClick}>
|
||||
<div className="item-content">
|
||||
{props.showSelection && (
|
||||
<div className="item-selection-checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="item-selection-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={props.isSelected || false}
|
||||
onChange={(e) => { props.onCheckboxChange && props.onCheckboxChange(e); }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
aria-label="Select media"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -82,8 +82,8 @@ export function MediaItemEditLink(props) {
|
||||
|
||||
export function MediaItemViewLink(props) {
|
||||
return !props.link ? null : (
|
||||
<a href={props.link} title={translateString("View media")} className="item-view-icon">
|
||||
<i className="material-icons">visibility</i>
|
||||
<a href={props.link} title={translateString("Publish media")} className="item-view-icon">
|
||||
<i className="material-icons">publish</i>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
width: 10%;
|
||||
width: 20%;
|
||||
|
||||
&:nth-child(3n + 1),
|
||||
&:nth-child(3n + 2),
|
||||
@ -98,6 +98,12 @@
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.mi-filter-full-width {
|
||||
width: 100% !important;
|
||||
padding-left: 0 !important;
|
||||
padding-right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.mi-filter-title {
|
||||
@ -150,9 +156,48 @@
|
||||
|
||||
&.active button,
|
||||
button:hover {
|
||||
color: inherit;
|
||||
color: var(--default-theme-color);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.active button {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.mi-filter-options-horizontal {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
> * {
|
||||
display: inline-block;
|
||||
margin-top: 0;
|
||||
margin-right: 8px;
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 6px 12px;
|
||||
border-radius: 4px;
|
||||
background-color: var(--sidebar-nav-border-color);
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--default-theme-color);
|
||||
color: white;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
&.active button {
|
||||
background-color: var(--default-theme-color);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@
|
||||
li {
|
||||
a {
|
||||
color: var(--profile-page-nav-link-text-color);
|
||||
text-transform: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--profile-page-nav-link-hover-text-color);
|
||||
@ -196,14 +197,14 @@
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
text-decoration: none;
|
||||
color: #666;
|
||||
color: #fff;
|
||||
border: 0;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
background-color: rgba(40, 167, 69, 0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@ -221,8 +222,8 @@
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #333;
|
||||
background-color: rgba(40, 167, 69, 1);
|
||||
color: #fff;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
@ -232,11 +233,11 @@
|
||||
}
|
||||
|
||||
.dark_theme & {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: #aaa;
|
||||
background-color: rgba(40, 167, 69, 0.9);
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
background-color: rgba(40, 167, 69, 1);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
@ -529,7 +530,7 @@
|
||||
width: auto;
|
||||
padding: 0 16px;
|
||||
text-decoration: none;
|
||||
text-transform: uppercase;
|
||||
text-transform: none !important;
|
||||
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
@ -372,19 +372,43 @@ class NavMenuInlineTabs extends React.PureComponent {
|
||||
</li>
|
||||
{this.props.onToggleFiltersClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
||||
<li className="media-filters-toggle">
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={this.props.onToggleFiltersClick} title={translateString('Filters')}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.props.onToggleFiltersClick} title={translateString('Filters')}>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">filter_list</i>
|
||||
</CircleIconButton>
|
||||
{this.props.hasActiveFilters ? (
|
||||
<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>
|
||||
</li>
|
||||
) : null}
|
||||
{this.props.onToggleTagsClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
|
||||
<li className="media-tags-toggle">
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer' }} onClick={this.props.onToggleTagsClick} title={translateString('Tags')}>
|
||||
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.props.onToggleTagsClick} title={translateString('Tags')}>
|
||||
<CircleIconButton buttonShadow={false}>
|
||||
<i className="material-icons">local_offer</i>
|
||||
</CircleIconButton>
|
||||
{this.props.hasActiveTags ? (
|
||||
<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>
|
||||
</li>
|
||||
) : null}
|
||||
@ -412,6 +436,8 @@ NavMenuInlineTabs.propTypes = {
|
||||
onToggleFiltersClick: PropTypes.func,
|
||||
onToggleTagsClick: PropTypes.func,
|
||||
onToggleSortingClick: PropTypes.func,
|
||||
hasActiveFilters: PropTypes.bool,
|
||||
hasActiveTags: PropTypes.bool,
|
||||
};
|
||||
|
||||
function AddBannerButton(props) {
|
||||
@ -574,7 +600,7 @@ export default function ProfilePagesHeader(props) {
|
||||
></span>
|
||||
) : null}
|
||||
|
||||
{userCanDeleteProfile ? (
|
||||
{userCanDeleteProfile && !userIsAuthor ? (
|
||||
<span className="delete-profile-wrap">
|
||||
<PopupTrigger contentRef={popupContentRef}>
|
||||
<button className="delete-profile" title="Remove profile">
|
||||
@ -602,7 +628,7 @@ export default function ProfilePagesHeader(props) {
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
{userCanEditProfile ? (
|
||||
{userCanEditProfile && userIsAuthor ? (
|
||||
props.author.banner_thumbnail_url ? (
|
||||
<EditBannerButton link={ProfilePageStore.get('author-data').default_channel_edit_url} />
|
||||
) : (
|
||||
@ -620,7 +646,7 @@ export default function ProfilePagesHeader(props) {
|
||||
{props.author.name ? (
|
||||
<div className="profile-name-edit-wrapper">
|
||||
<h1>{props.author.name}</h1>
|
||||
{userCanEditProfile ? <EditProfileButton link={ProfilePageStore.get('author-data').edit_url} /> : null}
|
||||
{userCanEditProfile && !userIsAuthor ? <EditProfileButton link={ProfilePageStore.get('author-data').edit_url} /> : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@ -635,6 +661,8 @@ export default function ProfilePagesHeader(props) {
|
||||
onToggleFiltersClick={props.onToggleFiltersClick}
|
||||
onToggleTagsClick={props.onToggleTagsClick}
|
||||
onToggleSortingClick={props.onToggleSortingClick}
|
||||
hasActiveFilters={props.hasActiveFilters}
|
||||
hasActiveTags={props.hasActiveTags}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -648,6 +676,8 @@ ProfilePagesHeader.propTypes = {
|
||||
onToggleFiltersClick: PropTypes.func,
|
||||
onToggleTagsClick: PropTypes.func,
|
||||
onToggleSortingClick: PropTypes.func,
|
||||
hasActiveFilters: PropTypes.bool,
|
||||
hasActiveTags: PropTypes.bool,
|
||||
};
|
||||
|
||||
ProfilePagesHeader.defaultProps = {
|
||||
|
||||
@ -72,8 +72,8 @@ export function ProfileMediaFilters(props) {
|
||||
upload_date: uploadDateFilter,
|
||||
duration: durationFilter,
|
||||
publish_state: publishStateFilter,
|
||||
sort_by: sortByFilter,
|
||||
tag: tagFilter,
|
||||
sort_by: props.selectedSort || sortByFilter,
|
||||
tag: props.selectedTag || tagFilter,
|
||||
};
|
||||
|
||||
switch (ev.currentTarget.getAttribute('filter')) {
|
||||
@ -180,6 +180,8 @@ ProfileMediaFilters.propTypes = {
|
||||
hidden: PropTypes.bool,
|
||||
tags: PropTypes.array,
|
||||
onFiltersUpdate: PropTypes.func.isRequired,
|
||||
selectedTag: PropTypes.string,
|
||||
selectedSort: PropTypes.string,
|
||||
};
|
||||
|
||||
ProfileMediaFilters.defaultProps = {
|
||||
|
||||
@ -43,9 +43,9 @@ export function ProfileMediaTags(props) {
|
||||
return (
|
||||
<div ref={containerRef} className={'mi-filters-row' + (isHidden ? ' hidden' : '')}>
|
||||
<div ref={innerContainerRef} className="mi-filters-row-inner">
|
||||
<div className="mi-filter">
|
||||
<div className="mi-filter mi-filter-full-width">
|
||||
<div className="mi-filter-title">{translateString('TAGS')}</div>
|
||||
<div className="mi-filter-options" style={tagsOptions.length >= 10 ? { maxHeight: '300px', overflowY: 'auto' } : {}}>
|
||||
<div className="mi-filter-options mi-filter-options-horizontal">
|
||||
<FilterOptions id={'tag'} options={tagsOptions} selected={tagFilter} onSelect={onFilterSelect} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -583,17 +583,23 @@ export class ProfileMediaPage extends Page {
|
||||
onToggleFiltersClick() {
|
||||
this.setState({
|
||||
hiddenFilters: !this.state.hiddenFilters,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleTagsClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: !this.state.hiddenTags,
|
||||
hiddenSorting: true,
|
||||
});
|
||||
}
|
||||
|
||||
onToggleSortingClick() {
|
||||
this.setState({
|
||||
hiddenFilters: true,
|
||||
hiddenTags: true,
|
||||
hiddenSorting: !this.state.hiddenSorting,
|
||||
});
|
||||
}
|
||||
@ -869,6 +875,23 @@ export class ProfileMediaPage extends Page {
|
||||
|
||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||
|
||||
// Check if any filters are active (excluding default sort and tags)
|
||||
const hasActiveFilters = this.state.filterArgs && (
|
||||
this.state.filterArgs.includes('media_type=') ||
|
||||
this.state.filterArgs.includes('upload_date=') ||
|
||||
this.state.filterArgs.includes('duration=') ||
|
||||
this.state.filterArgs.includes('publish_state=')
|
||||
);
|
||||
|
||||
const hasActiveTags = this.state.selectedTag && this.state.selectedTag !== 'all';
|
||||
|
||||
console.log('Filter Debug:', {
|
||||
filterArgs: this.state.filterArgs,
|
||||
selectedTag: this.state.selectedTag,
|
||||
hasActiveFilters,
|
||||
hasActiveTags
|
||||
});
|
||||
|
||||
return [
|
||||
this.state.author ? (
|
||||
<ProfilePagesHeader
|
||||
@ -879,20 +902,14 @@ export class ProfileMediaPage extends Page {
|
||||
onToggleFiltersClick={this.onToggleFiltersClick}
|
||||
onToggleTagsClick={this.onToggleTagsClick}
|
||||
onToggleSortingClick={this.onToggleSortingClick}
|
||||
hasActiveFilters={hasActiveFilters}
|
||||
hasActiveTags={hasActiveTags}
|
||||
/>
|
||||
) : null,
|
||||
this.state.author ? (
|
||||
<ProfilePagesContent key="ProfilePagesContent">
|
||||
<MediaListWrapper
|
||||
title={
|
||||
!isMediaAuthor
|
||||
? this.state.title
|
||||
: 0 < this.state.channelMediaCount
|
||||
? this.state.title === 'Uploads'
|
||||
? null
|
||||
: this.state.title
|
||||
: null
|
||||
}
|
||||
title={this.state.title}
|
||||
className="items-list-ver"
|
||||
showBulkActions={isMediaAuthor}
|
||||
selectedCount={this.state.selectedMedia.size}
|
||||
@ -901,7 +918,7 @@ export class ProfileMediaPage extends Page {
|
||||
onSelectAll={this.handleSelectAll}
|
||||
onDeselectAll={this.handleDeselectAll}
|
||||
>
|
||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} />
|
||||
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} selectedTag={this.state.selectedTag} selectedSort={this.state.selectedSort} />
|
||||
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} />
|
||||
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
|
||||
<LazyLoadItemListAsync
|
||||
|
||||
@ -35,7 +35,7 @@ export function useMediaItem(props) {
|
||||
}
|
||||
|
||||
function viewMediaComponent() {
|
||||
return props.showSelection ? <MediaItemViewLink link={props.link} /> : null;
|
||||
return props.showSelection ? <MediaItemViewLink link={props.publishLink || props.link} /> : null;
|
||||
}
|
||||
|
||||
function authorComponent() {
|
||||
|
||||
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
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
File diff suppressed because one or more lines are too long
@ -12,6 +12,146 @@
|
||||
{{ form.as_p }}
|
||||
<button class="primaryAction" type="submit">Update Profile</button>
|
||||
</form>
|
||||
|
||||
{% if is_author %}
|
||||
<div class="danger-zone">
|
||||
<h2>Danger Zone</h2>
|
||||
<div class="danger-zone-content">
|
||||
<div class="danger-zone-info">
|
||||
<h3>Delete Account</h3>
|
||||
<p>This will permanently remove all data, including media and playlists.</p>
|
||||
</div>
|
||||
<button class="btn-danger" id="delete-account-btn" type="button">Delete Account</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.danger-zone {
|
||||
margin-top: 60px;
|
||||
padding-top: 40px;
|
||||
border-top: 2px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.danger-zone h2 {
|
||||
color: #d32f2f;
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.danger-zone-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20px;
|
||||
border: 2px solid #ffcdd2;
|
||||
border-radius: 8px;
|
||||
background-color: #ffebee;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.dark_theme .danger-zone-content {
|
||||
background-color: #3a1f1f;
|
||||
border-color: #5a2d2d;
|
||||
}
|
||||
|
||||
.danger-zone-info h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 8px 0;
|
||||
color: #d32f2f;
|
||||
}
|
||||
|
||||
.danger-zone-info p {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.dark_theme .danger-zone-info p {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
padding: 10px 24px;
|
||||
background-color: #d32f2f;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #b71c1c;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-danger:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.danger-zone-content {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.getElementById('delete-account-btn').addEventListener('click', function() {
|
||||
const username = '{{ user.username }}';
|
||||
const confirmText = 'Are you sure you want to delete your account?\n\n' +
|
||||
'This action CANNOT be undone. This will permanently delete:\n' +
|
||||
'• Your profile and all personal information\n' +
|
||||
'• All your uploaded media\n' +
|
||||
'• All your playlists\n' +
|
||||
'• All your comments and interactions\n\n' +
|
||||
'Type "DELETE" to confirm:';
|
||||
|
||||
const userInput = prompt(confirmText);
|
||||
|
||||
if (userInput === 'DELETE') {
|
||||
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
|
||||
|
||||
fetch('/api/v1/users/' + username, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-CSRFToken': csrfToken,
|
||||
},
|
||||
})
|
||||
.then(function(response) {
|
||||
if (response.ok) {
|
||||
alert('Your account has been deleted successfully. You will be redirected to the home page.');
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
return response.json().then(function(data) {
|
||||
throw new Error(data.detail || 'Failed to delete account.');
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
alert('Error deleting account: ' + error.message);
|
||||
console.error('Error deleting account:', error);
|
||||
});
|
||||
} else if (userInput !== null) {
|
||||
alert('Account deletion cancelled. You must type "DELETE" exactly to confirm.');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock innercontent %}
|
||||
|
||||
@ -102,6 +102,7 @@ def view_user_about(request, username):
|
||||
|
||||
@login_required
|
||||
def edit_user(request, username):
|
||||
context = {}
|
||||
user = get_user(username=username)
|
||||
if not user or (user != request.user and not is_mediacms_manager(request.user)):
|
||||
return HttpResponseRedirect("/")
|
||||
@ -114,7 +115,13 @@ def edit_user(request, username):
|
||||
return HttpResponseRedirect(user.get_absolute_url())
|
||||
else:
|
||||
form = UserForm(request.user, instance=user)
|
||||
return render(request, "cms/user_edit.html", {"form": form})
|
||||
context["form"] = form
|
||||
context["user"] = user
|
||||
if user == request.user:
|
||||
context["is_author"] = True
|
||||
else:
|
||||
context["is_author"] = False
|
||||
return render(request, "cms/user_edit.html", context)
|
||||
|
||||
|
||||
def view_channel(request, friendly_token):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user