Merge branch 'main' into feat-lti-integration-select

This commit is contained in:
Markos Gogoulos
2026-01-31 15:33:27 +02:00
41 changed files with 5081 additions and 4831 deletions

View File

@@ -1 +1 @@
VERSION = "7.8124" VERSION = "8"

View File

@@ -1,6 +1,6 @@
@use "sass:math"; @use "sass:math";
@import '../../../css/includes/_variables.scss'; @import "../../../css/includes/_variables.scss";
@import '../../../css/includes/_variables_dimensions.scss'; @import "../../../css/includes/_variables_dimensions.scss";
.visible-sidebar .page-main-wrap { .visible-sidebar .page-main-wrap {
padding-left: 0; padding-left: 0;
@@ -119,7 +119,7 @@
background-color: var(--media-actions-share-copy-field-bg-color); background-color: var(--media-actions-share-copy-field-bg-color);
} }
input[type='text'] { input[type="text"] {
color: var(--media-actions-share-copy-field-input-text-color); color: var(--media-actions-share-copy-field-input-text-color);
} }
} }
@@ -180,7 +180,7 @@
color: var(--report-form-field-label-text-color); color: var(--report-form-field-label-text-color);
} }
input[type='text'], input[type="text"],
textarea { textarea {
color: var(--report-form-field-input-text-color); color: var(--report-form-field-input-text-color);
border-color: var(--report-form-field-input-border-color); border-color: var(--report-form-field-input-border-color);
@@ -479,7 +479,7 @@
&.audio-player-container { &.audio-player-container {
&:before { &:before {
content: '\E3A1'; content: "\E3A1";
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
@@ -490,12 +490,11 @@
line-height: 1; line-height: 1;
padding: 0; padding: 0;
font-family: 'Material Icons'; font-family: "Material Icons";
text-decoration: none; text-decoration: none;
color: #888; color: #888;
} }
.vjs-big-play-button { .vjs-big-play-button {
} }
@@ -514,6 +513,13 @@
} }
} }
.embedded-app {
.viewer-container,
.viewer-info {
width: 100%;
}
}
.viewer-image-container { .viewer-image-container {
position: relative; position: relative;
display: block; display: block;
@@ -550,8 +556,6 @@
max-width: 90%; max-width: 90%;
} }
.slideshow-image img { .slideshow-image img {
display: block; display: block;
width: auto; width: auto;
@@ -560,7 +564,9 @@
max-height: 90vh; max-height: 90vh;
border-radius: 0; border-radius: 0;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
transition: transform 60s ease-in-out, opacity 60 ease-in-out; transition:
transform 60s ease-in-out,
opacity 60 ease-in-out;
} }
.slideshow-title { .slideshow-title {
@@ -572,7 +578,6 @@
z-index: 1200; z-index: 1200;
} }
.arrow { .arrow {
position: absolute; position: absolute;
display: flex; display: flex;
@@ -590,7 +595,9 @@
padding: 10px; padding: 10px;
border-radius: 50%; border-radius: 50%;
z-index: 1000; z-index: 1000;
transition: background-color 0.2s ease, transform 0.2s ease; transition:
background-color 0.2s ease,
transform 0.2s ease;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
} }
@@ -685,18 +692,19 @@
width: 100%; // Default width for mobile width: 100%; // Default width for mobile
height: 400px; // Default height for mobile height: 400px; // Default height for mobile
@media (min-width: 768px) and (max-width: 1023px) { // Tablets @media (min-width: 768px) and (max-width: 1023px) {
// Tablets
width: 90%; width: 90%;
height: 600px; height: 600px;
} }
@media (min-width: 1024px) { // Desktop @media (min-width: 1024px) {
// Desktop
width: 85%; width: 85%;
height: 900px; height: 900px;
} }
} }
.viewer-container .player-container.viewer-pdf-container, .viewer-container .player-container.viewer-pdf-container,
.viewer-container .player-container.viewer-attachment-container { .viewer-container .player-container.viewer-attachment-container {
background-color: var(--item-thumb-bg-color); background-color: var(--item-thumb-bg-color);
@@ -785,78 +793,6 @@
} }
} }
.edit-media-dropdown {
position: relative;
display: inline-block;
.popup-fullscreen-overlay {
display: none;
}
.popup-main {
position: absolute;
top: calc(100% + 8px);
right: 0;
background: #fff;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
min-width: 200px;
z-index: 1000;
.dark_theme & {
background: #2d2d2d;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
}
.edit-options {
.navigation-menu-list {
padding: 8px 0;
li {
list-style: none;
margin: 0;
a {
display: flex;
align-items: center;
padding: 12px 16px;
text-decoration: none;
color: #333;
transition: background-color 0.2s ease;
.dark_theme & {
color: #e0e0e0;
}
&:hover {
background-color: rgba(0, 153, 51, 0.1);
.dark_theme & {
background-color: rgba(102, 187, 102, 0.2);
}
}
.material-icons {
margin-right: 12px;
font-size: 20px;
color: rgba(0, 153, 51, 0.9);
.dark_theme & {
color: rgba(102, 187, 102, 0.9);
}
}
span {
font-size: 14px;
font-weight: 500;
}
}
}
}
}
}
.remove-media-icon { .remove-media-icon {
background-color: rgba(220, 53, 69, 0.9); background-color: rgba(220, 53, 69, 0.9);
@@ -1078,7 +1014,7 @@
&.like, &.like,
&.dislike { &.dislike {
&:before { &:before {
content: ''; content: "";
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: -4px; left: -4px;
@@ -1218,7 +1154,7 @@
border-radius: 2px; border-radius: 2px;
} }
input[type='text'] { input[type="text"] {
width: 100%; width: 100%;
height: 42px; height: 42px;
padding: 1px 0 1px 16px; padding: 1px 0 1px 16px;
@@ -1278,13 +1214,18 @@
width: 220px; width: 220px;
} }
box-shadow: 0 16px 24px 2px rgba(#000, 0.14), 0 6px 30px 5px rgba(#000, 0.12), box-shadow:
0 16px 24px 2px rgba(#000, 0.14),
0 6px 30px 5px rgba(#000, 0.12),
0 8px 10px -5px rgba(#000, 0.4); 0 8px 10px -5px rgba(#000, 0.4);
&.main-options, &.main-options,
&.video-download-options { &.video-download-options {
width: 240px; width: 240px;
box-shadow: 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12), 0 3px 1px -2px rgba(#000, 0.2); box-shadow:
0 2px 2px 0 rgba(#000, 0.14),
0 1px 5px 0 rgba(#000, 0.12),
0 3px 1px -2px rgba(#000, 0.2);
} }
} }
} }
@@ -1342,7 +1283,9 @@
padding: 24px; padding: 24px;
text-align: initial; text-align: initial;
box-shadow: rgba(#000, 0.14) 0px 16px 24px 2px, rgba(#000, 0.12) 0px 6px 30px 5px, box-shadow:
rgba(#000, 0.14) 0px 16px 24px 2px,
rgba(#000, 0.12) 0px 6px 30px 5px,
rgba(#000, 0.4) 0px 8px 10px; rgba(#000, 0.4) 0px 8px 10px;
} }
} }
@@ -1375,13 +1318,18 @@
width: 220px; width: 220px;
} }
box-shadow: 0 16px 24px 2px rgba(#000, 0.14), 0 6px 30px 5px rgba(#000, 0.12), box-shadow:
0 16px 24px 2px rgba(#000, 0.14),
0 6px 30px 5px rgba(#000, 0.12),
0 8px 10px -5px rgba(#000, 0.4); 0 8px 10px -5px rgba(#000, 0.4);
&.main-options, &.main-options,
&.video-download-options { &.video-download-options {
width: 240px; width: 240px;
box-shadow: 0 2px 2px 0 rgba(#000, 0.14), 0 1px 5px 0 rgba(#000, 0.12), 0 3px 1px -2px rgba(#000, 0.2); box-shadow:
0 2px 2px 0 rgba(#000, 0.14),
0 1px 5px 0 rgba(#000, 0.12),
0 3px 1px -2px rgba(#000, 0.2);
.popup-main { .popup-main {
min-height: 0; min-height: 0;
@@ -1484,7 +1432,7 @@
font-weight: 500; font-weight: 500;
} }
input[type='text'], input[type="text"],
textarea { textarea {
min-width: 100%; min-width: 100%;
width: 100%; width: 100%;
@@ -1507,7 +1455,7 @@
cursor: not-allowed; cursor: not-allowed;
} }
input[type='text'] { input[type="text"] {
font-size: 14px; font-size: 14px;
} }
@@ -1804,7 +1752,7 @@
border-width: 0 0 1px; border-width: 0 0 1px;
&:after { &:after {
content: ''; content: "";
position: absolute; position: absolute;
bottom: -5px; bottom: -5px;
right: 0; right: 0;
@@ -1860,7 +1808,7 @@
max-height: 100%; max-height: 100%;
padding: 16px; padding: 16px;
cursor: text; cursor: text;
font-family: 'Roboto Mono', monospace; font-family: "Roboto Mono", monospace;
font-size: 14px; font-size: 14px;
line-height: 1.714285714; line-height: 1.714285714;
outline: 0; outline: 0;
@@ -1894,7 +1842,7 @@
vertical-align: top; vertical-align: top;
input { input {
&[type='checkbox'] { &[type="checkbox"] {
margin-left: 0; margin-left: 0;
} }
} }
@@ -1906,7 +1854,7 @@
width: 100%; width: 100%;
input { input {
&[type='checkbox'] { &[type="checkbox"] {
margin-left: 0; margin-left: 0;
} }
} }
@@ -2051,8 +1999,8 @@
} }
.item-date:before { .item-date:before {
content: ''; content: "";
content: '\2022'; content: "\2022";
margin: 0 4px; margin: 0 4px;
} }
@@ -2089,14 +2037,14 @@
margin-right: 4px; margin-right: 4px;
&:after { &:after {
content: ','; content: ",";
} }
&:last-child { &:last-child {
margin-right: 0; margin-right: 0;
&:after { &:after {
content: ''; content: "";
} }
} }
} }

View File

@@ -3,7 +3,7 @@ import { SiteContext } from '../../utils/contexts/';
import { useUser, usePopup } from '../../utils/hooks/'; import { useUser, usePopup } from '../../utils/hooks/';
import { PageStore, MediaPageStore } from '../../utils/stores/'; import { PageStore, MediaPageStore } from '../../utils/stores/';
import { PageActions, MediaPageActions } from '../../utils/actions/'; import { PageActions, MediaPageActions } from '../../utils/actions/';
import { formatInnerLink, publishedOnDate } from '../../utils/helpers/'; import { formatInnerLink, inEmbeddedApp, publishedOnDate } from '../../utils/helpers/';
import { PopupMain } from '../_shared/'; import { PopupMain } from '../_shared/';
import CommentsList from '../comments/Comments'; import CommentsList from '../comments/Comments';
import { replaceString } from '../../utils/helpers/'; import { replaceString } from '../../utils/helpers/';
@@ -73,10 +73,14 @@ function MediaMetaField(props) {
} }
function EditMediaButton(props) { function EditMediaButton(props) {
const friendlyToken = window.MediaCMS.mediaId; let link = props.link;
if (window.MediaCMS.site.devEnv) {
link = '/edit-media.html';
}
return ( return (
<a href={`/edit?m=${friendlyToken}`} className="edit-media-icon" title={translateString('Edit metadata')}> <a href={link} rel="nofollow" title={translateString('Edit media')} className="edit-media-icon">
<i className="material-icons">edit</i> <i className="material-icons">edit</i>
</a> </a>
); );
@@ -121,7 +125,9 @@ export default function ViewerInfoContent(props) {
PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete'); PageActions.addNotification('Media removed. Redirecting...', 'mediaDelete');
setTimeout(function () { setTimeout(function () {
window.location.href = window.location.href =
SiteContext._currentValue.url + '/' + MediaPageStore.get('media-data').author_profile.replace(/^\//g, ''); SiteContext._currentValue.url +
'/' +
MediaPageStore.get('media-data').author_profile.replace(/^\//g, '');
}, 2000); }, 2000);
}, 100); }, 100);
@@ -181,7 +187,12 @@ export default function ViewerInfoContent(props) {
{void 0 === PageStore.get('config-media-item').displayAuthor || {void 0 === PageStore.get('config-media-item').displayAuthor ||
null === PageStore.get('config-media-item').displayAuthor || null === PageStore.get('config-media-item').displayAuthor ||
!!PageStore.get('config-media-item').displayAuthor ? ( !!PageStore.get('config-media-item').displayAuthor ? (
<MediaAuthorBanner link={authorLink} thumb={authorThumb} name={props.author.name} published={props.published} /> <MediaAuthorBanner
link={authorLink}
thumb={authorThumb}
name={props.author.name}
published={props.published}
/>
) : null} ) : null}
<div className="media-content-banner"> <div className="media-content-banner">
@@ -208,14 +219,20 @@ export default function ViewerInfoContent(props) {
{categoriesContent.length ? ( {categoriesContent.length ? (
<MediaMetaField <MediaMetaField
value={categoriesContent} value={categoriesContent}
title={1 < categoriesContent.length ? translateString('Categories') : translateString('Category')} title={
1 < categoriesContent.length
? translateString('Categories')
: translateString('Category')
}
id="categories" id="categories"
/> />
) : null} ) : null}
{userCan.editMedia ? ( {userCan.editMedia ? (
<div className="media-author-actions"> <div className="media-author-actions">
{userCan.editMedia ? <EditMediaButton /> : null} {userCan.editMedia ? (
<EditMediaButton link={MediaPageStore.get('media-data').edit_url} />
) : null}
{userCan.deleteMedia ? ( {userCan.deleteMedia ? (
<PopupTrigger contentRef={popupContentRef}> <PopupTrigger contentRef={popupContentRef}>
@@ -230,14 +247,22 @@ export default function ViewerInfoContent(props) {
<PopupMain> <PopupMain>
<div className="popup-message"> <div className="popup-message">
<span className="popup-message-title">Media removal</span> <span className="popup-message-title">Media removal</span>
<span className="popup-message-main">You're willing to remove media permanently?</span> <span className="popup-message-main">
You're willing to remove media permanently?
</span>
</div> </div>
<hr /> <hr />
<span className="popup-message-bottom"> <span className="popup-message-bottom">
<button className="button-link cancel-comment-removal" onClick={cancelMediaRemoval}> <button
className="button-link cancel-comment-removal"
onClick={cancelMediaRemoval}
>
CANCEL CANCEL
</button> </button>
<button className="button-link proceed-comment-removal" onClick={proceedMediaRemoval}> <button
className="button-link proceed-comment-removal"
onClick={proceedMediaRemoval}
>
PROCEED PROCEED
</button> </button>
</span> </span>
@@ -249,7 +274,7 @@ export default function ViewerInfoContent(props) {
</div> </div>
</div> </div>
<CommentsList /> {!inEmbeddedApp() && <CommentsList />}
</div> </div>
); );
} }

View File

@@ -1,8 +1,16 @@
import React from 'react'; import React from 'react';
import { formatViewsNumber } from '../../utils/helpers/'; import { formatViewsNumber, inEmbeddedApp } from '../../utils/helpers/';
import { PageStore, MediaPageStore } from '../../utils/stores/'; import { PageStore, MediaPageStore } from '../../utils/stores/';
import { MemberContext, PlaylistsContext } from '../../utils/contexts/'; import { MemberContext, PlaylistsContext } from '../../utils/contexts/';
import { MediaLikeIcon, MediaDislikeIcon, OtherMediaDownloadLink, VideoMediaDownloadLink, MediaSaveButton, MediaShareButton, MediaMoreOptionsIcon } from '../media-actions/'; import {
MediaLikeIcon,
MediaDislikeIcon,
OtherMediaDownloadLink,
VideoMediaDownloadLink,
MediaSaveButton,
MediaShareButton,
MediaMoreOptionsIcon,
} from '../media-actions/';
import ViewerInfoTitleBanner from './ViewerInfoTitleBanner'; import ViewerInfoTitleBanner from './ViewerInfoTitleBanner';
import { translateString } from '../../utils/helpers/'; import { translateString } from '../../utils/helpers/';
@@ -74,7 +82,8 @@ export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
{displayViews ? ( {displayViews ? (
<div className="media-views"> <div className="media-views">
{formatViewsNumber(this.props.views, true)} {1 >= this.props.views ? translateString('view') : translateString('views')} {formatViewsNumber(this.props.views, true)}{' '}
{1 >= this.props.views ? translateString('view') : translateString('views')}
</div> </div>
) : null} ) : null}
@@ -82,9 +91,12 @@ export default class ViewerInfoVideoTitleBanner extends ViewerInfoTitleBanner {
<div> <div>
{MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null} {MemberContext._currentValue.can.likeMedia ? <MediaLikeIcon /> : null}
{MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null} {MemberContext._currentValue.can.dislikeMedia ? <MediaDislikeIcon /> : null}
{MemberContext._currentValue.can.shareMedia ? <MediaShareButton isVideo={true} /> : null} {!inEmbeddedApp() && MemberContext._currentValue.can.shareMedia ? (
<MediaShareButton isVideo={true} />
) : null}
{!MemberContext._currentValue.is.anonymous && {!inEmbeddedApp() &&
!MemberContext._currentValue.is.anonymous &&
MemberContext._currentValue.can.saveMedia && MemberContext._currentValue.can.saveMedia &&
-1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? ( -1 < PlaylistsContext._currentValue.mediaTypes.indexOf(MediaPageStore.get('media-type')) ? (
<MediaSaveButton /> <MediaSaveButton />

View File

@@ -23,6 +23,11 @@
transition-property: padding-left; transition-property: padding-left;
transition-duration: 0.2s; transition-duration: 0.2s;
} }
.embedded-app & {
padding-top: 0;
padding-left: 0;
}
} }
#page-profile-media, #page-profile-media,

View File

@@ -162,12 +162,16 @@ class ProfileSearchBar extends React.PureComponent {
if (!this.state.visibleForm) { if (!this.state.visibleForm) {
return ( return (
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} onClick={this.showForm}> <span
style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }}
onClick={this.showForm}
>
<CircleIconButton buttonShadow={false}> <CircleIconButton buttonShadow={false}>
<i className="material-icons">search</i> <i className="material-icons">search</i>
</CircleIconButton> </CircleIconButton>
{hasSearchText ? ( {hasSearchText ? (
<span style={{ <span
style={{
position: 'absolute', position: 'absolute',
top: '8px', top: '8px',
right: '8px', right: '8px',
@@ -176,7 +180,8 @@ class ProfileSearchBar extends React.PureComponent {
borderRadius: '50%', borderRadius: '50%',
backgroundColor: 'var(--default-theme-color)', backgroundColor: 'var(--default-theme-color)',
border: '2px solid white', border: '2px solid white',
}}></span> }}
></span>
) : null} ) : null}
</span> </span>
); );
@@ -189,7 +194,8 @@ class ProfileSearchBar extends React.PureComponent {
<i className="material-icons">search</i> <i className="material-icons">search</i>
</CircleIconButton> </CircleIconButton>
{hasSearchText ? ( {hasSearchText ? (
<span style={{ <span
style={{
position: 'absolute', position: 'absolute',
top: '8px', top: '8px',
right: '8px', right: '8px',
@@ -198,7 +204,8 @@ class ProfileSearchBar extends React.PureComponent {
borderRadius: '50%', borderRadius: '50%',
backgroundColor: 'var(--default-theme-color)', backgroundColor: 'var(--default-theme-color)',
border: '2px solid white', border: '2px solid white',
}}></span> }}
></span>
) : null} ) : null}
</span> </span>
<span> <span>
@@ -427,17 +434,32 @@ class NavMenuInlineTabs extends React.PureComponent {
{!['about', 'playlists'].includes(this.props.type) ? ( {!['about', 'playlists'].includes(this.props.type) ? (
<li className="media-search"> <li className="media-search">
<ProfileSearchBar onQueryChange={this.props.onQueryChange} toggleSearchField={this.onToggleSearchField} type={this.props.type} /> <ProfileSearchBar
onQueryChange={this.props.onQueryChange}
toggleSearchField={this.onToggleSearchField}
type={this.props.type}
/>
</li> </li>
) : null} ) : null}
{this.props.onToggleFiltersClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? ( {this.props.onToggleFiltersClick &&
['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
<li className="media-filters-toggle"> <li className="media-filters-toggle">
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} 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}> <CircleIconButton buttonShadow={false}>
<i className="material-icons">filter_list</i> <i className="material-icons">filter_list</i>
</CircleIconButton> </CircleIconButton>
{this.props.hasActiveFilters ? ( {this.props.hasActiveFilters ? (
<span style={{ <span
style={{
position: 'absolute', position: 'absolute',
top: '8px', top: '8px',
right: '8px', right: '8px',
@@ -446,19 +468,31 @@ class NavMenuInlineTabs extends React.PureComponent {
borderRadius: '50%', borderRadius: '50%',
backgroundColor: 'var(--default-theme-color)', backgroundColor: 'var(--default-theme-color)',
border: '2px solid white', border: '2px solid white',
}}></span> }}
></span>
) : null} ) : null}
</span> </span>
</li> </li>
) : null} ) : null}
{this.props.onToggleTagsClick && ['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? ( {this.props.onToggleTagsClick &&
['media', 'shared_by_me', 'shared_with_me'].includes(this.props.type) ? (
<li className="media-tags-toggle"> <li className="media-tags-toggle">
<span style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', position: 'relative' }} 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}> <CircleIconButton buttonShadow={false}>
<i className="material-icons">local_offer</i> <i className="material-icons">local_offer</i>
</CircleIconButton> </CircleIconButton>
{this.props.hasActiveTags ? ( {this.props.hasActiveTags ? (
<span style={{ <span
style={{
position: 'absolute', position: 'absolute',
top: '8px', top: '8px',
right: '8px', right: '8px',
@@ -467,19 +501,31 @@ class NavMenuInlineTabs extends React.PureComponent {
borderRadius: '50%', borderRadius: '50%',
backgroundColor: 'var(--default-theme-color)', backgroundColor: 'var(--default-theme-color)',
border: '2px solid white', border: '2px solid white',
}}></span> }}
></span>
) : null} ) : null}
</span> </span>
</li> </li>
) : 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', position: 'relative' }} 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 ? ( {this.props.hasActiveSort ? (
<span style={{ <span
style={{
position: 'absolute', position: 'absolute',
top: '8px', top: '8px',
right: '8px', right: '8px',
@@ -488,7 +534,8 @@ class NavMenuInlineTabs extends React.PureComponent {
borderRadius: '50%', borderRadius: '50%',
backgroundColor: 'var(--default-theme-color)', backgroundColor: 'var(--default-theme-color)',
border: '2px solid white', border: '2px solid white',
}}></span> }}
></span>
) : null} ) : null}
</span> </span>
</li> </li>
@@ -658,6 +705,7 @@ export default function ProfilePagesHeader(props) {
return ( return (
<div ref={profilePageHeaderRef} className={'profile-page-header' + (fixedNav ? ' fixed-nav' : '')}> <div ref={profilePageHeaderRef} className={'profile-page-header' + (fixedNav ? ' fixed-nav' : '')}>
{!props.hideChannelBanner && (
<span className="profile-banner-wrap"> <span className="profile-banner-wrap">
{props.author.banner_thumbnail_url ? ( {props.author.banner_thumbnail_url ? (
<span <span
@@ -685,14 +733,22 @@ export default function ProfilePagesHeader(props) {
<PopupMain> <PopupMain>
<div className="popup-message"> <div className="popup-message">
<span className="popup-message-title">Profile removal</span> <span className="popup-message-title">Profile removal</span>
<span className="popup-message-main">You're willing to remove profile permanently?</span> <span className="popup-message-main">
You're willing to remove profile permanently?
</span>
</div> </div>
<hr /> <hr />
<span className="popup-message-bottom"> <span className="popup-message-bottom">
<button className="button-link cancel-profile-removal" onClick={cancelProfileRemoval}> <button
className="button-link cancel-profile-removal"
onClick={cancelProfileRemoval}
>
CANCEL CANCEL
</button> </button>
<button className="button-link proceed-profile-removal" onClick={proceedMediaRemoval}> <button
className="button-link proceed-profile-removal"
onClick={proceedMediaRemoval}
>
PROCEED PROCEED
</button> </button>
</span> </span>
@@ -709,17 +765,22 @@ export default function ProfilePagesHeader(props) {
) )
) : null} ) : null}
</span> </span>
)}
<div className="profile-info-nav-wrap"> <div className="profile-info-nav-wrap">
{props.author.thumbnail_url || props.author.name ? ( {props.author.thumbnail_url || props.author.name ? (
<div className="profile-info"> <div className="profile-info">
<div className="profile-info-inner"> <div className="profile-info-inner">
<div>{props.author.thumbnail_url ? <img src={props.author.thumbnail_url} alt="" /> : null}</div> <div>
{props.author.thumbnail_url ? <img src={props.author.thumbnail_url} alt="" /> : null}
</div>
<div> <div>
{props.author.name ? ( {props.author.name ? (
<div className="profile-name-edit-wrapper"> <div className="profile-name-edit-wrapper">
<h1>{props.author.name}</h1> <h1>{props.author.name}</h1>
{userCanEditProfile && !userIsAuthor ? <EditProfileButton link={ProfilePageStore.get('author-data').edit_url} /> : null} {userCanEditProfile && !userIsAuthor ? (
<EditProfileButton link={ProfilePageStore.get('author-data').edit_url} />
) : null}
</div> </div>
) : null} ) : null}
</div> </div>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import UrlParse from 'url-parse'; import UrlParse from 'url-parse';
import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/'; import { ApiUrlContext, MemberContext, SiteContext } from '../utils/contexts/';
import { formatInnerLink, csrfToken, postRequest } from '../utils/helpers/'; import { formatInnerLink, csrfToken, postRequest, inEmbeddedApp } from '../utils/helpers/';
import { PageActions } from '../utils/actions/'; import { PageActions } from '../utils/actions/';
import { PageStore, ProfilePageStore } from '../utils/stores/'; import { PageStore, ProfilePageStore } from '../utils/stores/';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
@@ -268,7 +268,7 @@ export class ProfileAboutPage extends ProfileMediaPage {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="about" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}> <ProfilePagesContent key="ProfilePagesContent" enabledContactForm={this.enabledContactForm}>

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -28,7 +29,7 @@ export class ProfileHistoryPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="history" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

View File

@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -28,7 +29,7 @@ export class ProfileLikedPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="liked" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts'; import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts';
import { PageStore, ProfilePageStore } from '../utils/stores'; import { PageStore, ProfilePageStore } from '../utils/stores';
import { ProfilePageActions, PageActions } from '../utils/actions'; import { ProfilePageActions, PageActions } from '../utils/actions';
import { translateString } from '../utils/helpers/'; import { inEmbeddedApp, translateString } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -120,7 +120,13 @@ export class ProfileMediaPage extends Page {
if (author) { if (author) {
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + this.state.filterArgs; requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + this.state.filterArgs;
} }
@@ -171,7 +177,13 @@ export class ProfileMediaPage extends Page {
let requestUrl; let requestUrl;
if (newQuery) { if (newQuery) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&q=' +
encodeURIComponent(newQuery) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs; requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs;
} }
@@ -212,37 +224,55 @@ export class ProfileMediaPage extends Page {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to delete') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to delete') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'enable-comments') { } else if (action === 'enable-comments') {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to enable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to enable comments to') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'disable-comments') { } else if (action === 'disable-comments') {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to disable comments to') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to disable comments to') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'enable-download') { } else if (action === 'enable-download') {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to enable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to enable download for') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'disable-download') { } else if (action === 'disable-download') {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to disable download for') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to disable download for') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'copy-media') { } else if (action === 'copy-media') {
this.setState({ this.setState({
showConfirmModal: true, showConfirmModal: true,
pendingAction: action, pendingAction: action,
confirmMessage: translateString('You are going to copy') + ` ${selectedCount} ` + translateString('media, are you sure?'), confirmMessage:
translateString('You are going to copy') +
` ${selectedCount} ` +
translateString('media, are you sure?'),
}); });
} else if (action === 'add-remove-coviewers') { } else if (action === 'add-remove-coviewers') {
this.setState({ this.setState({
@@ -337,7 +367,8 @@ export class ProfileMediaPage extends Page {
return response.json(); return response.json();
}) })
.then((data) => { .then((data) => {
const message = selectedCount === 1 const message =
selectedCount === 1
? translateString('The media was deleted successfully.') ? translateString('The media was deleted successfully.')
: translateString('Successfully deleted') + ` ${selectedCount} ` + translateString('media.'); : translateString('Successfully deleted') + ` ${selectedCount} ` + translateString('media.');
this.showNotification(message); this.showNotification(message);
@@ -590,10 +621,18 @@ export class ProfileMediaPage extends Page {
this.setState({ selectedTag: tag }, () => { 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')
upload_date: this.state.filterArgs.includes('upload_date') ? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1] : null, ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1]
duration: this.state.filterArgs.includes('duration') ? this.state.filterArgs.match(/duration=([^&]*)/)?.[1] : null, : null,
publish_state: this.state.filterArgs.includes('publish_state') ? this.state.filterArgs.match(/publish_state=([^&]*)/)?.[1] : null, upload_date: this.state.filterArgs.includes('upload_date')
? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1]
: null,
duration: this.state.filterArgs.includes('duration')
? this.state.filterArgs.match(/duration=([^&]*)/)?.[1]
: null,
publish_state: this.state.filterArgs.includes('publish_state')
? this.state.filterArgs.match(/publish_state=([^&]*)/)?.[1]
: null,
sort_by: this.state.selectedSort, sort_by: this.state.selectedSort,
tag: tag, tag: tag,
}); });
@@ -604,10 +643,18 @@ export class ProfileMediaPage extends Page {
this.setState({ selectedSort: sortOption }, () => { 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')
upload_date: this.state.filterArgs.includes('upload_date') ? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1] : null, ? this.state.filterArgs.match(/media_type=([^&]*)/)?.[1]
duration: this.state.filterArgs.includes('duration') ? this.state.filterArgs.match(/duration=([^&]*)/)?.[1] : null, : null,
publish_state: this.state.filterArgs.includes('publish_state') ? this.state.filterArgs.match(/publish_state=([^&]*)/)?.[1] : null, upload_date: this.state.filterArgs.includes('upload_date')
? this.state.filterArgs.match(/upload_date=([^&]*)/)?.[1]
: null,
duration: this.state.filterArgs.includes('duration')
? this.state.filterArgs.match(/duration=([^&]*)/)?.[1]
: null,
publish_state: this.state.filterArgs.includes('publish_state')
? this.state.filterArgs.match(/publish_state=([^&]*)/)?.[1]
: null,
sort_by: sortOption, sort_by: sortOption,
tag: this.state.selectedTag, tag: this.state.selectedTag,
}); });
@@ -707,9 +754,16 @@ export class ProfileMediaPage extends Page {
let requestUrl; let requestUrl;
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + this.state.filterArgs;
} }
this.setState({ this.setState({
@@ -851,7 +905,10 @@ export class ProfileMediaPage extends Page {
onResponseDataLoaded(responseData) { onResponseDataLoaded(responseData) {
// Extract tags from response // Extract tags from response
if (responseData && responseData.tags) { if (responseData && responseData.tags) {
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag); const tags = responseData.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag);
this.setState({ availableTags: tags }); this.setState({ availableTags: tags });
} }
} }
@@ -862,12 +919,12 @@ export class ProfileMediaPage extends Page {
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username; const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
// Check if any filters are active (excluding default sort and tags) // Check if any filters are active (excluding default sort and tags)
const hasActiveFilters = this.state.filterArgs && ( const hasActiveFilters =
this.state.filterArgs.includes('media_type=') || this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
this.state.filterArgs.includes('upload_date=') || this.state.filterArgs.includes('upload_date=') ||
this.state.filterArgs.includes('duration=') || this.state.filterArgs.includes('duration=') ||
this.state.filterArgs.includes('publish_state=') this.state.filterArgs.includes('publish_state='));
);
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'; const hasActiveSort = this.state.selectedSort && this.state.selectedSort !== 'date_added_desc';
@@ -885,6 +942,7 @@ export class ProfileMediaPage extends Page {
hasActiveFilters={hasActiveFilters} hasActiveFilters={hasActiveFilters}
hasActiveTags={hasActiveTags} hasActiveTags={hasActiveTags}
hasActiveSort={hasActiveSort} hasActiveSort={hasActiveSort}
hideChannelBanner={inEmbeddedApp()}
/> />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
@@ -900,8 +958,18 @@ export class ProfileMediaPage extends Page {
onDeselectAll={this.handleDeselectAll} onDeselectAll={this.handleDeselectAll}
showAddMediaButton={isMediaAuthor} showAddMediaButton={isMediaAuthor}
> >
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} selectedTag={this.state.selectedTag} selectedSort={this.state.selectedSort} /> <ProfileMediaFilters
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} /> 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} /> <ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<LazyLoadItemListAsync <LazyLoadItemListAsync
key={`${this.state.requestUrl}-${this.state.listKey}`} key={`${this.state.requestUrl}-${this.state.listKey}`}

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { ApiUrlConsumer } from '../utils/contexts/'; import { ApiUrlConsumer } from '../utils/contexts/';
import { PageStore } from '../utils/stores/'; import { PageStore } from '../utils/stores/';
import { inEmbeddedApp } from '../utils/helpers/';
import { MediaListWrapper } from '../components/MediaListWrapper'; import { MediaListWrapper } from '../components/MediaListWrapper';
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader'; import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent'; import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
@@ -30,7 +31,7 @@ export class ProfilePlaylistsPage extends ProfileMediaPage {
pageContent() { pageContent() {
return [ return [
this.state.author ? ( this.state.author ? (
<ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" /> <ProfilePagesHeader key="ProfilePagesHeader" author={this.state.author} type="playlists" hideChannelBanner={inEmbeddedApp()} />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">

View File

@@ -11,7 +11,7 @@ import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFi
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
import { BulkActionsModals } from '../components/BulkActionsModals'; import { BulkActionsModals } from '../components/BulkActionsModals';
import { translateString } from '../utils/helpers'; import { inEmbeddedApp, translateString } from '../utils/helpers';
import { withBulkActions } from '../utils/hoc/withBulkActions'; import { withBulkActions } from '../utils/hoc/withBulkActions';
import { Page } from './_Page'; import { Page } from './_Page';
@@ -24,9 +24,7 @@ function EmptySharedByMe(props) {
{(links) => ( {(links) => (
<div className="empty-media empty-channel-media"> <div className="empty-media empty-channel-media">
<div className="welcome-title">No shared media</div> <div className="welcome-title">No shared media</div>
<div className="start-uploading"> <div className="start-uploading">Media that you have shared with others will show up here.</div>
Media that you have shared with others will show up here.
</div>
</div> </div>
)} )}
</LinksConsumer> </LinksConsumer>
@@ -81,9 +79,20 @@ class ProfileSharedByMePage extends Page {
if (author) { if (author) {
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_by_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_by_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_by_me' +
this.state.filterArgs;
} }
} }
@@ -132,9 +141,20 @@ class ProfileSharedByMePage extends Page {
let requestUrl; let requestUrl;
if (newQuery) { if (newQuery) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me&q=' +
encodeURIComponent(newQuery) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me' +
this.state.filterArgs;
} }
let title = this.state.title; let title = this.state.title;
@@ -290,9 +310,20 @@ class ProfileSharedByMePage extends Page {
let requestUrl; let requestUrl;
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_by_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_by_me' +
this.state.filterArgs;
} }
this.setState({ this.setState({
@@ -304,7 +335,10 @@ class ProfileSharedByMePage extends Page {
onResponseDataLoaded(responseData) { onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) { if (responseData && responseData.tags) {
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag); const tags = responseData.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag);
this.setState({ availableTags: tags }); this.setState({ availableTags: tags });
} }
} }
@@ -315,12 +349,12 @@ 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 // Check if any filters are active
const hasActiveFilters = this.state.filterArgs && ( const hasActiveFilters =
this.state.filterArgs.includes('media_type=') || this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
this.state.filterArgs.includes('upload_date=') || this.state.filterArgs.includes('upload_date=') ||
this.state.filterArgs.includes('duration=') || this.state.filterArgs.includes('duration=') ||
this.state.filterArgs.includes('publish_state=') this.state.filterArgs.includes('publish_state='));
);
return [ return [
this.state.author ? ( this.state.author ? (
@@ -335,6 +369,7 @@ class ProfileSharedByMePage extends Page {
hasActiveFilters={hasActiveFilters} hasActiveFilters={hasActiveFilters}
hasActiveTags={this.state.selectedTag !== 'all'} hasActiveTags={this.state.selectedTag !== 'all'}
hasActiveSort={this.state.selectedSort !== 'date_added_desc'} hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
hideChannelBanner={inEmbeddedApp()}
/> />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
@@ -349,8 +384,16 @@ class ProfileSharedByMePage extends Page {
onSelectAll={this.props.bulkActions.handleSelectAll} onSelectAll={this.props.bulkActions.handleSelectAll}
onDeselectAll={this.props.bulkActions.handleDeselectAll} onDeselectAll={this.props.bulkActions.handleDeselectAll}
> >
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} /> <ProfileMediaFilters
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} /> hidden={this.state.hiddenFilters}
tags={this.state.availableTags}
onFiltersUpdate={this.onFiltersUpdate}
/>
<ProfileMediaTags
hidden={this.state.hiddenTags}
tags={this.state.availableTags}
onTagSelect={this.onTagSelect}
/>
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} /> <ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<LazyLoadItemListAsync <LazyLoadItemListAsync
key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`} key={`${this.state.requestUrl}-${this.props.bulkActions.listKey}`}

View File

@@ -10,7 +10,7 @@ import { LazyLoadItemListAsync } from '../components/item-list/LazyLoadItemListA
import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters'; import { ProfileMediaFilters } from '../components/search-filters/ProfileMediaFilters';
import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags'; import { ProfileMediaTags } from '../components/search-filters/ProfileMediaTags';
import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting'; import { ProfileMediaSorting } from '../components/search-filters/ProfileMediaSorting';
import { translateString } from '../utils/helpers'; import { inEmbeddedApp, translateString } from '../utils/helpers';
import { Page } from './_Page'; import { Page } from './_Page';
@@ -22,9 +22,7 @@ function EmptySharedWithMe(props) {
{(links) => ( {(links) => (
<div className="empty-media empty-channel-media"> <div className="empty-media empty-channel-media">
<div className="welcome-title">No shared media</div> <div className="welcome-title">No shared media</div>
<div className="start-uploading"> <div className="start-uploading">Media that others have shared with you will show up here.</div>
Media that others have shared with you will show up here.
</div>
</div> </div>
)} )}
</LinksConsumer> </LinksConsumer>
@@ -79,9 +77,20 @@ export class ProfileSharedWithMePage extends Page {
if (author) { if (author) {
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_with_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + author.id + '&show=shared_with_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
author.id +
'&show=shared_with_me' +
this.state.filterArgs;
} }
} }
@@ -130,9 +139,20 @@ export class ProfileSharedWithMePage extends Page {
let requestUrl; let requestUrl;
if (newQuery) { if (newQuery) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(newQuery) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me&q=' +
encodeURIComponent(newQuery) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me' +
this.state.filterArgs;
} }
let title = this.state.title; let title = this.state.title;
@@ -288,9 +308,20 @@ export class ProfileSharedWithMePage extends Page {
let requestUrl; let requestUrl;
if (this.state.query) { if (this.state.query) {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me&q=' + encodeURIComponent(this.state.query) + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me&q=' +
encodeURIComponent(this.state.query) +
this.state.filterArgs;
} else { } else {
requestUrl = ApiUrlContext._currentValue.media + '?author=' + this.state.author.id + '&show=shared_with_me' + this.state.filterArgs; requestUrl =
ApiUrlContext._currentValue.media +
'?author=' +
this.state.author.id +
'&show=shared_with_me' +
this.state.filterArgs;
} }
this.setState({ this.setState({
@@ -302,7 +333,10 @@ export class ProfileSharedWithMePage extends Page {
onResponseDataLoaded(responseData) { onResponseDataLoaded(responseData) {
if (responseData && responseData.tags) { if (responseData && responseData.tags) {
const tags = responseData.tags.split(',').map((tag) => tag.trim()).filter((tag) => tag); const tags = responseData.tags
.split(',')
.map((tag) => tag.trim())
.filter((tag) => tag);
this.setState({ availableTags: tags }); this.setState({ availableTags: tags });
} }
} }
@@ -313,12 +347,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 // Check if any filters are active
const hasActiveFilters = this.state.filterArgs && ( const hasActiveFilters =
this.state.filterArgs.includes('media_type=') || this.state.filterArgs &&
(this.state.filterArgs.includes('media_type=') ||
this.state.filterArgs.includes('upload_date=') || this.state.filterArgs.includes('upload_date=') ||
this.state.filterArgs.includes('duration=') || this.state.filterArgs.includes('duration=') ||
this.state.filterArgs.includes('publish_state=') this.state.filterArgs.includes('publish_state='));
);
return [ return [
this.state.author ? ( this.state.author ? (
@@ -333,16 +367,22 @@ export class ProfileSharedWithMePage extends Page {
hasActiveFilters={hasActiveFilters} hasActiveFilters={hasActiveFilters}
hasActiveTags={this.state.selectedTag !== 'all'} hasActiveTags={this.state.selectedTag !== 'all'}
hasActiveSort={this.state.selectedSort !== 'date_added_desc'} hasActiveSort={this.state.selectedSort !== 'date_added_desc'}
hideChannelBanner={inEmbeddedApp()}
/> />
) : null, ) : null,
this.state.author ? ( this.state.author ? (
<ProfilePagesContent key="ProfilePagesContent"> <ProfilePagesContent key="ProfilePagesContent">
<MediaListWrapper <MediaListWrapper title={this.state.title} className="items-list-ver">
title={this.state.title} <ProfileMediaFilters
className="items-list-ver" hidden={this.state.hiddenFilters}
> tags={this.state.availableTags}
<ProfileMediaFilters hidden={this.state.hiddenFilters} tags={this.state.availableTags} onFiltersUpdate={this.onFiltersUpdate} /> onFiltersUpdate={this.onFiltersUpdate}
<ProfileMediaTags hidden={this.state.hiddenTags} tags={this.state.availableTags} onTagSelect={this.onTagSelect} /> />
<ProfileMediaTags
hidden={this.state.hiddenTags}
tags={this.state.availableTags}
onTagSelect={this.onTagSelect}
/>
<ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} /> <ProfileMediaSorting hidden={this.state.hiddenSorting} onSortSelect={this.onSortSelect} />
<LazyLoadItemListAsync <LazyLoadItemListAsync
key={this.state.requestUrl} key={this.state.requestUrl}

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { PageStore, MediaPageStore } from '../utils/stores/'; import { PageStore, MediaPageStore } from '../utils/stores/';
import { MediaPageActions } from '../utils/actions/'; import { MediaPageActions } from '../utils/actions/';
import { inEmbeddedApp } from '../utils/helpers/';
import ViewerError from '../components/media-page/ViewerError'; import ViewerError from '../components/media-page/ViewerError';
import ViewerInfo from '../components/media-page/ViewerInfo'; import ViewerInfo from '../components/media-page/ViewerInfo';
import ViewerSidebar from '../components/media-page/ViewerSidebar'; import ViewerSidebar from '../components/media-page/ViewerSidebar';
@@ -86,7 +87,7 @@ export class _MediaPage extends Page {
{!this.state.infoAndSidebarViewType {!this.state.infoAndSidebarViewType
? [ ? [
<ViewerInfo key="viewer-info" />, <ViewerInfo key="viewer-info" />,
this.state.pagePlaylistLoaded ? ( !inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
<ViewerSidebar <ViewerSidebar
key="viewer-sidebar" key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')} mediaId={MediaPageStore.get('media-id')}
@@ -95,7 +96,7 @@ export class _MediaPage extends Page {
) : null, ) : null,
] ]
: [ : [
this.state.pagePlaylistLoaded ? ( !inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
<ViewerSidebar <ViewerSidebar
key="viewer-sidebar" key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')} mediaId={MediaPageStore.get('media-id')}

View File

@@ -2,6 +2,7 @@ import React from 'react';
// FIXME: 'VideoViewerStore' is used only in case of video media, but is included in every media page code. // FIXME: 'VideoViewerStore' is used only in case of video media, but is included in every media page code.
import { PageStore, MediaPageStore, VideoViewerStore } from '../utils/stores/'; import { PageStore, MediaPageStore, VideoViewerStore } from '../utils/stores/';
import { MediaPageActions } from '../utils/actions/'; import { MediaPageActions } from '../utils/actions/';
import { inEmbeddedApp } from '../utils/helpers/';
import ViewerInfoVideo from '../components/media-page/ViewerInfoVideo'; import ViewerInfoVideo from '../components/media-page/ViewerInfoVideo';
import ViewerError from '../components/media-page/ViewerError'; import ViewerError from '../components/media-page/ViewerError';
import ViewerSidebar from '../components/media-page/ViewerSidebar'; import ViewerSidebar from '../components/media-page/ViewerSidebar';
@@ -54,7 +55,8 @@ export class _VideoMediaPage extends Page {
} }
onMediaLoad() { onMediaLoad() {
const isVideoMedia = 'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type'); const isVideoMedia =
'video' === MediaPageStore.get('media-type') || 'audio' === MediaPageStore.get('media-type');
if (isVideoMedia) { if (isVideoMedia) {
this.onViewerModeChange = this.onViewerModeChange.bind(this); this.onViewerModeChange = this.onViewerModeChange.bind(this);
@@ -102,7 +104,7 @@ export class _VideoMediaPage extends Page {
{!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode) {!this.state.wideLayout || (this.state.isVideoMedia && this.state.theaterMode)
? [ ? [
<ViewerInfoVideo key="viewer-info" />, <ViewerInfoVideo key="viewer-info" />,
this.state.pagePlaylistLoaded ? ( !inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
<ViewerSidebar <ViewerSidebar
key="viewer-sidebar" key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')} mediaId={MediaPageStore.get('media-id')}
@@ -111,7 +113,7 @@ export class _VideoMediaPage extends Page {
) : null, ) : null,
] ]
: [ : [
this.state.pagePlaylistLoaded ? ( !inEmbeddedApp() && this.state.pagePlaylistLoaded ? (
<ViewerSidebar <ViewerSidebar
key="viewer-sidebar" key="viewer-sidebar"
mediaId={MediaPageStore.get('media-id')} mediaId={MediaPageStore.get('media-id')}

View File

@@ -1,7 +1,7 @@
import React, { createContext, useContext, useEffect, useState } from 'react'; import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { BrowserCache } from '../classes/'; import { BrowserCache } from '../classes/';
import { PageStore } from '../stores/'; import { PageStore } from '../stores/';
import { addClassname, removeClassname } from '../helpers/'; import { addClassname, removeClassname, inEmbeddedApp } from '../helpers/';
import SiteContext from './SiteContext'; import SiteContext from './SiteContext';
let slidingSidebarTimeout; let slidingSidebarTimeout;
@@ -45,7 +45,10 @@ export const LayoutProvider = ({ children }) => {
const site = useContext(SiteContext); const site = useContext(SiteContext);
const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400); const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
const enabledSidebar = !!(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar')); const isMediaPage = useMemo(() => PageStore.get('current-page') === 'media', []);
const isEmbeddedApp = useMemo(() => inEmbeddedApp(), []);
const enabledSidebar = Boolean(document.getElementById('app-sidebar') || document.querySelector('.page-sidebar'));
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar')); const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false); const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
@@ -61,28 +64,27 @@ export const LayoutProvider = ({ children }) => {
}; };
useEffect(() => { useEffect(() => {
if (visibleSidebar) { if (!isEmbeddedApp && visibleSidebar) {
addClassname(document.body, 'visible-sidebar'); addClassname(document.body, 'visible-sidebar');
} else { } else {
removeClassname(document.body, 'visible-sidebar'); removeClassname(document.body, 'visible-sidebar');
} }
if ('media' !== PageStore.get('current-page') && 1023 < window.innerWidth) {
if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
cache.set('visible-sidebar', visibleSidebar); cache.set('visible-sidebar', visibleSidebar);
} }
}, [visibleSidebar]); }, [isEmbeddedApp, isMediaPage, visibleSidebar]);
useEffect(() => { useEffect(() => {
PageStore.once('page_init', () => { PageStore.once('page_init', () => {
if ('media' === PageStore.get('current-page')) { if (isEmbeddedApp || isMediaPage) {
setVisibleSidebar(false); setVisibleSidebar(false);
removeClassname(document.body, 'visible-sidebar'); removeClassname(document.body, 'visible-sidebar');
} }
}); });
setVisibleSidebar( setVisibleSidebar(
'media' !== PageStore.get('current-page') && !isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth && (null === visibleSidebar || visibleSidebar)
1023 < window.innerWidth &&
(null === visibleSidebar || visibleSidebar)
); );
}, []); }, []);

View File

@@ -0,0 +1,20 @@
export function inEmbeddedApp() {
try {
const params = new URL(globalThis.location.href).searchParams;
const mode = params.get('mode');
if (mode === 'embed_mode') {
sessionStorage.setItem('media_cms_embed_mode', 'true');
return true;
}
if (mode === 'standard') {
sessionStorage.removeItem('media_cms_embed_mode');
return false;
}
return sessionStorage.getItem('media_cms_embed_mode') === 'true';
} catch (e) {
return false;
}
}

View File

@@ -14,3 +14,4 @@ export * from './quickSort';
export * from './requests'; export * from './requests';
export { translateString } from './translate'; export { translateString } from './translate';
export { replaceString } from './replacementStrings'; export { replaceString } from './replacementStrings';
export * from './embeddedApp';

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import { ThemeProvider } from './contexts/ThemeContext'; import { ThemeProvider } from './contexts/ThemeContext';
import { LayoutProvider } from './contexts/LayoutContext'; import { LayoutProvider } from './contexts/LayoutContext';
import { UserProvider } from './contexts/UserContext'; import { UserProvider } from './contexts/UserContext';
import { inEmbeddedApp } from './helpers';
const AppProviders = ({ children }) => ( const AppProviders = ({ children }) => (
<LayoutProvider> <LayoutProvider>
@@ -15,9 +16,27 @@ const AppProviders = ({ children }) => (
import { PageHeader, PageSidebar } from '../components/page-layout'; import { PageHeader, PageSidebar } from '../components/page-layout';
export function renderPage(idSelector, PageComponent) { export function renderPage(idSelector, PageComponent) {
if (inEmbeddedApp()) {
globalThis.document.body.classList.add('embedded-app');
globalThis.document.body.classList.remove('visible-sidebar');
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
if (appContent && PageComponent) {
ReactDOM.render(
<AppProviders>
<PageComponent />
</AppProviders>,
appContent
);
}
return;
}
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
const appHeader = document.getElementById('app-header'); const appHeader = document.getElementById('app-header');
const appSidebar = document.getElementById('app-sidebar'); const appSidebar = document.getElementById('app-sidebar');
const appContent = idSelector ? document.getElementById(idSelector) : undefined;
if (appContent && PageComponent) { if (appContent && PageComponent) {
ReactDOM.render( ReactDOM.render(

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

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

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