Compare commits

..

1 Commits

Author SHA1 Message Date
Markos Gogoulos
223e87073f
feat: Implement persistent "Embed Mode" to hide UI shell via Session Storage (#1484)
* initial implementation

* updates in ViewerInfoVideoTitleBanner component

* Implement persistent "Embed Mode" to hide UI shell via Session Storage

---------

Co-authored-by: Yiannis <1515939+styiannis@users.noreply.github.com>
2026-01-31 15:27:40 +02:00
73 changed files with 6036 additions and 8805 deletions

View File

@ -1 +1 @@
VERSION = "7.6" VERSION = "7.7"

View File

@ -1,4 +1,3 @@
{ {
"editor.formatOnSave": true, "editor.formatOnSave": true
"prettier.configPath": "../.prettierrc"
} }

View File

@ -21,9 +21,6 @@
"@babel/core": "^7.26.9", "@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9", "@babel/preset-env": "^7.26.9",
"@babel/preset-react": "^7.26.3", "@babel/preset-react": "^7.26.3",
"@testing-library/dom": "^8.20.1",
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^12.1.5",
"@types/flux": "^3.1.15", "@types/flux": "^3.1.15",
"@types/jest": "^29.5.12", "@types/jest": "^29.5.12",
"@types/minimatch": "^5.1.2", "@types/minimatch": "^5.1.2",

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);
@ -1006,7 +1014,7 @@
&.like, &.like,
&.dislike { &.dislike {
&:before { &:before {
content: ''; content: "";
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: -4px; left: -4px;
@ -1146,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;
@ -1206,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);
} }
} }
} }
@ -1270,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;
} }
} }
@ -1303,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;
@ -1412,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%;
@ -1435,7 +1455,7 @@
cursor: not-allowed; cursor: not-allowed;
} }
input[type='text'] { input[type="text"] {
font-size: 14px; font-size: 14px;
} }
@ -1732,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;
@ -1788,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;
@ -1822,7 +1842,7 @@
vertical-align: top; vertical-align: top;
input { input {
&[type='checkbox'] { &[type="checkbox"] {
margin-left: 0; margin-left: 0;
} }
} }
@ -1834,7 +1854,7 @@
width: 100%; width: 100%;
input { input {
&[type='checkbox'] { &[type="checkbox"] {
margin-left: 0; margin-left: 0;
} }
} }
@ -1979,8 +1999,8 @@
} }
.item-date:before { .item-date:before {
content: ''; content: "";
content: '\2022'; content: "\2022";
margin: 0 4px; margin: 0 4px;
} }
@ -2017,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/';
@ -125,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);
@ -185,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">
@ -212,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 link={MediaPageStore.get('media-data').edit_url} /> : null} {userCan.editMedia ? (
<EditMediaButton link={MediaPageStore.get('media-data').edit_url} />
) : null}
{userCan.deleteMedia ? ( {userCan.deleteMedia ? (
<PopupTrigger contentRef={popupContentRef}> <PopupTrigger contentRef={popupContentRef}>
@ -234,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>
@ -253,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(

View File

@ -1,6 +1,6 @@
import EventEmitter from 'events'; import EventEmitter from 'events';
import { exportStore, getRequest, postRequest, putRequest, deleteRequest, csrfToken } from '../helpers'; import { exportStore, getRequest, postRequest, putRequest, deleteRequest, csrfToken } from '../helpers';
import { config as mediacmsConfig } from '../settings/config'; import { config as mediacmsConfig } from '../settings/config.js';
import UrlParse from 'url-parse'; import UrlParse from 'url-parse';
@ -39,35 +39,6 @@ function extractPlaylistId() {
return playlistId; return playlistId;
} }
function extractMediaId() {
let result = undefined;
let urlParams = (function () {
let ret = new UrlParse(window.location.href).query;
if (!ret) {
ret = [];
} else {
ret = ret.substring(1);
ret = ret.split('&');
ret = ret.length ? ret.map((v) => v.split('=')).flat() : [];
}
return ret;
})();
if (urlParams.length) {
let i = 0;
while (i < urlParams.length) {
if ('m' === urlParams[i]) {
// NOTE: "m" => media id/token.
result = urlParams[i + 1];
}
i += 2;
}
}
return result;
}
const MediaPageStoreData = {}; const MediaPageStoreData = {};
class MediaPageStore extends EventEmitter { class MediaPageStore extends EventEmitter {
@ -83,11 +54,8 @@ class MediaPageStore extends EventEmitter {
this.userList = null; this.userList = null;
MediaPageStoreData[ MediaPageStoreData[
Object.defineProperty(this, 'id', { Object.defineProperty(this, 'id', { value: 'MediaPageStoreData_' + Object.keys(MediaPageStoreData).length }).id
value: 'MediaPageStoreData_' + Object.keys(MediaPageStoreData).length,
}).id
] = { ] = {
mediaId: extractMediaId(),
likedMedia: false, likedMedia: false,
dislikedMedia: false, dislikedMedia: false,
reported_times: 0, reported_times: 0,
@ -109,7 +77,30 @@ class MediaPageStore extends EventEmitter {
} }
loadData() { loadData() {
MediaPageStoreData[this.id].mediaId = MediaPageStoreData[this.id].mediaId ?? extractMediaId(); if (!MediaPageStoreData[this.id].mediaId) {
let urlParams = (function () {
let ret = new UrlParse(window.location.href).query;
if (!ret) {
ret = [];
} else {
ret = ret.substring(1);
ret.split('&');
ret = ret.length ? ret.split('=') : [];
}
return ret;
})();
if (urlParams.length) {
let i = 0;
while (i < urlParams.length) {
if ('m' === urlParams[i]) {
// NOTE: "m" => media id/token.
MediaPageStoreData[this.id].mediaId = urlParams[i + 1];
}
i += 2;
}
}
}
if (!MediaPageStoreData[this.id].mediaId) { if (!MediaPageStoreData[this.id].mediaId) {
console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId); console.warn('Invalid media id:', MediaPageStoreData[this.id].mediaId);
@ -222,9 +213,7 @@ class MediaPageStore extends EventEmitter {
case 'unavailable': case 'unavailable':
MediaPageStoreData[this.id].loadErrorType = response.type; MediaPageStoreData[this.id].loadErrorType = response.type;
MediaPageStoreData[this.id].loadErrorMessage = MediaPageStoreData[this.id].loadErrorMessage =
void 0 !== response.message void 0 !== response.message ? response.message : "Αn error occurred while loading the media's data";
? response.message
: "Αn error occurred while loading the media's data";
this.emit('loaded_media_error'); this.emit('loaded_media_error');
break; break;
} }
@ -284,9 +273,7 @@ class MediaPageStore extends EventEmitter {
while (f < resp.data.playlist_media.length) { while (f < resp.data.playlist_media.length) {
arr = resp.data.playlist_media[f].url.split('m='); arr = resp.data.playlist_media[f].url.split('m=');
if (2 === arr.length) { if (2 === arr.length) {
MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list.push( MediaPageStoreData[_this.id].playlists[playlistsIndex].media_list.push(arr[1]);
arr[1]
);
} }
f += 1; f += 1;
} }
@ -295,12 +282,12 @@ class MediaPageStore extends EventEmitter {
cntr += 1; cntr += 1;
if (cntr === tmp_playlists.length) { if (cntr === tmp_playlists.length) {
_this.emit('playlists_load'); this.emit('playlists_load');
} }
} }
); );
} }
}).bind(this)(i); }.bind(this)(i));
i += 1; i += 1;
} }
@ -442,16 +429,11 @@ class MediaPageStore extends EventEmitter {
r = MediaPageStoreData[this.id].playlists || []; r = MediaPageStoreData[this.id].playlists || [];
break; break;
case 'media-load-error-type': case 'media-load-error-type':
r = r = void 0 !== MediaPageStoreData[this.id].loadErrorType ? MediaPageStoreData[this.id].loadErrorType : null;
void 0 !== MediaPageStoreData[this.id].loadErrorType
? MediaPageStoreData[this.id].loadErrorType
: null;
break; break;
case 'media-load-error-message': case 'media-load-error-message':
r = r =
void 0 !== MediaPageStoreData[this.id].loadErrorMessage void 0 !== MediaPageStoreData[this.id].loadErrorMessage ? MediaPageStoreData[this.id].loadErrorMessage : null;
? MediaPageStoreData[this.id].loadErrorMessage
: null;
break; break;
case 'media-comments': case 'media-comments':
r = MediaPageStoreData[this.id].comments || []; r = MediaPageStoreData[this.id].comments || [];
@ -493,14 +475,12 @@ class MediaPageStore extends EventEmitter {
tmp = MediaPageStoreData[this.id].dislikedMedia ? 1 : 0; tmp = MediaPageStoreData[this.id].dislikedMedia ? 1 : 0;
if (tmp) { if (tmp) {
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.dislikes
void 0 !== MediaPageStoreData[this.id].data.dislikes
? MediaPageStoreData[this.id].data.dislikes + tmp ? MediaPageStoreData[this.id].data.dislikes + tmp
: tmp; : tmp;
} else { } else {
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.dislikes
void 0 !== MediaPageStoreData[this.id].data.dislikes
? MediaPageStoreData[this.id].data.dislikes ? MediaPageStoreData[this.id].data.dislikes
: 'N/A'; : 'N/A';
} }
@ -513,8 +493,7 @@ class MediaPageStore extends EventEmitter {
break; break;
case 'media-categories': case 'media-categories':
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.categories_info
void 0 !== MediaPageStoreData[this.id].data.categories_info
? MediaPageStoreData[this.id].data.categories_info ? MediaPageStoreData[this.id].data.categories_info
: []; : [];
break; break;
@ -526,22 +505,19 @@ class MediaPageStore extends EventEmitter {
break; break;
case 'media-type': case 'media-type':
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.media_type
void 0 !== MediaPageStoreData[this.id].data.media_type
? MediaPageStoreData[this.id].data.media_type ? MediaPageStoreData[this.id].data.media_type
: null; : null;
break; break;
case 'media-original-url': case 'media-original-url':
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.original_media_url
void 0 !== MediaPageStoreData[this.id].data.original_media_url
? MediaPageStoreData[this.id].data.original_media_url ? MediaPageStoreData[this.id].data.original_media_url
: null; : null;
break; break;
case 'media-thumbnail-url': case 'media-thumbnail-url':
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.thumbnail_url
void 0 !== MediaPageStoreData[this.id].data.thumbnail_url
? MediaPageStoreData[this.id].data.thumbnail_url ? MediaPageStoreData[this.id].data.thumbnail_url
: null; : null;
break; break;
@ -553,8 +529,7 @@ class MediaPageStore extends EventEmitter {
break; break;
case 'media-author-thumbnail-url': case 'media-author-thumbnail-url':
r = r =
void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data && void 0 !== MediaPageStoreData[this.id].data.author_thumbnail
void 0 !== MediaPageStoreData[this.id].data.author_thumbnail
? this.mediacms_config.site.url + ? this.mediacms_config.site.url +
'/' + '/' +
MediaPageStoreData[this.id].data.author_thumbnail.replace(/^\//g, '') MediaPageStoreData[this.id].data.author_thumbnail.replace(/^\//g, '')
@ -574,9 +549,7 @@ class MediaPageStore extends EventEmitter {
activeItem = 0; activeItem = 0;
i = 0; i = 0;
while (i < this.pagePlaylistData.playlist_media.length) { while (i < this.pagePlaylistData.playlist_media.length) {
if ( if (MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token) {
MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token
) {
activeItem = i; activeItem = i;
break; break;
} }
@ -606,9 +579,7 @@ class MediaPageStore extends EventEmitter {
activeItem = 0; activeItem = 0;
i = 0; i = 0;
while (i < this.pagePlaylistData.playlist_media.length) { while (i < this.pagePlaylistData.playlist_media.length) {
if ( if (MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token) {
MediaPageStoreData[this.id].mediaId === this.pagePlaylistData.playlist_media[i].friendly_token
) {
activeItem = i; activeItem = i;
break; break;
} }
@ -678,10 +649,7 @@ class MediaPageStore extends EventEmitter {
new_playlist_media = []; new_playlist_media = [];
j = 0; j = 0;
while (j < MediaPageStoreData[this.id].playlists[i].media_list.length) { while (j < MediaPageStoreData[this.id].playlists[i].media_list.length) {
if ( if (MediaPageStoreData[this.id].mediaId !== MediaPageStoreData[this.id].playlists[i].media_list[j]) {
MediaPageStoreData[this.id].mediaId !==
MediaPageStoreData[this.id].playlists[i].media_list[j]
) {
new_playlist_media.push(MediaPageStoreData[this.id].playlists[i].media_list[j]); new_playlist_media.push(MediaPageStoreData[this.id].playlists[i].media_list[j]);
} }
j += 1; j += 1;
@ -852,13 +820,6 @@ class MediaPageStore extends EventEmitter {
if (response && 204 === response.status) { if (response && 204 === response.status) {
this.emit('media_delete', MediaPageStoreData[this.id].mediaId); this.emit('media_delete', MediaPageStoreData[this.id].mediaId);
} }
setTimeout(
function (ins) {
MediaPageStoreData[ins.id].while.deleteMedia = null;
},
100,
this
);
} }
removeMediaFail() { removeMediaFail() {
@ -889,10 +850,7 @@ class MediaPageStore extends EventEmitter {
let newComments = []; let newComments = [];
for (k in MediaPageStoreData[this.id].comments) { for (k in MediaPageStoreData[this.id].comments) {
if (MediaPageStoreData[this.id].comments.hasOwnProperty(k)) { if (MediaPageStoreData[this.id].comments.hasOwnProperty(k)) {
if ( if (MediaPageStoreData[this.id].while.deleteCommentId !== MediaPageStoreData[this.id].comments[k].uid) {
MediaPageStoreData[this.id].while.deleteCommentId !==
MediaPageStoreData[this.id].comments[k].uid
) {
newComments.push(MediaPageStoreData[this.id].comments[k]); newComments.push(MediaPageStoreData[this.id].comments[k]);
} }
} }

View File

@ -62,7 +62,6 @@ class VideoPlayerStore extends EventEmitter {
switch (action.type) { switch (action.type) {
case 'TOGGLE_VIEWER_MODE': case 'TOGGLE_VIEWER_MODE':
_StoreData.inTheaterMode = !_StoreData.inTheaterMode; _StoreData.inTheaterMode = !_StoreData.inTheaterMode;
browserCache.set('in-theater-mode', _StoreData.inTheaterMode);
this.emit('changed_viewer_mode'); this.emit('changed_viewer_mode');
break; break;
case 'SET_VIEWER_MODE': case 'SET_VIEWER_MODE':

View File

@ -1,385 +0,0 @@
export const sampleGlobalMediaCMS = {
profileId: 'john',
site: {
id: 'my-site',
url: 'https://example.com/',
api: 'https://example.com/api/',
title: 'Example',
theme: { mode: 'dark', switch: { enabled: true, position: 'sidebar' } },
logo: {
lightMode: { img: '/img/light.png', svg: '/img/light.svg' },
darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' },
},
devEnv: false,
useRoundedCorners: true,
version: '1.0.0',
taxonomies: {
tags: { enabled: true, title: 'Topic Tags' },
categories: { enabled: false, title: 'Kinds' },
},
pages: {
featured: { enabled: true, title: 'Featured picks' },
latest: { enabled: true, title: 'Recent uploads' },
members: { enabled: true, title: 'People' },
recommended: { enabled: false, title: 'You may like' },
},
userPages: {
liked: { enabled: true, title: 'Favorites' },
history: { enabled: true, title: 'Watched' },
},
},
url: {
home: '/',
admin: '/admin',
error404: '/404',
latestMedia: '/latest',
featuredMedia: '/featured',
recommendedMedia: '/recommended',
signin: '/signin',
signout: '/signout',
register: '/register',
changePassword: '/password',
members: '/members',
search: '/search',
likedMedia: '/liked',
history: '/history',
addMedia: '/add',
editChannel: '/edit/channel',
editProfile: '/edit/profile',
tags: '/tags',
categories: '/categories',
manageMedia: '/manage/media',
manageUsers: '/manage/users',
manageComments: '/manage/comments',
},
api: {
media: 'v1/media/',
playlists: 'v1/playlists',
members: 'v1/users',
liked: 'v1/user/liked',
history: 'v1/user/history',
tags: 'v1/tags',
categories: 'v1/categories',
manage_media: 'v1/manage/media',
manage_users: 'v1/manage/users',
manage_comments: 'v1/manage/comments',
search: 'v1/search',
actions: 'v1/actions',
comments: 'v1/comments',
},
contents: {
header: {
right: '',
onLogoRight: '',
},
notifications: {
messages: { addToLiked: 'Yay', removeFromLiked: 'Oops', addToDisliked: 'nay', removeFromDisliked: 'ok' },
},
sidebar: {
belowNavMenu: '__belowNavMenu__',
belowThemeSwitcher: '__belowThemeSwitcher__',
footer: '__footer__',
mainMenuExtraItems: [
{ text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' },
],
navMenuItems: [
{ text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' },
],
},
uploader: {
belowUploadArea: '__belowUploadArea__',
postUploadMessage: '__postUploadMessage__',
},
},
pages: {
home: {
sections: {
latest: { title: 'Latest T' },
featured: { title: 'Featured T' },
recommended: { title: 'Recommended T' },
},
},
media: { categoriesWithTitle: true, htmlInDescription: true, hideViews: true, related: { initialSize: 5 } },
profile: { htmlInDescription: true, includeHistory: true, includeLikedMedia: true },
search: { advancedFilters: true },
},
features: {
mediaItem: { hideAuthor: true, hideViews: false, hideDate: true },
media: {
actions: {
like: true,
dislike: true,
report: true,
comment: true,
comment_mention: true,
download: true,
save: true,
share: true,
},
shareOptions: ['embed', 'email'],
},
playlists: { mediaTypes: ['audio'] },
sideBar: { hideHomeLink: false, hideTagsLink: true, hideCategoriesLink: false },
embeddedVideo: { initialDimensions: { width: 640, height: 360 } },
headerBar: { hideLogin: false, hideRegister: true },
},
user: {
is: { anonymous: false, admin: true },
name: ' John ',
username: ' john ',
thumbnail: ' /img/j.png ',
can: {
changePassword: true,
deleteProfile: true,
addComment: true,
mentionComment: true,
deleteComment: true,
editMedia: true,
deleteMedia: true,
editSubtitle: true,
manageMedia: true,
manageUsers: true,
manageComments: true,
contactUser: true,
canSeeMembersPage: true,
usersNeedsToBeApproved: false,
addMedia: true,
editProfile: true,
readComment: true,
},
pages: { about: '/u/john/about ', media: '/u/john ', playlists: '/u/john/playlists ' },
},
};
export const sampleMediaCMSConfig = {
api: {
archive: {
tags: '',
categories: '',
},
featured: '',
manage: {
media: '',
users: '',
comments: '',
},
media: '',
playlists: '/v1/playlists',
recommended: '',
search: {
query: '',
titles: './search.html?titles=',
tag: '',
category: '',
},
user: {
liked: '',
history: '',
playlists: '/playlists/?author=',
},
users: '/users',
},
contents: {
header: {
right: '',
onLogoRight: '',
},
uploader: {
belowUploadArea: '',
postUploadMessage: '',
},
sidebar: {
belowNavMenu: '__belowNavMenu__',
belowThemeSwitcher: '__belowThemeSwitcher__',
footer: '__footer__',
mainMenuExtra: {
items: [{ text: '__text_1__', link: '__link_1__', icon: '__icon_1__', className: '__className_1__' }],
},
navMenu: {
items: [{ text: '__text_2__', link: '__link_2__', icon: '__icon_2__', className: '__className_2__' }],
},
},
},
enabled: {
taxonomies: sampleGlobalMediaCMS.site.taxonomies,
pages: {
featured: { enabled: true, title: 'Featured picks' },
latest: { enabled: true, title: 'Recent uploads' },
members: { enabled: true, title: 'People' },
recommended: { enabled: true, title: 'You may like' },
liked: { enabled: true, title: 'Favorites' },
history: { enabled: true, title: 'Watched' },
},
},
member: {
name: null,
username: 'john',
thumbnail: null,
is: {
admin: false,
anonymous: false,
},
can: {
addComment: false,
addMedia: false,
canSeeMembersPage: false,
changePassword: false,
contactUser: false,
deleteComment: false,
deleteMedia: false,
deleteProfile: false,
dislikeMedia: false,
downloadMedia: false,
editMedia: false,
editProfile: false,
editSubtitle: false,
likeMedia: false,
login: false,
manageComments: false,
manageMedia: false,
manageUsers: false,
mentionComment: false,
readComment: true,
register: false,
reportMedia: false,
saveMedia: true,
shareMedia: false,
usersNeedsToBeApproved: false,
},
pages: {
home: null,
about: null,
media: null,
playlists: null,
},
},
media: {
item: {
displayAuthor: false,
displayViews: false,
displayPublishDate: false,
},
share: {
options: [],
},
},
notifications: {
messages: {
addToLiked: '',
removeFromLiked: '',
addToDisliked: '',
removeFromDisliked: '',
},
},
options: {
pages: {
home: {
sections: {
latest: {
title: '',
},
featured: {
title: '',
},
recommended: {
title: '',
},
},
},
search: {
advancedFilters: false,
},
media: {
categoriesWithTitle: true,
htmlInDescription: true,
related: { initialSize: 5 },
displayViews: true,
},
profile: {
htmlInDescription: false,
includeHistory: false,
includeLikedMedia: false,
},
},
embedded: {
video: {
dimensions: {
width: 0,
widthUnit: 'px',
height: 0,
heightUnit: 'px',
},
},
},
},
playlists: {
mediaTypes: [],
},
sidebar: {
hideHomeLink: false,
hideTagsLink: false,
hideCategoriesLink: false,
},
site: {
api: '',
id: '',
title: '',
url: '',
useRoundedCorners: false,
version: '',
},
theme: {
logo: {
lightMode: { img: '/img/light.png', svg: '/img/light.svg' },
darkMode: { img: '/img/dark.png', svg: '/img/dark.svg' },
},
mode: 'dark',
switch: {
enabled: true,
position: 'sidebar',
},
},
url: {
admin: '',
archive: {
categories: '',
tags: '',
},
changePassword: '',
embed: '',
error404: '',
featured: '',
home: '',
latest: '',
manage: {
comments: '',
media: '',
users: '',
},
members: '',
profile: {
about: '',
media: '',
playlists: '',
shared_by_me: '',
shared_with_me: '',
},
recommended: '',
register: '',
search: {
base: '',
category: '',
query: '',
tag: '',
},
signin: '',
signout: '',
user: {
addMedia: '',
editChannel: '',
editProfile: '',
history: '',
liked: '',
},
},
};

View File

@ -1,145 +0,0 @@
import * as MediaPageActions from '../../../src/static/js/utils/actions/MediaPageActions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by MediaPageActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('MediaPageActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('loadMediaData', () => {
it('Should dispatch LOAD_MEDIA_DATA action', () => {
MediaPageActions.loadMediaData();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_MEDIA_DATA' });
});
});
describe('likeMedia / dislikeMedia', () => {
it('Should dispatch LIKE_MEDIA action', () => {
MediaPageActions.likeMedia();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'LIKE_MEDIA' });
});
it('Should dispatch DISLIKE_MEDIA action', () => {
MediaPageActions.dislikeMedia();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'DISLIKE_MEDIA' });
});
});
describe('reportMedia', () => {
it('Should dispatch REPORT_MEDIA with empty string when description is undefined', () => {
MediaPageActions.reportMedia();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: '' });
});
// @todo: Revisit this behavior
it('Should dispatch REPORT_MEDIA with stripped description when provided', () => {
MediaPageActions.reportMedia(' some text ');
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'sometext' });
});
// @todo: Revisit this behavior
it('Should remove all whitespace characters including newlines and tabs', () => {
MediaPageActions.reportMedia('\n\t spaced\ntext \t');
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REPORT_MEDIA', reportDescription: 'spacedtext' });
});
});
describe('copyShareLink / copyEmbedMediaCode', () => {
it('Should dispatch COPY_SHARE_LINK carrying the provided input element', () => {
const inputElem = document.createElement('input');
MediaPageActions.copyShareLink(inputElem);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_SHARE_LINK', inputElement: inputElem });
});
it('Should dispatch COPY_EMBED_MEDIA_CODE carrying the provided textarea element', () => {
const textarea = document.createElement('textarea');
MediaPageActions.copyEmbedMediaCode(textarea);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'COPY_EMBED_MEDIA_CODE', inputElement: textarea });
});
});
describe('removeMedia', () => {
it('Should dispatch REMOVE_MEDIA action', () => {
MediaPageActions.removeMedia();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA' });
});
});
describe('comments', () => {
it('Should dispatch SUBMIT_COMMENT with provided text', () => {
const commentText = 'Nice one';
MediaPageActions.submitComment(commentText);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'SUBMIT_COMMENT', commentText });
});
it('Should dispatch DELETE_COMMENT with provided comment id', () => {
const commentId = 'c-123';
MediaPageActions.deleteComment(commentId);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
});
// @todo: Revisit this behavior
it('Should dispatch DELETE_COMMENT with numeric comment id', () => {
const commentId = 42;
MediaPageActions.deleteComment(commentId);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'DELETE_COMMENT', commentId });
});
});
describe('playlists', () => {
it('Should dispatch CREATE_PLAYLIST with provided data', () => {
const payload = { title: 'My list', description: 'Desc' };
MediaPageActions.createPlaylist(payload);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'CREATE_PLAYLIST', playlist_data: payload });
});
it('Should dispatch ADD_MEDIA_TO_PLAYLIST with ids', () => {
const playlist_id = 'pl-1';
const media_id = 'm-1';
MediaPageActions.addMediaToPlaylist(playlist_id, media_id);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_MEDIA_TO_PLAYLIST', playlist_id, media_id });
});
it('Should dispatch REMOVE_MEDIA_FROM_PLAYLIST with ids', () => {
const playlist_id = 'pl-1';
const media_id = 'm-1';
MediaPageActions.removeMediaFromPlaylist(playlist_id, media_id);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id, media_id });
});
it('Should dispatch APPEND_NEW_PLAYLIST with provided playlist data', () => {
const playlist_data = {
playlist_id: 'pl-2',
add_date: new Date('2020-01-01T00:00:00Z'),
description: 'Cool',
title: 'T',
media_list: ['a', 'b'],
};
MediaPageActions.addNewPlaylist(playlist_data);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'APPEND_NEW_PLAYLIST', playlist_data });
});
});
});
});

View File

@ -1,55 +0,0 @@
import * as PageActions from '../../../src/static/js/utils/actions/PageActions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by PageActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('PageActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('initPage', () => {
it('Should dispatch INIT_PAGE with provided page string', () => {
PageActions.initPage('home');
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: 'home' });
});
// @todo: Revisit this behavior
it('Should dispatch INIT_PAGE with empty string', () => {
PageActions.initPage('');
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'INIT_PAGE', page: '' });
});
});
describe('toggleMediaAutoPlay', () => {
it('Should dispatch TOGGLE_AUTO_PLAY action', () => {
PageActions.toggleMediaAutoPlay();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_AUTO_PLAY' });
});
});
describe('addNotification', () => {
it('Should dispatch ADD_NOTIFICATION with message and id', () => {
const notification = 'Saved!';
const notificationId = 'notif-1';
PageActions.addNotification(notification, notificationId);
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
});
// @todo: Revisit this behavior
it('Should dispatch ADD_NOTIFICATION with empty notification message', () => {
const notification = '';
const notificationId = 'id-empty';
PageActions.addNotification(notification, notificationId);
expect(dispatch).toHaveBeenCalledWith({ type: 'ADD_NOTIFICATION', notification, notificationId });
});
});
});
});

View File

@ -1,96 +0,0 @@
import { PlaylistPageActions } from '../../../src/static/js/utils/actions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by PlaylistPageActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('PlaylistPageActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('loadPlaylistData', () => {
it('Should dispatch LOAD_PLAYLIST_DATA action', () => {
PlaylistPageActions.loadPlaylistData();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_PLAYLIST_DATA' });
});
});
describe('toggleSave', () => {
it('Should dispatch TOGGLE_SAVE action', () => {
PlaylistPageActions.toggleSave();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
});
});
describe('updatePlaylist', () => {
it('Should dispatch UPDATE_PLAYLIST with provided title and description', () => {
const payload = { title: 'My Playlist', description: 'A description' };
PlaylistPageActions.updatePlaylist(payload);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
});
// @todo: Revisit this behavior
it('Should dispatch UPDATE_PLAYLIST with empty strings for title and description', () => {
const payload = { title: '', description: '' };
PlaylistPageActions.updatePlaylist(payload);
expect(dispatch).toHaveBeenCalledWith({ type: 'UPDATE_PLAYLIST', playlist_data: payload });
});
});
describe('removePlaylist', () => {
it('Should dispatch REMOVE_PLAYLIST action', () => {
PlaylistPageActions.removePlaylist();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PLAYLIST' });
});
});
describe('removedMediaFromPlaylist', () => {
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with media and playlist ids', () => {
PlaylistPageActions.removedMediaFromPlaylist('m1', 'p1');
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
media_id: 'm1',
playlist_id: 'p1',
});
});
// @todo: Revisit this behavior
it('Should dispatch MEDIA_REMOVED_FROM_PLAYLIST with empty ids as strings', () => {
PlaylistPageActions.removedMediaFromPlaylist('', '');
expect(dispatch).toHaveBeenCalledWith({
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
media_id: '',
playlist_id: '',
});
});
});
describe('reorderedMediaInPlaylist', () => {
it('Should dispatch PLAYLIST_MEDIA_REORDERED with provided array', () => {
const items = [
{ id: '1', url: '/1', thumbnail_url: '/t1' },
{ id: '2', url: '/2', thumbnail_url: '/t2' },
];
PlaylistPageActions.reorderedMediaInPlaylist(items);
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
});
// @todo: Revisit this behavior
it('Should dispatch PLAYLIST_MEDIA_REORDERED with empty array for playlist media', () => {
const items: any[] = [];
PlaylistPageActions.reorderedMediaInPlaylist(items);
expect(dispatch).toHaveBeenCalledWith({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: items });
});
});
});
});

View File

@ -1,39 +0,0 @@
import { PlaylistViewActions } from '../../../src/static/js/utils/actions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by PlaylistViewActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('PlaylistViewActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('toggleLoop', () => {
it('Should dispatch TOGGLE_LOOP action', () => {
PlaylistViewActions.toggleLoop();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_LOOP' });
});
});
describe('toggleShuffle', () => {
it('Should dispatch TOGGLE_SHUFFLE action', () => {
PlaylistViewActions.toggleShuffle();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SHUFFLE' });
});
});
describe('toggleSave', () => {
it('Should dispatch TOGGLE_SAVE action', () => {
PlaylistViewActions.toggleSave();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'TOGGLE_SAVE' });
});
});
});
});

View File

@ -1,27 +0,0 @@
import { ProfilePageActions } from '../../../src/static/js/utils/actions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by ProfilePageActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('ProfilePageActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
it('Should dispatch LOAD_AUTHOR_DATA ', () => {
ProfilePageActions.load_author_data();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'LOAD_AUTHOR_DATA' });
});
it('Should dispatch REMOVE_PROFILE ', () => {
ProfilePageActions.remove_profile();
expect(dispatch).toHaveBeenCalledTimes(1);
expect(dispatch).toHaveBeenCalledWith({ type: 'REMOVE_PROFILE' });
});
});
});

View File

@ -1,25 +0,0 @@
import { SearchFieldActions } from '../../../src/static/js/utils/actions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by SearchFieldActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('SearchFieldActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('requestPredictions', () => {
it('Should dispatch REQUEST_PREDICTIONS with provided query strings', () => {
SearchFieldActions.requestPredictions('cats');
SearchFieldActions.requestPredictions('');
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'REQUEST_PREDICTIONS', query: 'cats' });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'REQUEST_PREDICTIONS', query: '' });
});
});
});
});

View File

@ -1,72 +0,0 @@
import { VideoViewerActions } from '../../../src/static/js/utils/actions';
import dispatcher from '../../../src/static/js/utils/dispatcher';
// Mock the dispatcher module used by VideoViewerActions
jest.mock('../../../src/static/js/utils/dispatcher', () => ({ dispatch: jest.fn() }));
describe('utils/actions', () => {
describe('VideoViewerActions', () => {
const dispatch = dispatcher.dispatch;
beforeEach(() => {
(dispatcher.dispatch as jest.Mock).mockClear();
});
describe('set_viewer_mode', () => {
it('Should dispatch SET_VIEWER_MODE with "true" and "false" for enabling and disabling theater mode', () => {
VideoViewerActions.set_viewer_mode(true);
VideoViewerActions.set_viewer_mode(false);
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIEWER_MODE', inTheaterMode: true });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIEWER_MODE', inTheaterMode: false });
});
});
describe('set_player_volume', () => {
it('Should dispatch SET_PLAYER_VOLUME with provided volume numbers', () => {
VideoViewerActions.set_player_volume(0);
VideoViewerActions.set_player_volume(0.75);
VideoViewerActions.set_player_volume(1);
expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_VOLUME', playerVolume: 0 });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_PLAYER_VOLUME', playerVolume: 0.75 });
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_PLAYER_VOLUME', playerVolume: 1 });
});
});
describe('set_player_sound_muted', () => {
it('Should dispatch SET_PLAYER_SOUND_MUTED with "true" and "false"', () => {
VideoViewerActions.set_player_sound_muted(true);
VideoViewerActions.set_player_sound_muted(false);
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: true });
expect(dispatch).toHaveBeenNthCalledWith(2, {
type: 'SET_PLAYER_SOUND_MUTED',
playerSoundMuted: false,
});
});
});
describe('set_video_quality', () => {
it('Should dispatch SET_VIDEO_QUALITY with "auto" and numeric quality', () => {
VideoViewerActions.set_video_quality('auto');
VideoViewerActions.set_video_quality(720);
expect(dispatch).toHaveBeenCalledTimes(2);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_QUALITY', quality: 'auto' });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_QUALITY', quality: 720 });
});
});
describe('set_video_playback_speed', () => {
it('Should dispatch SET_VIDEO_PLAYBACK_SPEED with different speeds', () => {
VideoViewerActions.set_video_playback_speed(1.5);
VideoViewerActions.set_video_playback_speed(0.5);
VideoViewerActions.set_video_playback_speed(2);
expect(dispatch).toHaveBeenCalledTimes(3);
expect(dispatch).toHaveBeenNthCalledWith(1, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 1.5 });
expect(dispatch).toHaveBeenNthCalledWith(2, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 0.5 });
expect(dispatch).toHaveBeenNthCalledWith(3, { type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: 2 });
});
});
});
});

View File

@ -1,92 +0,0 @@
import { BrowserCache } from '../../../src/static/js/utils/classes/BrowserCache';
// Mocks for helpers used by BrowserCache
jest.mock('../../../src/static/js/utils/helpers/', () => ({
logErrorAndReturnError: jest.fn((args: any[]) => ({ error: true, args })),
logWarningAndReturnError: jest.fn((args: any[]) => ({ warning: true, args })),
}));
const { logErrorAndReturnError } = jest.requireMock('../../../src/static/js/utils/helpers/');
describe('utils/classes', () => {
describe('BrowserCache', () => {
beforeEach(() => {
localStorage.clear();
jest.clearAllMocks();
});
test('Returns error when prefix is missing', () => {
const cache = BrowserCache(undefined, 3600);
expect(cache).toEqual(expect.objectContaining({ error: true }));
expect(logErrorAndReturnError).toHaveBeenCalledWith(['Cache object prefix is required']);
});
test('Set and get returns stored primitive value before expiration', () => {
const cache = BrowserCache('prefix', 3600);
if (cache instanceof Error) {
expect(cache instanceof Error).toBe(false);
return;
}
expect(cache.set('foo', 'bar')).toBe(true);
expect(cache.get('foo')).toBe('bar');
// Ensure value serialized in localStorage with namespaced key
const raw = localStorage.getItem('prefix[foo]') as string;
const parsed = JSON.parse(raw);
expect(parsed.value).toBe('bar');
expect(typeof parsed.expire).toBe('number');
expect(parsed.expire).toBeGreaterThan(Date.now());
});
test('Get returns null when expired', () => {
const cache = BrowserCache('prefix', 1);
if (cache instanceof Error) {
expect(cache instanceof Error).toBe(false);
return;
}
cache.set('exp', { a: 1 });
jest.useFakeTimers();
jest.advanceTimersByTime(1_000);
expect(cache.get('exp')).toBeNull();
jest.useRealTimers();
});
test('Clear removes only keys for its prefix', () => {
const cacheA = BrowserCache('A', 3600);
const cacheB = BrowserCache('B', 3600);
if (cacheA instanceof Error) {
expect(cacheA instanceof Error).toBe(false);
return;
}
if (cacheB instanceof Error) {
expect(cacheB instanceof Error).toBe(false);
return;
}
cacheA.set('x', 1);
cacheB.set('x', 2);
expect(localStorage.getItem('A[x]')).toBeTruthy();
expect(localStorage.getItem('B[x]')).toBeTruthy();
cacheA.clear();
expect(localStorage.getItem('A[x]')).toBeNull();
expect(localStorage.getItem('B[x]')).toBeTruthy();
cacheB.clear();
expect(localStorage.getItem('A[x]')).toBeNull();
expect(localStorage.getItem('B[x]')).toBeNull();
});
});
});

View File

@ -1,101 +0,0 @@
import { MediaDurationInfo } from '../../../src/static/js/utils/classes/MediaDurationInfo';
describe('utils/classes', () => {
describe('MediaDurationInfo', () => {
test('Initializes via constructor when seconds is a positive integer (<= 59)', () => {
const mdi = new MediaDurationInfo(42);
expect(mdi.toString()).toBe('0:42');
expect(mdi.ariaLabel()).toBe('42 seconds');
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H0M42S');
});
test('Formats minutes and zero-pads seconds; no hours prefix under 60 minutes', () => {
const mdi = new MediaDurationInfo();
mdi.update(5 * 60 + 7);
expect(mdi.toString()).toBe('5:07');
expect(mdi.ariaLabel()).toBe('5 minutes, 7 seconds');
expect(mdi.ISO8601()).toBe('P0Y0M0DT0H5M7S');
});
test('Includes hours when duration >= 1 hour and zero-pads minutes when needed', () => {
const mdi = new MediaDurationInfo();
mdi.update(1 * 3600 + 2 * 60 + 3);
expect(mdi.toString()).toBe('1:02:03');
expect(mdi.ariaLabel()).toBe('1 hours, 2 minutes, 3 seconds');
expect(mdi.ISO8601()).toBe('P0Y0M0DT1H2M3S');
});
test('Accumulates hours when days are present (e.g., 1 day + 2:03:04 => 26:03:04)', () => {
const mdi = new MediaDurationInfo();
const seconds = 1 * 86400 + 2 * 3600 + 3 * 60 + 4; // 1d 2:03:04 => 26:03:04
mdi.update(seconds);
expect(mdi.toString()).toBe('26:03:04');
expect(mdi.ariaLabel()).toBe('26 hours, 3 minutes, 4 seconds');
expect(mdi.ISO8601()).toBe('P0Y0M0DT26H3M4S');
});
test('Large durations: multiple days correctly mapped into hours', () => {
const mdi = new MediaDurationInfo();
const seconds = 3 * 86400 + 10 * 3600 + 15 * 60 + 9; // 3d 10:15:09 => 82:15:09
mdi.update(seconds);
expect(mdi.toString()).toBe('82:15:09');
expect(mdi.ariaLabel()).toBe('82 hours, 15 minutes, 9 seconds');
expect(mdi.ISO8601()).toBe('P0Y0M0DT82H15M9S');
});
test('Caching: toString and ariaLabel recompute only after update()', () => {
const mdi = new MediaDurationInfo(59);
const firstToString = mdi.toString();
const firstAria = mdi.ariaLabel();
expect(firstToString).toBe('0:59');
expect(firstAria).toBe('59 seconds');
// Call again to hit cached path
expect(mdi.toString()).toBe(firstToString);
expect(mdi.ariaLabel()).toBe(firstAria);
// Update and ensure cache invalidates
mdi.update(60);
expect(mdi.toString()).toBe('1:00');
expect(mdi.ariaLabel()).toBe('1 minutes');
});
test('Ignores invalid (non-positive integer or zero) updates, retaining previous value', () => {
const mdi = new MediaDurationInfo(10);
expect(mdi.toString()).toBe('0:10');
mdi.update(1.23);
expect(mdi.toString()).toBe('0:10');
mdi.update(-5);
expect(mdi.toString()).toBe('0:10');
mdi.update('x');
expect(mdi.toString()).toBe('0:10');
});
test('Boundary conditions around a minute and an hour', () => {
const mdi = new MediaDurationInfo();
mdi.update(59);
expect(mdi.toString()).toBe('0:59');
mdi.update(60);
expect(mdi.toString()).toBe('1:00');
mdi.update(3599);
expect(mdi.toString()).toBe('59:59');
mdi.update(3600);
expect(mdi.toString()).toBe('1:00:00');
});
// @todo: Revisit this behavior
test('Constructs without initial seconds', () => {
const mdi = new MediaDurationInfo();
expect(typeof mdi.toString()).toBe('function');
expect(mdi.ariaLabel()).toBe('');
expect(mdi.ISO8601()).toBe('P0Y0M0DTundefinedHundefinedMundefinedS');
});
});
});

View File

@ -1,102 +0,0 @@
import { UpNextLoaderView } from '../../../src/static/js/utils/classes/UpNextLoaderView';
// Minimal helpers mocks used by UpNextLoaderView
jest.mock('../../../src/static/js/utils/helpers/', () => ({
addClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.add(cn)),
removeClassname: jest.fn((el: any, cn: string) => el && el.classList && el.classList.remove(cn)),
translateString: (s: string) => s,
}));
const { addClassname, removeClassname } = jest.requireMock('../../../src/static/js/utils/helpers/');
const makeNextItem = () => ({
url: '/next-url',
title: 'Next title',
author_name: 'Jane Doe',
thumbnail_url: 'https://example.com/thumb.jpg',
});
describe('utils/classes', () => {
describe('UpNextLoaderView', () => {
test('html() builds structure with expected classes and content', () => {
const v = new UpNextLoaderView(makeNextItem());
const root = v.html();
expect(root).toBeInstanceOf(HTMLElement);
expect(root.querySelector('.up-next-loader-inner')).not.toBeNull();
expect(root.querySelector('.up-next-label')!.textContent).toBe('Up Next');
expect(root.querySelector('.next-media-title')!.textContent).toBe('Next title');
expect(root.querySelector('.next-media-author')!.textContent).toBe('Jane Doe');
// poster background
const poster = root.querySelector('.next-media-poster') as HTMLElement;
expect(poster.style.backgroundImage).toContain('thumb.jpg');
// go-next link points to next url
const link = root.querySelector('.go-next a') as HTMLAnchorElement;
expect(link.getAttribute('href')).toBe('/next-url');
});
test('setVideoJsPlayerElem marks player with vjs-mediacms-has-up-next-view class', () => {
const v = new UpNextLoaderView(makeNextItem());
const player = document.createElement('div');
v.setVideoJsPlayerElem(player);
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-has-up-next-view');
expect(v.vjsPlayerElem).toBe(player);
});
test('startTimer shows view, registers scroll, and navigates after 10s', () => {
const next = makeNextItem();
const v = new UpNextLoaderView(next);
const player = document.createElement('div');
v.setVideoJsPlayerElem(player);
v.startTimer();
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
});
test('cancelTimer clears timeout, stops scroll, and marks canceled', () => {
const v = new UpNextLoaderView(makeNextItem());
const player = document.createElement('div');
v.setVideoJsPlayerElem(player);
v.startTimer();
v.cancelTimer();
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
});
test('Cancel button click hides the view and cancels timer', () => {
const v = new UpNextLoaderView(makeNextItem());
const player = document.createElement('div');
v.setVideoJsPlayerElem(player);
v.startTimer();
const root = v.html();
const cancelBtn = root.querySelector('.up-next-cancel button') as HTMLButtonElement;
cancelBtn.click();
expect(addClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
});
test('showTimerView shows or starts timer based on flag', () => {
const v = new UpNextLoaderView(makeNextItem());
const player = document.createElement('div');
v.setVideoJsPlayerElem(player);
// beginTimer=false -> just show view
v.showTimerView(false);
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-up-next-hidden');
// beginTimer=true -> starts timer
v.showTimerView(true);
expect(removeClassname).toHaveBeenCalledWith(player, 'vjs-mediacms-canceled-next');
});
});
});

View File

@ -1,739 +0,0 @@
import { csrfToken, deleteRequest, getRequest, postRequest, putRequest } from '../../../src/static/js/utils/helpers';
const MEDIA_ID = 'MEDIA_ID';
const PLAYLIST_ID = 'PLAYLIST_ID';
window.history.pushState({}, '', `/?m=${MEDIA_ID}&pl=${PLAYLIST_ID}`);
import store from '../../../src/static/js/utils/stores/MediaPageStore';
import { sampleGlobalMediaCMS, sampleMediaCMSConfig } from '../../tests-constants';
jest.mock('../../../src/static/js/utils/classes/', () => ({
BrowserCache: jest.fn().mockImplementation(() => ({
get: jest.fn(),
set: jest.fn(),
})),
}));
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
BrowserEvents: jest.fn().mockImplementation(() => ({
doc: jest.fn(),
win: jest.fn(),
})),
csrfToken: jest.fn(),
deleteRequest: jest.fn(),
exportStore: jest.fn((store) => store),
getRequest: jest.fn(),
postRequest: jest.fn(),
putRequest: jest.fn(),
}));
describe('utils/store', () => {
describe('MediaPageStore', () => {
const handler = store.actions_handler.bind(store);
const onLoadedViewerPlaylistData = jest.fn();
const onLoadedPagePlaylistData = jest.fn();
const onLoadedViewerPlaylistError = jest.fn();
const onLoadedVideoData = jest.fn();
const onLoadedImageData = jest.fn();
const onLoadedMediaData = jest.fn();
const onLoadedMediaError = jest.fn();
const onCommentsLoad = jest.fn();
const onUsersLoad = jest.fn();
const onPlaylistsLoad = jest.fn();
const onLikedMediaFailedRequest = jest.fn();
const onLikedMedia = jest.fn();
const onDislikedMediaFailedRequest = jest.fn();
const onDislikedMedia = jest.fn();
const onReportedMedia = jest.fn();
const onPlaylistCreationCompleted = jest.fn();
const onPlaylistCreationFailed = jest.fn();
const onMediaPlaylistAdditionCompleted = jest.fn();
const onMediaPlaylistAdditionFailed = jest.fn();
const onMediaPlaylistRemovalCompleted = jest.fn();
const onMediaPlaylistRemovalFailed = jest.fn();
const onCopiedMediaLink = jest.fn();
const onCopiedEmbedMediaCode = jest.fn();
const onMediaDelete = jest.fn();
const onMediaDeleteFail = jest.fn();
const onCommentDeleteFail = jest.fn();
const onCommentDelete = jest.fn();
const onCommentSubmitFail = jest.fn();
const onCommentSubmit = jest.fn();
store.on('loaded_viewer_playlist_data', onLoadedViewerPlaylistData);
store.on('loaded_page_playlist_data', onLoadedPagePlaylistData);
store.on('loaded_viewer_playlist_error', onLoadedViewerPlaylistError);
store.on('loaded_video_data', onLoadedVideoData);
store.on('loaded_image_data', onLoadedImageData);
store.on('loaded_media_data', onLoadedMediaData);
store.on('loaded_media_error', onLoadedMediaError);
store.on('comments_load', onCommentsLoad);
store.on('users_load', onUsersLoad);
store.on('playlists_load', onPlaylistsLoad);
store.on('liked_media_failed_request', onLikedMediaFailedRequest);
store.on('liked_media', onLikedMedia);
store.on('disliked_media_failed_request', onDislikedMediaFailedRequest);
store.on('disliked_media', onDislikedMedia);
store.on('reported_media', onReportedMedia);
store.on('playlist_creation_completed', onPlaylistCreationCompleted);
store.on('playlist_creation_failed', onPlaylistCreationFailed);
store.on('media_playlist_addition_completed', onMediaPlaylistAdditionCompleted);
store.on('media_playlist_addition_failed', onMediaPlaylistAdditionFailed);
store.on('media_playlist_removal_completed', onMediaPlaylistRemovalCompleted);
store.on('media_playlist_removal_failed', onMediaPlaylistRemovalFailed);
store.on('copied_media_link', onCopiedMediaLink);
store.on('copied_embed_media_code', onCopiedEmbedMediaCode);
store.on('media_delete', onMediaDelete);
store.on('media_delete_fail', onMediaDeleteFail);
store.on('comment_delete_fail', onCommentDeleteFail);
store.on('comment_delete', onCommentDelete);
store.on('comment_submit_fail', onCommentSubmitFail);
store.on('comment_submit', onCommentSubmit);
beforeAll(() => {
(globalThis as any).window.MediaCMS = {
// mediaId: MEDIA_ID, // @note: It doesn't belong in 'sampleGlobalMediaCMS, but it could be used
features: sampleGlobalMediaCMS.features,
};
});
afterAll(() => {
delete (globalThis as any).window.MediaCMS;
});
afterEach(() => {
jest.clearAllMocks();
});
test('Validate initial values', () => {
expect(store.get('users')).toStrictEqual([]);
expect(store.get('playlists')).toStrictEqual([]);
expect(store.get('media-load-error-type')).toBe(null);
expect(store.get('media-load-error-message')).toBe(null);
expect(store.get('media-comments')).toStrictEqual([]);
expect(store.get('media-data')).toBe(null);
expect(store.get('media-id')).toBe(MEDIA_ID);
expect(store.get('media-url')).toBe('N/A');
expect(store.get('media-edit-subtitle-url')).toBe(null);
expect(store.get('media-likes')).toBe('N/A');
expect(store.get('media-dislikes')).toBe('N/A');
expect(store.get('media-summary')).toBe(null);
expect(store.get('media-categories')).toStrictEqual([]);
expect(store.get('media-tags')).toStrictEqual([]);
expect(store.get('media-type')).toBe(null);
expect(store.get('media-original-url')).toBe(null);
expect(store.get('media-thumbnail-url')).toBe(null);
expect(store.get('user-liked-media')).toBe(false);
expect(store.get('user-disliked-media')).toBe(false);
expect(store.get('media-author-thumbnail-url')).toBe(null);
expect(store.get('playlist-data')).toBe(null);
expect(store.get('playlist-id')).toBe(null);
expect(store.get('playlist-next-media-url')).toBe(null);
expect(store.get('playlist-previous-media-url')).toBe(null);
});
describe('Trigger and validate actions behavior', () => {
const MEDIA_DATA = {
add_subtitle_url: '/MEDIA_DATA_ADD_SUBTITLE_URL',
author_thumbnail: 'MEDIA_DATA_AUTHOR_THUMBNAIL',
categories_info: [
{ title: 'Art', url: '/search?c=Art' },
{ title: 'Documentary', url: '/search?c=Documentary' },
],
likes: 12,
dislikes: 4,
media_type: 'video',
original_media_url: 'MEDIA_DATA_ORIGINAL_MEDIA_URL',
reported_times: 0,
summary: 'MEDIA_DATA_SUMMARY',
tags_info: [
{ title: 'and', url: '/search?t=and' },
{ title: 'behavior', url: '/search?t=behavior' },
],
thumbnail_url: 'MEDIA_DATA_THUMBNAIL_URL',
url: '/MEDIA_DATA_URL',
};
const PLAYLIST_DATA = {
playlist_media: [
{ friendly_token: `${MEDIA_ID}_2`, url: '/PLAYLIT_MEDIA_URL_2' },
{ friendly_token: MEDIA_ID, url: '/PLAYLIT_MEDIA_URL_1' },
{ friendly_token: `${MEDIA_ID}_3`, url: '/PLAYLIT_MEDIA_URL_3' },
],
};
const USER_PLAYLIST_DATA = { playlist_media: [{ url: 'm=PLAYLIST_MEDIA_ID' }] };
test('Action type: "LOAD_MEDIA_DATA"', () => {
const MEDIA_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`;
const MEDIA_COMMENTS_API_URL = `${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`;
const PLAYLIST_API_URL = `${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`;
const USERS_API_URL = sampleMediaCMSConfig.api.users;
const USER_PLAYLISTS_API_URL = `${sampleMediaCMSConfig.api.user.playlists}${sampleMediaCMSConfig.member.username}`;
const USER_PLAYLIST_API_URL = `${sampleMediaCMSConfig.site.url}/${'PLAYLIST_API_URL'.replace(/^\//g, '')}`;
const MEDIA_COMMENTS_RESULTS = ['COMMENT_ID_1'];
const USERS_RESULTS = ['USER_ID_1'];
const USER_PLAYLISTS_RESULTS = [
{
url: `/${PLAYLIST_ID}`,
user: sampleMediaCMSConfig.member.username,
title: 'PLAYLIST_TITLE',
description: 'PLAYLIST_DECRIPTION',
add_date: 'PLAYLIST_ADD_DATE',
api_url: 'PLAYLIST_API_URL',
},
];
(getRequest as jest.Mock).mockImplementation((url, _cache, successCallback, _failCallback) => {
if (url === PLAYLIST_API_URL) {
return successCallback({ data: PLAYLIST_DATA });
}
if (url === USER_PLAYLIST_API_URL) {
return successCallback({ data: USER_PLAYLIST_DATA });
}
if (url === MEDIA_API_URL) {
return successCallback({ data: MEDIA_DATA });
}
if (url === USERS_API_URL) {
return successCallback({ data: { count: USERS_RESULTS.length, results: USERS_RESULTS } });
}
if (url === MEDIA_COMMENTS_API_URL) {
return successCallback({
data: { count: MEDIA_COMMENTS_RESULTS.length, results: MEDIA_COMMENTS_RESULTS },
});
}
if (url === USER_PLAYLISTS_API_URL) {
return successCallback({
data: { count: USER_PLAYLISTS_RESULTS.length, results: USER_PLAYLISTS_RESULTS },
});
}
});
handler({ type: 'LOAD_MEDIA_DATA' });
expect(getRequest).toHaveBeenCalledTimes(6);
expect(getRequest).toHaveBeenCalledWith(
PLAYLIST_API_URL,
false,
store.playlistDataResponse,
store.playlistDataErrorResponse
);
expect(getRequest).toHaveBeenCalledWith(
MEDIA_API_URL,
false,
store.dataResponse,
store.dataErrorResponse
);
expect(getRequest).toHaveBeenCalledWith(MEDIA_COMMENTS_API_URL, false, store.commentsResponse);
expect(getRequest).toHaveBeenCalledWith(USERS_API_URL, false, store.usersResponse);
expect(getRequest).toHaveBeenCalledWith(USER_PLAYLISTS_API_URL, false, store.playlistsResponse);
expect(getRequest).toHaveBeenCalledWith(USER_PLAYLIST_API_URL, false, expect.any(Function));
expect(onLoadedViewerPlaylistData).toHaveBeenCalledTimes(1);
expect(onLoadedPagePlaylistData).toHaveBeenCalledTimes(1);
expect(onLoadedViewerPlaylistError).toHaveBeenCalledTimes(0);
expect(onLoadedVideoData).toHaveBeenCalledTimes(1);
expect(onLoadedImageData).toHaveBeenCalledTimes(0);
expect(onLoadedMediaData).toHaveBeenCalledTimes(1);
expect(onLoadedMediaError).toHaveBeenCalledTimes(0);
expect(onCommentsLoad).toHaveBeenCalledTimes(1);
expect(onUsersLoad).toHaveBeenCalledTimes(1);
expect(onPlaylistsLoad).toHaveBeenCalledTimes(1);
expect(onLikedMediaFailedRequest).toHaveBeenCalledTimes(0);
expect(onLikedMedia).toHaveBeenCalledTimes(0);
expect(onDislikedMediaFailedRequest).toHaveBeenCalledTimes(0);
expect(onDislikedMedia).toHaveBeenCalledTimes(0);
expect(onReportedMedia).toHaveBeenCalledTimes(0);
expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(0);
expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(0);
expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(0);
expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(0);
expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(0);
expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(0);
expect(onCopiedMediaLink).toHaveBeenCalledTimes(0);
expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(0);
expect(onMediaDelete).toHaveBeenCalledTimes(0);
expect(onMediaDeleteFail).toHaveBeenCalledTimes(0);
expect(onCommentDeleteFail).toHaveBeenCalledTimes(0);
expect(onCommentDelete).toHaveBeenCalledTimes(0);
expect(onCommentSubmitFail).toHaveBeenCalledTimes(0);
expect(onCommentSubmit).toHaveBeenCalledTimes(0);
expect(store.isVideo()).toBeTruthy();
expect(store.get('users')).toStrictEqual(USERS_RESULTS);
expect(store.get('playlists')).toStrictEqual([
{
playlist_id: PLAYLIST_ID,
title: 'PLAYLIST_TITLE',
description: 'PLAYLIST_DECRIPTION',
add_date: 'PLAYLIST_ADD_DATE',
media_list: ['PLAYLIST_MEDIA_ID'],
},
]);
expect(store.get('media-load-error-type')).toBe(null);
expect(store.get('media-load-error-message')).toBe(null);
expect(store.get('media-comments')).toStrictEqual(MEDIA_COMMENTS_RESULTS);
expect(store.get('media-data')).toBe(MEDIA_DATA);
expect(store.get('media-id')).toBe(MEDIA_ID);
expect(store.get('media-url')).toBe(MEDIA_DATA.url);
expect(store.get('media-edit-subtitle-url')).toBe(MEDIA_DATA.add_subtitle_url);
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes);
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
expect(store.get('media-summary')).toBe(MEDIA_DATA.summary);
expect(store.get('media-categories')).toStrictEqual(MEDIA_DATA.categories_info);
expect(store.get('media-tags')).toStrictEqual(MEDIA_DATA.tags_info);
expect(store.get('media-type')).toBe(MEDIA_DATA.media_type);
expect(store.get('media-original-url')).toBe(MEDIA_DATA.original_media_url);
expect(store.get('media-thumbnail-url')).toBe(MEDIA_DATA.thumbnail_url);
expect(store.get('user-liked-media')).toBe(false);
expect(store.get('user-disliked-media')).toBe(false);
expect(store.get('media-author-thumbnail-url')).toBe(`/${MEDIA_DATA.author_thumbnail}`);
expect(store.get('playlist-data')).toBe(PLAYLIST_DATA);
expect(store.get('playlist-id')).toBe(PLAYLIST_ID);
expect(store.get('playlist-next-media-url')).toBe(
`${PLAYLIST_DATA.playlist_media[2].url}&pl=${PLAYLIST_ID}`
);
expect(store.get('playlist-previous-media-url')).toBe(
`${PLAYLIST_DATA.playlist_media[0].url}&pl=${PLAYLIST_ID}`
);
});
test('Action type: "LIKE_MEDIA"', () => {
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: {} })
);
handler({ type: 'LIKE_MEDIA' });
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`,
{ type: 'like' },
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.likeActionResponse,
expect.any(Function)
);
expect(onLikedMedia).toHaveBeenCalledTimes(1);
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1);
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
expect(store.get('user-liked-media')).toBe(true);
expect(store.get('user-disliked-media')).toBe(false);
});
test('Action type: "DISLIKE_MEDIA"', () => {
handler({ type: 'DISLIKE_MEDIA' });
expect(postRequest).toHaveBeenCalledTimes(0);
expect(onDislikedMedia).toHaveBeenCalledTimes(0);
expect(store.get('media-likes')).toBe(MEDIA_DATA.likes + 1);
expect(store.get('media-dislikes')).toBe(MEDIA_DATA.dislikes);
expect(store.get('user-liked-media')).toBe(true);
expect(store.get('user-disliked-media')).toBe(false);
});
test('Action type: "REPORT_MEDIA"', () => {
const REPORT_DESCRIPTION = 'REPORT_DESCRIPTION';
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: {} })
);
handler({ type: 'REPORT_MEDIA', reportDescription: REPORT_DESCRIPTION });
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/actions`,
{ type: 'report', extra_info: REPORT_DESCRIPTION },
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.reportActionResponse,
store.reportActionResponse
);
expect(onReportedMedia).toHaveBeenCalledTimes(1);
});
test('Action type: "COPY_SHARE_LINK"', () => {
document.execCommand = jest.fn(); // @deprecated
const inputElement = document.createElement('input');
handler({ type: 'COPY_SHARE_LINK', inputElement });
expect(onCopiedMediaLink).toHaveBeenCalledTimes(1);
expect(document.execCommand).toHaveBeenCalledWith('copy');
});
test('Action type: "COPY_EMBED_MEDIA_CODE"', () => {
document.execCommand = jest.fn(); // @deprecated
const inputElement = document.createElement('input');
handler({ type: 'COPY_EMBED_MEDIA_CODE', inputElement });
expect(onCopiedEmbedMediaCode).toHaveBeenCalledTimes(1);
expect(document.execCommand).toHaveBeenCalledWith('copy');
});
describe('Action type: "REMOVE_MEDIA"', () => {
const mockCSRFtoken = 'test-csrf-token';
beforeEach(() => {
// Mock the CSRF token
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
jest.useFakeTimers();
});
afterEach(() => {
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}`,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.removeMediaResponse,
store.removeMediaFail
);
// Fast-forward time
jest.advanceTimersByTime(100);
jest.useRealTimers();
});
test('Successful', () => {
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
);
handler({ type: 'REMOVE_MEDIA' });
expect(onMediaDelete).toHaveBeenCalledTimes(1);
expect(onMediaDelete).toHaveBeenCalledWith(MEDIA_ID);
});
test('Failed', () => {
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _configData, _sync, _successCallback, failCallback) => failCallback({})
);
handler({ type: 'REMOVE_MEDIA' });
expect(onMediaDeleteFail).toHaveBeenCalledTimes(1);
});
});
describe('Action type: "SUBMIT_COMMENT"', () => {
const COMMENT_TEXT = 'COMMENT_TEXT';
const COMMENT_UID = 'COMMENT_UID';
const mockCSRFtoken = 'test-csrf-token';
beforeEach(() => {
// Mock the CSRF token
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
jest.useFakeTimers();
});
afterEach(() => {
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments`,
{ text: COMMENT_TEXT },
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.submitCommentResponse,
store.submitCommentFail
);
// Fast-forward time
jest.advanceTimersByTime(100);
jest.useRealTimers();
});
test('Successful', () => {
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: { uid: COMMENT_UID }, status: 201 })
);
handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT });
expect(onCommentSubmit).toHaveBeenCalledTimes(1);
expect(onCommentSubmit).toHaveBeenCalledWith(COMMENT_UID);
});
test('Failed', () => {
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
);
handler({ type: 'SUBMIT_COMMENT', commentText: COMMENT_TEXT });
expect(onCommentSubmitFail).toHaveBeenCalledTimes(1);
});
});
describe('Action type: "DELETE_COMMENT"', () => {
const COMMENT_ID = 'COMMENT_ID';
const mockCSRFtoken = 'test-csrf-token';
beforeEach(() => {
// Mock the CSRF token
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
jest.useFakeTimers();
});
afterEach(() => {
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.media}/${MEDIA_ID}/comments/${COMMENT_ID}`,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.removeCommentResponse,
store.removeCommentFail
);
// Fast-forward time
jest.advanceTimersByTime(100);
jest.useRealTimers();
});
test('Successful', () => {
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _configData, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
);
handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID });
expect(onCommentDelete).toHaveBeenCalledTimes(1);
});
test('Failed', () => {
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _configData, _sync, _successCallback, failCallback) => failCallback()
);
handler({ type: 'DELETE_COMMENT', commentId: COMMENT_ID });
expect(onCommentDeleteFail).toHaveBeenCalledTimes(1);
});
});
describe('Action type: "CREATE_PLAYLIST"', () => {
const NEW_PLAYLIST_DATA = {
title: 'NEW_PLAYLIST_DATA_TITLE',
description: 'NEW_PLAYLIST_DATA_DESCRIPTION',
};
const mockCSRFtoken = 'test-csrf-token';
beforeEach(() => {
// Mock the CSRF token
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
});
afterEach(() => {
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
sampleMediaCMSConfig.api.playlists,
NEW_PLAYLIST_DATA,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
expect.any(Function),
expect.any(Function)
);
});
test('Successful', () => {
const NEW_PLAYLIST_RESPONSE_DATA = { uid: 'COMMENT_UID' };
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: NEW_PLAYLIST_RESPONSE_DATA, status: 201 })
);
handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA });
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
sampleMediaCMSConfig.api.playlists,
NEW_PLAYLIST_DATA,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
expect.any(Function),
expect.any(Function)
);
expect(onPlaylistCreationCompleted).toHaveBeenCalledTimes(1);
expect(onPlaylistCreationCompleted).toHaveBeenCalledWith(NEW_PLAYLIST_RESPONSE_DATA);
});
test('Failed', () => {
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
);
handler({ type: 'CREATE_PLAYLIST', playlist_data: NEW_PLAYLIST_DATA });
expect(onPlaylistCreationFailed).toHaveBeenCalledTimes(1);
});
});
describe('Action type: "ADD_MEDIA_TO_PLAYLIST"', () => {
const NEW_PLAYLIST_MEDIA_ID = 'NEW_PLAYLIST_MEDIA_ID';
const mockCSRFtoken = 'test-csrf-token';
beforeEach(() => {
// Mock the CSRF token
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
});
afterEach(() => {
// Verify postRequest was called with correct parameters
expect(putRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`,
{ type: 'add', media_friendly_token: NEW_PLAYLIST_MEDIA_ID },
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
expect.any(Function),
expect.any(Function)
);
});
test('Successful', () => {
// Mock put request
(putRequest as jest.Mock).mockImplementation(
(_url, _putData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: {} })
);
handler({
type: 'ADD_MEDIA_TO_PLAYLIST',
playlist_id: PLAYLIST_ID,
media_id: NEW_PLAYLIST_MEDIA_ID,
});
expect(onMediaPlaylistAdditionCompleted).toHaveBeenCalledTimes(1);
});
test('Failed', () => {
// Mock put request
(putRequest as jest.Mock).mockImplementation(
(_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback()
);
handler({
type: 'ADD_MEDIA_TO_PLAYLIST',
playlist_id: PLAYLIST_ID,
media_id: NEW_PLAYLIST_MEDIA_ID,
});
expect(onMediaPlaylistAdditionFailed).toHaveBeenCalledTimes(1);
});
});
describe('Action type: "REMOVE_MEDIA_FROM_PLAYLIST"', () => {
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
afterEach(() => {
// Verify postRequest was called with correct parameters
expect(putRequest).toHaveBeenCalledWith(
`${sampleMediaCMSConfig.api.playlists}/${PLAYLIST_ID}`,
{ type: 'remove', media_friendly_token: MEDIA_ID },
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
expect.any(Function),
expect.any(Function)
);
});
test('Successful', () => {
// Mock put request
(putRequest as jest.Mock).mockImplementation(
(_url, _putData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: {} })
);
handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID });
expect(onMediaPlaylistRemovalCompleted).toHaveBeenCalledTimes(1);
});
test('Failed', () => {
// Mock put request
(putRequest as jest.Mock).mockImplementation(
(_url, _putData, _configData, _sync, _successCallback, failCallback) => failCallback()
);
handler({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id: PLAYLIST_ID, media_id: MEDIA_ID });
expect(onMediaPlaylistRemovalFailed).toHaveBeenCalledTimes(1);
});
});
test('Action type: "APPEND_NEW_PLAYLIST"', () => {
const NEW_USER_PLAYLIST = {
add_date: 'PLAYLIST_ADD_DATE_2',
description: 'PLAYLIST_DECRIPTION_2',
media_list: ['PLAYLIST_MEDIA_ID'],
playlist_id: 'PLAYLIST_ID',
title: 'PLAYLIST_TITLE_2',
};
handler({ type: 'APPEND_NEW_PLAYLIST', playlist_data: NEW_USER_PLAYLIST });
expect(onPlaylistsLoad).toHaveBeenCalledTimes(1);
expect(store.get('playlists')).toStrictEqual([
{
add_date: 'PLAYLIST_ADD_DATE',
description: 'PLAYLIST_DECRIPTION',
media_list: ['PLAYLIST_MEDIA_ID'],
playlist_id: PLAYLIST_ID,
title: 'PLAYLIST_TITLE',
},
NEW_USER_PLAYLIST,
]);
});
});
});
});

View File

@ -1,162 +0,0 @@
import { BrowserCache } from '../../../src/static/js/utils/classes';
import store from '../../../src/static/js/utils/stores/PageStore';
import { sampleMediaCMSConfig } from '../../tests-constants';
jest.mock('../../../src/static/js/utils/classes/', () => ({
BrowserCache: jest.fn().mockImplementation(() => ({
get: (key: string) => (key === 'media-auto-play' ? false : undefined),
set: jest.fn(),
})),
}));
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
BrowserEvents: jest.fn().mockImplementation(() => ({
doc: jest.fn(),
win: jest.fn(),
})),
exportStore: jest.fn((store) => store),
}));
describe('utils/store', () => {
afterAll(() => {
jest.clearAllMocks();
});
describe('PageStore', () => {
const handler = store.actions_handler.bind(store);
const onInit = jest.fn();
const onToggleAutoPlay = jest.fn();
const onAddNotification = jest.fn();
store.on('page_init', onInit);
store.on('switched_media_auto_play', onToggleAutoPlay);
store.on('added_notification', onAddNotification);
test('Validate initial values', () => {
// BrowserCache mock
expect(store.get('browser-cache').get('media-auto-play')).toBe(false);
expect(store.get('browser-cache').get('ANY')).toBe(undefined);
// Autoplay media files
expect(store.get('media-auto-play')).toBe(false);
// Configuration
expect(store.get('config-contents')).toStrictEqual(sampleMediaCMSConfig.contents);
expect(store.get('config-enabled')).toStrictEqual(sampleMediaCMSConfig.enabled);
expect(store.get('config-media-item')).toStrictEqual(sampleMediaCMSConfig.media.item);
expect(store.get('config-options')).toStrictEqual(sampleMediaCMSConfig.options);
expect(store.get('config-site')).toStrictEqual(sampleMediaCMSConfig.site);
// Playlists API path
expect(store.get('api-playlists')).toStrictEqual(sampleMediaCMSConfig.api.playlists);
// Notifications
expect(store.get('notifications')).toStrictEqual([]);
expect(store.get('notifications-size')).toBe(0);
expect(store.get('current-page')).toBe(undefined);
});
test('Trigger and validate browser events behavior', () => {
const docVisChange = jest.fn();
const winScroll = jest.fn();
const winResize = jest.fn();
store.on('document_visibility_change', docVisChange);
store.on('window_scroll', winScroll);
store.on('window_resize', winResize);
store.onDocumentVisibilityChange();
store.onWindowScroll();
store.onWindowResize();
expect(docVisChange).toHaveBeenCalled();
expect(winScroll).toHaveBeenCalled();
expect(winResize).toHaveBeenCalledTimes(1);
});
describe('Trigger and validate actions behavior', () => {
test('Action type: "INIT_PAGE"', () => {
handler({ type: 'INIT_PAGE', page: 'home' });
expect(onInit).toHaveBeenCalledTimes(1);
expect(store.get('current-page')).toBe('home');
handler({ type: 'INIT_PAGE', page: 'about' });
expect(onInit).toHaveBeenCalledTimes(2);
expect(store.get('current-page')).toBe('about');
handler({ type: 'INIT_PAGE', page: 'profile' });
expect(onInit).toHaveBeenCalledTimes(3);
expect(store.get('current-page')).toBe('profile');
expect(onInit).toHaveBeenCalledWith();
expect(onToggleAutoPlay).toHaveBeenCalledTimes(0);
expect(onAddNotification).toHaveBeenCalledTimes(0);
});
test('Action type: "TOGGLE_AUTO_PLAY"', () => {
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
const browserCacheSetSpy = browserCacheInstance.set;
const initialValue = store.get('media-auto-play');
handler({ type: 'TOGGLE_AUTO_PLAY' });
expect(onToggleAutoPlay).toHaveBeenCalledWith();
expect(onToggleAutoPlay).toHaveBeenCalledTimes(1);
expect(store.get('media-auto-play')).toBe(!initialValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('media-auto-play', !initialValue);
browserCacheSetSpy.mockRestore();
});
test('Action type: "ADD_NOTIFICATION"', () => {
const notificationMsg1 = 'NOTIFICATION_MSG_1';
const notificationMsg2 = 'NOTIFICATION_MSG_2';
const invalidNotification = 44;
// Add notification
handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg1 });
expect(onAddNotification).toHaveBeenCalledWith();
expect(onAddNotification).toHaveBeenCalledTimes(1);
expect(store.get('notifications-size')).toBe(1);
const currentNotifications = store.get('notifications');
expect(currentNotifications.length).toBe(1);
expect(typeof currentNotifications[0][0]).toBe('string');
expect(currentNotifications[0][1]).toBe(notificationMsg1);
expect(store.get('notifications-size')).toBe(0);
expect(store.get('notifications')).toStrictEqual([]);
// Add another notification
handler({ type: 'ADD_NOTIFICATION', notification: notificationMsg2 });
expect(onAddNotification).toHaveBeenCalledWith();
expect(onAddNotification).toHaveBeenCalledTimes(2);
expect(store.get('notifications-size')).toBe(1);
expect(store.get('notifications')[0][1]).toBe(notificationMsg2);
expect(store.get('notifications-size')).toBe(0);
expect(store.get('notifications')).toStrictEqual([]);
// Add invalid notification
handler({ type: 'ADD_NOTIFICATION', notification: invalidNotification });
expect(onAddNotification).toHaveBeenCalledWith();
expect(onAddNotification).toHaveBeenCalledTimes(3);
expect(store.get('notifications-size')).toBe(0);
expect(store.get('notifications')).toStrictEqual([]);
});
});
});
});

View File

@ -1,390 +0,0 @@
import {
publishedOnDate,
getRequest,
postRequest,
deleteRequest,
csrfToken,
} from '../../../src/static/js/utils/helpers';
import store from '../../../src/static/js/utils/stores/PlaylistPageStore';
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
publishedOnDate: jest.fn(),
exportStore: jest.fn((store) => store),
getRequest: jest.fn(),
postRequest: jest.fn(),
deleteRequest: jest.fn(),
csrfToken: jest.fn(),
}));
describe('utils/store', () => {
beforeAll(() => {
(globalThis as any).window.MediaCMS = { playlistId: null };
});
afterAll(() => {
delete (globalThis as any).window.MediaCMS;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('PlaylistPageStore', () => {
const handler = store.actions_handler.bind(store);
const onLoadedPlaylistData = jest.fn();
const onLoadedPlaylistEerror = jest.fn();
const onLoadedMediaError = jest.fn();
const onPlaylistUpdateCompleted = jest.fn();
const onPlaylistUpdateFailed = jest.fn();
const onPlaylistRemovalCompleted = jest.fn();
const onPlaylistRemovalFailed = jest.fn();
const onSavedUpdated = jest.fn();
const onReorderedMediaInPlaylist = jest.fn();
const onRemovedMediaFromPlaylist = jest.fn();
store.on('loaded_playlist_data', onLoadedPlaylistData);
store.on('loaded_playlist_error', onLoadedPlaylistEerror);
store.on('loaded_media_error', onLoadedMediaError); // @todo: It doesn't get called
store.on('playlist_update_completed', onPlaylistUpdateCompleted);
store.on('playlist_update_failed', onPlaylistUpdateFailed);
store.on('playlist_removal_completed', onPlaylistRemovalCompleted);
store.on('playlist_removal_failed', onPlaylistRemovalFailed);
store.on('saved-updated', onSavedUpdated);
store.on('reordered_media_in_playlist', onReorderedMediaInPlaylist);
store.on('removed_media_from_playlist', onRemovedMediaFromPlaylist);
test('Validate initial values', () => {
expect(store.get('INVALID_TYPE')).toBe(null);
expect(store.get('playlistId')).toBe(null);
expect(store.get('logged-in-user-playlist')).toBe(false);
expect(store.get('playlist-media')).toStrictEqual([]);
expect(store.get('visibility')).toBe('public');
expect(store.get('visibility-icon')).toBe(null);
// // expect(store.get('total-items')).toBe(0); // @todo: It throws error
expect(store.get('views-count')).toBe('N/A');
expect(store.get('title')).toBe(null);
expect(store.get('edit-link')).toBe('#');
expect(store.get('thumb')).toBe(null);
expect(store.get('description')).toBe(null);
expect(store.get('author-username')).toBe(null);
expect(store.get('author-name')).toBe(null);
expect(store.get('author-link')).toBe(null);
expect(store.get('author-thumb')).toBe(null);
expect(store.get('saved-playlist')).toBe(false);
expect(store.get('date-label')).toBe(null);
});
describe('Trigger and validate actions behavior', () => {
test('Action type: "LOAD_PLAYLIST_DATA" - failed', () => {
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
const loadDataSpy = jest.spyOn(store, 'loadData');
handler({ type: 'LOAD_PLAYLIST_DATA' });
expect(loadDataSpy).toHaveBeenCalledTimes(1);
expect(loadDataSpy).toHaveReturnedWith(false);
expect(warnSpy).toHaveBeenCalledTimes(1);
expect(warnSpy).toHaveBeenCalledWith('Invalid playlist id:', '');
expect(store.get('playlistId')).toBe(null);
loadDataSpy.mockRestore();
warnSpy.mockRestore();
});
test('Action type: "LOAD_PLAYLIST_DATA" - completed successful', () => {
const playlistId = 'PLAYLIST_ID_1';
window.history.pushState({}, '', `/playlists/${playlistId}`);
// Mock get request
const mockGetRequestResponse = {
data: {
add_date: Date.now(),
description: 'DESCRIPTION',
playlist_media: [],
title: 'TITLE',
user: 'USER',
user_thumbnail_url: 'USER_THUMB_URL',
},
};
(getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) =>
successCallback(mockGetRequestResponse)
);
const loadDataSpy = jest.spyOn(store, 'loadData');
const dataResponseSpy = jest.spyOn(store, 'dataResponse');
handler({ type: 'LOAD_PLAYLIST_DATA' });
expect(store.get('playlistId')).toBe(playlistId);
expect(store.get('author-name')).toBe(mockGetRequestResponse.data.user);
expect(store.get('author-link')).toBe(`/user/${mockGetRequestResponse.data.user}`);
expect(store.get('author-thumb')).toBe(`/${mockGetRequestResponse.data.user_thumbnail_url}`);
expect(store.get('date-label')).toBe('Created on undefined');
expect(publishedOnDate).toHaveBeenCalledWith(new Date(mockGetRequestResponse.data.add_date), 3);
expect(loadDataSpy).toHaveBeenCalledTimes(1);
expect(loadDataSpy).toHaveReturnedWith(undefined);
expect(dataResponseSpy).toHaveBeenCalledTimes(1);
expect(dataResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse);
// Verify getRequest was called with correct parameters
expect(getRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
false,
store.dataResponse,
store.dataErrorResponse
);
expect(onLoadedPlaylistData).toHaveBeenCalledTimes(1);
expect(onLoadedPlaylistData).toHaveBeenCalledWith();
loadDataSpy.mockRestore();
dataResponseSpy.mockRestore();
});
test('Action type: "LOAD_PLAYLIST_DATA" - completed with error', () => {
const playlistId = 'PLAYLIST_ID_2';
window.history.pushState({}, '', `/playlists/${playlistId}`);
// Mock get request
const mockGetRequestResponse = { type: 'private' };
(getRequest as jest.Mock).mockImplementation((_url, _cache, _successCallback, failCallback) =>
failCallback(mockGetRequestResponse)
);
const loadDataSpy = jest.spyOn(store, 'loadData');
const dataErrorResponseSpy = jest.spyOn(store, 'dataErrorResponse');
handler({ type: 'LOAD_PLAYLIST_DATA' });
expect(store.get('playlistId')).toBe(playlistId);
expect(loadDataSpy).toHaveBeenCalledTimes(1);
expect(loadDataSpy).toHaveReturnedWith(undefined);
expect(dataErrorResponseSpy).toHaveBeenCalledTimes(1);
expect(dataErrorResponseSpy).toHaveBeenCalledWith(mockGetRequestResponse);
// Verify getRequest was called with correct parameters
expect(getRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
false,
store.dataResponse,
store.dataErrorResponse
);
expect(onLoadedPlaylistEerror).toHaveBeenCalledTimes(1);
expect(onLoadedPlaylistEerror).toHaveBeenCalledWith();
loadDataSpy.mockRestore();
dataErrorResponseSpy.mockRestore();
});
test('Action type: "TOGGLE_SAVE"', () => {
const initialValue = store.get('saved-playlist');
handler({ type: 'TOGGLE_SAVE' });
expect(onSavedUpdated).toHaveBeenCalledTimes(1);
expect(onSavedUpdated).toHaveBeenCalledWith();
expect(store.get('saved-playlist')).toBe(!initialValue);
});
test('Action type: "UPDATE_PLAYLIST" - failed', () => {
// Mock (updated) playlist data
const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' };
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, _successCallback, failCallback) => failCallback()
);
const initialStoreData = {
title: store.get('title'),
description: store.get('description'),
};
expect(store.get('title')).toBe(initialStoreData.title);
expect(store.get('description')).toBe(initialStoreData.description);
handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData });
expect(store.get('title')).toBe(initialStoreData.title);
expect(store.get('description')).toBe(initialStoreData.description);
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
mockPlaylistData,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.onPlaylistUpdateCompleted,
store.onPlaylistUpdateFailed
);
expect(onPlaylistUpdateFailed).toHaveBeenCalledWith();
});
test('Action type: "UPDATE_PLAYLIST" - successful', () => {
// Mock (updated) playlist data
const mockPlaylistData = { title: 'PLAYLIST_TITLE', description: 'PLAYLIST_DESCRIPTION' };
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock post request
(postRequest as jest.Mock).mockImplementation(
(_url, _postData, _configData, _sync, successCallback, _failCallback) =>
successCallback({ data: mockPlaylistData })
);
const initialStoreData = {
title: store.get('title'),
description: store.get('description'),
};
expect(store.get('title')).toBe(initialStoreData.title);
expect(store.get('description')).toBe(initialStoreData.description);
handler({ type: 'UPDATE_PLAYLIST', playlist_data: mockPlaylistData });
expect(store.get('title')).toBe(mockPlaylistData.title);
expect(store.get('description')).toBe(mockPlaylistData.description);
// Verify postRequest was called with correct parameters
expect(postRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
mockPlaylistData,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.onPlaylistUpdateCompleted,
store.onPlaylistUpdateFailed
);
expect(onPlaylistUpdateCompleted).toHaveBeenCalledWith(mockPlaylistData);
});
test('Action type: "REMOVE_PLAYLIST" - failed', () => {
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _config, _sync, _successCallback, failCallback) => failCallback()
);
handler({ type: 'REMOVE_PLAYLIST' });
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.onPlaylistRemovalCompleted,
store.onPlaylistRemovalFailed
);
expect(onPlaylistRemovalFailed).toHaveBeenCalledWith();
});
test('Action type: "REMOVE_PLAYLIST" - completed successful', () => {
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock delete request
const deleteRequestResponse = { status: 204 };
(deleteRequest as jest.Mock).mockImplementation(
(_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse)
);
handler({ type: 'REMOVE_PLAYLIST' });
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.onPlaylistRemovalCompleted,
store.onPlaylistRemovalFailed
);
expect(onPlaylistRemovalCompleted).toHaveBeenCalledWith(deleteRequestResponse);
});
test('Action type: "REMOVE_PLAYLIST" - completed with invalid status code', () => {
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock delete request
const deleteRequestResponse = { status: 403 };
(deleteRequest as jest.Mock).mockImplementation(
(_url, _config, _sync, successCallback, _failCallback) => successCallback(deleteRequestResponse)
);
handler({ type: 'REMOVE_PLAYLIST' });
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
store.playlistAPIUrl,
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.onPlaylistRemovalCompleted,
store.onPlaylistRemovalFailed
);
expect(onPlaylistRemovalFailed).toHaveBeenCalledWith();
});
test('Action type: "PLAYLIST_MEDIA_REORDERED"', () => {
// Mock playlist media data
const mockPlaylistMedia = [
{ thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' },
{ thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' },
];
handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia });
expect(onReorderedMediaInPlaylist).toHaveBeenCalledWith();
expect(store.get('playlist-media')).toStrictEqual(mockPlaylistMedia);
expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url);
expect(store.get('total-items')).toBe(mockPlaylistMedia.length);
});
test('Action type: "MEDIA_REMOVED_FROM_PLAYLIST"', () => {
// Mock playlist media data
const mockPlaylistMedia = [
{ thumbnail_url: 'THUMB_URL_1', url: '?id=MEDIA_ID_1' },
{ thumbnail_url: 'THUMB_URL_2', url: '?id=MEDIA_ID_2' },
];
handler({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: mockPlaylistMedia });
handler({ type: 'MEDIA_REMOVED_FROM_PLAYLIST', media_id: 'MEDIA_ID_2' });
expect(store.get('playlist-media')).toStrictEqual([mockPlaylistMedia[0]]);
expect(store.get('thumb')).toBe(mockPlaylistMedia[0].thumbnail_url);
expect(store.get('total-items')).toBe(mockPlaylistMedia.length - 1);
});
});
});
});

View File

@ -1,83 +0,0 @@
import { BrowserCache } from '../../../src/static/js/utils/classes/';
import store from '../../../src/static/js/utils/stores/PlaylistViewStore';
jest.mock('../../../src/static/js/utils/classes/', () => ({
BrowserCache: jest.fn().mockImplementation(() => ({
get: jest.fn(),
set: jest.fn(),
})),
}));
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
BrowserEvents: jest.fn().mockImplementation(() => ({
doc: jest.fn(),
win: jest.fn(),
})),
exportStore: jest.fn((store) => store),
}));
describe('utils/store', () => {
describe('PlaylistViewStore', () => {
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
const browserCacheSetSpy = browserCacheInstance.set;
const handler = store.actions_handler.bind(store);
const onLoopRepeatUpdated = jest.fn();
const onShuffleUpdated = jest.fn();
const onSavedUpdated = jest.fn();
store.on('loop-repeat-updated', onLoopRepeatUpdated);
store.on('shuffle-updated', onShuffleUpdated);
store.on('saved-updated', onSavedUpdated);
test('Validate initial values', () => {
expect(store.get('INVALID_TYPE')).toBe(null);
expect(store.get('logged-in-user-playlist')).toBe(false);
expect(store.get('enabled-loop')).toBe(undefined);
expect(store.get('enabled-shuffle')).toBe(undefined);
expect(store.get('saved-playlist')).toBe(false);
});
describe('Trigger and validate actions behavior', () => {
// @todo: Revisit the behavior of this action
test('Action type: "TOGGLE_LOOP"', () => {
handler({ type: 'TOGGLE_LOOP' });
expect(onLoopRepeatUpdated).toHaveBeenCalledTimes(1);
expect(onLoopRepeatUpdated).toHaveBeenCalledWith();
expect(store.get('enabled-loop')).toBe(undefined);
expect(browserCacheSetSpy).toHaveBeenCalledWith('loopPlaylist[null]', true);
});
// @todo: Revisit the behavior of this action
test('Action type: "TOGGLE_SHUFFLE"', () => {
handler({ type: 'TOGGLE_SHUFFLE' });
expect(onShuffleUpdated).toHaveBeenCalledTimes(1);
expect(onShuffleUpdated).toHaveBeenCalledWith();
expect(store.get('enabled-shuffle')).toBe(undefined);
expect(browserCacheSetSpy).toHaveBeenCalledWith('shufflePlaylist[null]', true);
});
test('Action type: "TOGGLE_SAVE"', () => {
const initialValue = store.get('saved-playlist');
handler({ type: 'TOGGLE_SAVE' });
expect(onSavedUpdated).toHaveBeenCalledTimes(1);
expect(onSavedUpdated).toHaveBeenCalledWith();
expect(store.get('saved-playlist')).toBe(!initialValue);
});
});
});
});

View File

@ -1,168 +0,0 @@
import { getRequest, deleteRequest, csrfToken } from '../../../src/static/js/utils/helpers';
import store from '../../../src/static/js/utils/stores/ProfilePageStore';
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => ({
...jest.requireActual('../../tests-constants').sampleMediaCMSConfig,
api: { ...jest.requireActual('../../tests-constants').sampleMediaCMSConfig.api, users: '' },
})),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
getRequest: jest.fn(),
deleteRequest: jest.fn(),
csrfToken: jest.fn(),
exportStore: jest.fn((store) => store),
}));
describe('utils/store', () => {
const mockAuthorData = { username: 'testuser', name: 'Test User' };
beforeAll(() => {
(globalThis as any).window.MediaCMS = { profileId: mockAuthorData.username };
});
afterAll(() => {
delete (globalThis as any).window.MediaCMS;
});
afterEach(() => {
jest.clearAllMocks();
});
describe('ProfilePageStore', () => {
const handler = store.actions_handler.bind(store);
const onProfileDelete = jest.fn();
const onProfileDeleteFail = jest.fn();
const onLoadAuthorData = jest.fn();
beforeAll(() => {
store.on('profile_delete', onProfileDelete);
store.on('profile_delete_fail', onProfileDeleteFail);
store.on('load-author-data', onLoadAuthorData);
});
beforeEach(() => {
// Reset store state
store.authorData = null;
store.removingProfile = false;
store.authorQuery = undefined;
});
describe('Trigger and validate actions behavior', () => {
test('Action type: "REMOVE_PROFILE" - successful deletion', async () => {
// Set up author data
store.authorData = mockAuthorData;
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _config, _sync, successCallback, _failCallback) => successCallback({ status: 204 })
);
handler({ type: 'REMOVE_PROFILE' });
// Verify deleteRequest was called with correct parameters
expect(deleteRequest).toHaveBeenCalledWith(
'/testuser', // API URL constructed from config + username
{ headers: { 'X-CSRFToken': mockCSRFtoken } },
false,
store.removeProfileResponse,
store.removeProfileFail
);
// Verify event was emitted
expect(onProfileDelete).toHaveBeenCalledWith(mockAuthorData.username);
expect(onProfileDelete).toHaveBeenCalledTimes(1);
});
test('Action type: "REMOVE_PROFILE" - deletion failure', async () => {
// Set up author data
store.authorData = mockAuthorData;
// Mock the CSRF token
const mockCSRFtoken = 'test-csrf-token';
(csrfToken as jest.Mock).mockReturnValue(mockCSRFtoken);
// Mock delete request
(deleteRequest as jest.Mock).mockImplementation(
(_url, _config, _sync, _successCallback, failCallback) => failCallback.call(store)
);
handler({ type: 'REMOVE_PROFILE' });
// Wait for the setTimeout in removeProfileFail
await new Promise((resolve) => setTimeout(resolve, 150));
// Verify event was emitted
expect(onProfileDeleteFail).toHaveBeenCalledWith(mockAuthorData.username);
expect(onProfileDeleteFail).toHaveBeenCalledTimes(1);
});
test('Action type: "REMOVE_PROFILE" - prevents duplicate calls while removing', () => {
// Set up author data
store.authorData = mockAuthorData;
handler({ type: 'REMOVE_PROFILE' });
expect(deleteRequest).toHaveBeenCalledTimes(1);
store.removingProfile = true;
handler({ type: 'REMOVE_PROFILE' });
expect(deleteRequest).toHaveBeenCalledTimes(1);
store.removingProfile = false;
handler({ type: 'REMOVE_PROFILE' });
expect(deleteRequest).toHaveBeenCalledTimes(2);
});
test('Action type: "LOAD_AUTHOR_DATA"', async () => {
(getRequest as jest.Mock).mockImplementation((_url, _cache, successCallback, _failCallback) =>
successCallback({ data: mockAuthorData })
);
handler({ type: 'LOAD_AUTHOR_DATA' });
// Verify getRequest was called with correct parameters
expect(getRequest).toHaveBeenCalledWith('/testuser', false, store.onDataLoad, store.onDataLoadFail);
// Verify event was emitted
expect(onLoadAuthorData).toHaveBeenCalledTimes(1);
// Verify author data was processed correctly
expect(store.get('author-data')).toStrictEqual(mockAuthorData);
});
});
describe('Getter methods', () => {
test('Validate initial values', () => {
expect(store.get('INVALID_TYPE')).toBe(undefined);
expect(store.get('author-data')).toBe(null);
expect(store.get('author-query')).toBe(null);
});
test('get("author-data") returns authorData', () => {
store.authorData = mockAuthorData;
expect(store.get('author-data')).toBe(mockAuthorData);
});
test('get("author-query") - without "aq" parameter in URL', () => {
window.history.pushState({}, '', '/path');
expect(store.get('author-query')).toBe(null);
});
test('get("author-query") - with "aq" parameter in URL', () => {
window.history.pushState({}, '', '/path?aq=AUTHOR_QUERY');
expect(store.get('author-query')).toBe('AUTHOR_QUERY');
});
test('get("author-query") - empty search string', () => {
window.history.pushState({}, '', '/path?aq');
expect(store.get('author-query')).toBe(null);
});
});
});
});

View File

@ -1,64 +0,0 @@
const urlParams = { q: 'search_query', c: 'category_1', t: 'tag_1' };
window.history.pushState({}, '', `/?q=${urlParams.q}&c=${urlParams.c}&t=${urlParams.t}`);
import store from '../../../src/static/js/utils/stores/SearchFieldStore';
import { getRequest } from '../../../src/static/js/utils/helpers';
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
exportStore: jest.fn((store) => store),
getRequest: jest.fn(),
}));
describe('utils/store', () => {
afterAll(() => {
jest.clearAllMocks();
});
describe('SearchFieldStore', () => {
const handler = store.actions_handler.bind(store);
const onLoadPredictions = jest.fn();
store.on('load_predictions', onLoadPredictions);
test('Validate initial values based on URL params', async () => {
expect(store.get('INVALID_TYPE')).toBe(null);
expect(store.get('search-query')).toBe(urlParams.q);
expect(store.get('search-categories')).toBe(urlParams.c);
expect(store.get('search-tags')).toBe(urlParams.t);
});
test('Action type: "Action type: "TOGGLE_VIEWER_MODE"', async () => {
const predictionsQuery_1 = 'predictions_query_1';
const predictionsQuery_2 = 'predictions_query_2';
const response_1 = { data: [{ title: 'Prediction 1' }, { title: 'Prediction 2' }] };
const response_2 = { data: [{ title: 'Prediction 3' }, { title: 'Prediction 4' }] };
(getRequest as jest.Mock)
.mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_1))
.mockImplementationOnce((_url, _cache, successCallback, _failCallback) => successCallback(response_2));
handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_1 });
handler({ type: 'REQUEST_PREDICTIONS', query: predictionsQuery_2 });
expect(onLoadPredictions).toHaveBeenCalledTimes(2);
expect(onLoadPredictions).toHaveBeenNthCalledWith(
1,
predictionsQuery_1,
response_1.data.map(({ title }) => title)
);
expect(onLoadPredictions).toHaveBeenNthCalledWith(
2,
predictionsQuery_2,
response_2.data.map(({ title }) => title)
);
});
});
});

View File

@ -1,147 +0,0 @@
import { BrowserCache } from '../../../src/static/js/utils/classes/';
import store from '../../../src/static/js/utils/stores/VideoViewerStore';
jest.mock('../../../src/static/js/utils/classes/', () => ({
BrowserCache: jest.fn().mockImplementation(() => ({
get: (key: string) => {
let result: any = undefined;
switch (key) {
case 'player-volume':
result = 0.6;
break;
case 'player-sound-muted':
result = false;
break;
case 'in-theater-mode':
result = true;
break;
case 'video-quality':
result = 720;
break;
case 'video-playback-speed':
result = 2;
break;
}
return result;
},
set: jest.fn(),
})),
}));
jest.mock('../../../src/static/js/utils/settings/config', () => ({
config: jest.fn(() => jest.requireActual('../../tests-constants').sampleMediaCMSConfig),
}));
jest.mock('../../../src/static/js/utils/helpers', () => ({
BrowserEvents: jest.fn().mockImplementation(() => ({
doc: jest.fn(),
win: jest.fn(),
})),
exportStore: jest.fn((store) => store),
}));
describe('utils/store', () => {
describe('VideoViewerStore', () => {
const browserCacheInstance = (BrowserCache as jest.Mock).mock.results[0].value;
const browserCacheSetSpy = browserCacheInstance.set;
const handler = store.actions_handler.bind(store);
const onChangedViewerMode = jest.fn();
const onChangedPlayerVolume = jest.fn();
const onChangedPlayerSoundMuted = jest.fn();
const onChangedVideoQuality = jest.fn();
const onChangedVideoPlaybackSpeed = jest.fn();
store.on('changed_viewer_mode', onChangedViewerMode);
store.on('changed_player_volume', onChangedPlayerVolume);
store.on('changed_player_sound_muted', onChangedPlayerSoundMuted);
store.on('changed_video_quality', onChangedVideoQuality);
store.on('changed_video_playback_speed', onChangedVideoPlaybackSpeed);
test('Validate initial values', () => {
expect(store.get('player-volume')).toBe(0.6);
expect(store.get('player-sound-muted')).toBe(false);
expect(store.get('in-theater-mode')).toBe(true);
expect(store.get('video-data')).toBe(undefined); // @todo: Revisit this behavior
expect(store.get('video-quality')).toBe(720);
expect(store.get('video-playback-speed')).toBe(2);
});
describe('Trigger and validate actions behavior', () => {
test('Action type: "TOGGLE_VIEWER_MODE"', () => {
const initialValue = store.get('in-theater-mode');
handler({ type: 'TOGGLE_VIEWER_MODE' });
expect(onChangedViewerMode).toHaveBeenCalledWith();
expect(onChangedViewerMode).toHaveBeenCalledTimes(1);
expect(store.get('in-theater-mode')).toBe(!initialValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', !initialValue);
});
test('Action type: "SET_VIEWER_MODE"', () => {
const initialValue = store.get('in-theater-mode');
const newValue = !initialValue;
handler({ type: 'SET_VIEWER_MODE', inTheaterMode: newValue });
expect(onChangedViewerMode).toHaveBeenCalledWith();
expect(onChangedViewerMode).toHaveBeenCalledTimes(2); // The first time called by 'TOGGLE_VIEWER_MODE' action.
expect(store.get('in-theater-mode')).toBe(newValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('in-theater-mode', newValue);
});
test('Action type: "SET_PLAYER_VOLUME"', () => {
const newValue = 0.3;
handler({ type: 'SET_PLAYER_VOLUME', playerVolume: newValue });
expect(onChangedPlayerVolume).toHaveBeenCalledWith();
expect(onChangedPlayerVolume).toHaveBeenCalledTimes(1);
expect(store.get('player-volume')).toBe(newValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('player-volume', newValue);
});
test('Action type: "SET_PLAYER_SOUND_MUTED"', () => {
const initialValue = store.get('player-sound-muted');
const newValue = !initialValue;
handler({ type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted: newValue });
expect(onChangedPlayerSoundMuted).toHaveBeenCalledWith();
expect(onChangedPlayerSoundMuted).toHaveBeenCalledTimes(1);
expect(store.get('player-sound-muted')).toBe(newValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('player-sound-muted', newValue);
});
test('Action type: "SET_VIDEO_QUALITY"', () => {
const newValue = 1080;
handler({ type: 'SET_VIDEO_QUALITY', quality: newValue });
expect(onChangedVideoQuality).toHaveBeenCalledWith();
expect(onChangedVideoQuality).toHaveBeenCalledTimes(1);
expect(store.get('video-quality')).toBe(newValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('video-quality', newValue);
});
test('Action type: "SET_VIDEO_PLAYBACK_SPEED"', () => {
const newValue = 1.5;
handler({ type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed: newValue });
expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledWith();
expect(onChangedVideoPlaybackSpeed).toHaveBeenCalledTimes(1);
expect(store.get('video-playback-speed')).toBe(newValue);
expect(browserCacheSetSpy).toHaveBeenCalledWith('video-playback-speed', newValue);
});
});
});
});

View File

@ -7,11 +7,6 @@
resolved "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz" resolved "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.30.tgz"
integrity sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg== integrity sha512-9CnlMCI0LmCIq0olalQqdWrJHPzm0/tw3gzOA9zJSgvFX7Xau3D24mAGa4BtwxwY69nsuJW6kQqqCzf/mEcQgg==
"@adobe/css-tools@^4.0.1":
version "4.4.4"
resolved "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz"
integrity sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==
"@asamuzakjp/css-color@^3.2.0": "@asamuzakjp/css-color@^3.2.0":
version "3.2.0" version "3.2.0"
resolved "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz" resolved "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz"
@ -50,7 +45,7 @@
resolved "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz" resolved "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz"
integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q== integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==
"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1": "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.27.1":
version "7.27.1" version "7.27.1"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz"
integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== integrity sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==
@ -1007,11 +1002,6 @@
"@babel/plugin-transform-modules-commonjs" "^7.27.1" "@babel/plugin-transform-modules-commonjs" "^7.27.1"
"@babel/plugin-transform-typescript" "^7.28.5" "@babel/plugin-transform-typescript" "^7.28.5"
"@babel/runtime@^7.12.5":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz"
integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==
"@babel/runtime@^7.3.4", "@babel/runtime@7.4.5": "@babel/runtime@^7.3.4", "@babel/runtime@7.4.5":
version "7.4.5" version "7.4.5"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz"
@ -1019,11 +1009,6 @@
dependencies: dependencies:
regenerator-runtime "^0.13.2" regenerator-runtime "^0.13.2"
"@babel/runtime@^7.9.2":
version "7.28.6"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz"
integrity sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==
"@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3": "@babel/template@^7.27.1", "@babel/template@^7.27.2", "@babel/template@^7.3.3":
version "7.27.2" version "7.27.2"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz" resolved "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz"
@ -1822,54 +1807,11 @@
"@svgr/plugin-svgo" "^5.5.0" "@svgr/plugin-svgo" "^5.5.0"
loader-utils "^2.0.0" loader-utils "^2.0.0"
"@testing-library/dom@^8.0.0", "@testing-library/dom@^8.20.1":
version "8.20.1"
resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.1.tgz"
integrity sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==
dependencies:
"@babel/code-frame" "^7.10.4"
"@babel/runtime" "^7.12.5"
"@types/aria-query" "^5.0.1"
aria-query "5.1.3"
chalk "^4.1.0"
dom-accessibility-api "^0.5.9"
lz-string "^1.5.0"
pretty-format "^27.0.2"
"@testing-library/jest-dom@^5.17.0":
version "5.17.0"
resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz"
integrity sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==
dependencies:
"@adobe/css-tools" "^4.0.1"
"@babel/runtime" "^7.9.2"
"@types/testing-library__jest-dom" "^5.9.1"
aria-query "^5.0.0"
chalk "^3.0.0"
css.escape "^1.5.1"
dom-accessibility-api "^0.5.6"
lodash "^4.17.15"
redent "^3.0.0"
"@testing-library/react@^12.1.5":
version "12.1.5"
resolved "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz"
integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==
dependencies:
"@babel/runtime" "^7.12.5"
"@testing-library/dom" "^8.0.0"
"@types/react-dom" "<18.0.0"
"@trysound/sax@0.2.0": "@trysound/sax@0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz" resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@types/aria-query@^5.0.1":
version "5.0.4"
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz"
integrity sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==
"@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5": "@types/babel__core@^7.1.14", "@types/babel__core@^7.20.5":
version "7.20.5" version "7.20.5"
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz"
@ -2035,7 +1977,7 @@
dependencies: dependencies:
"@types/istanbul-lib-report" "*" "@types/istanbul-lib-report" "*"
"@types/jest@*", "@types/jest@^29.5.12": "@types/jest@^29.5.12":
version "29.5.14" version "29.5.14"
resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz" resolved "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz"
integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ== integrity sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==
@ -2104,11 +2046,6 @@
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz"
integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ== integrity sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==
"@types/react-dom@<18.0.0":
version "17.0.26"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.26.tgz"
integrity sha512-Z+2VcYXJwOqQ79HreLU/1fyQ88eXSSFh6I3JdrEHQIfYSI0kCQpTGvOrbE6jFGGYXKsHuwY9tBa/w5Uo6KzrEg==
"@types/react@*", "@types/react@^19.0.10", "@types/react@^19.2.0": "@types/react@*", "@types/react@^19.0.10", "@types/react@^19.2.0":
version "19.2.7" version "19.2.7"
resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz"
@ -2167,13 +2104,6 @@
resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz" resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.12.tgz"
integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q== integrity sha512-bTHG8fcxEqv1M9+TD14P8ok8hjxoOCkfKc8XXLaaD05kI7ohpeI956jtDOD3XHKBQrlyPughUtzm1jtVhHpA5Q==
"@types/testing-library__jest-dom@^5.9.1":
version "5.14.9"
resolved "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz"
integrity sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==
dependencies:
"@types/jest" "*"
"@types/tough-cookie@*": "@types/tough-cookie@*":
version "4.0.5" version "4.0.5"
resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz" resolved "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz"
@ -2601,13 +2531,6 @@ argparse@^2.0.1:
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-query@^5.0.0, aria-query@5.1.3:
version "5.1.3"
resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz"
integrity sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==
dependencies:
deep-equal "^2.0.5"
arr-diff@^4.0.0: arr-diff@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz" resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz"
@ -2623,7 +2546,7 @@ arr-union@^3.1.0:
resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz" resolved "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz"
integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==
array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz" resolved "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz"
integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw== integrity sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==
@ -3350,7 +3273,7 @@ call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-
es-errors "^1.3.0" es-errors "^1.3.0"
function-bind "^1.1.2" function-bind "^1.1.2"
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.7, call-bind@^1.0.8: call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.7, call-bind@^1.0.8:
version "1.0.8" version "1.0.8"
resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz"
integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==
@ -4049,11 +3972,6 @@ css-what@^6.0.1:
resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz" resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz"
integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==
css.escape@^1.5.1:
version "1.5.1"
resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz"
integrity sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==
cssesc@^3.0.0: cssesc@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz" resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
@ -4273,30 +4191,6 @@ deep-equal@^1.0.1:
object-keys "^1.1.1" object-keys "^1.1.1"
regexp.prototype.flags "^1.5.1" regexp.prototype.flags "^1.5.1"
deep-equal@^2.0.5:
version "2.2.3"
resolved "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz"
integrity sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==
dependencies:
array-buffer-byte-length "^1.0.0"
call-bind "^1.0.5"
es-get-iterator "^1.1.3"
get-intrinsic "^1.2.2"
is-arguments "^1.1.1"
is-array-buffer "^3.0.2"
is-date-object "^1.0.5"
is-regex "^1.1.4"
is-shared-array-buffer "^1.0.2"
isarray "^2.0.5"
object-is "^1.1.5"
object-keys "^1.1.1"
object.assign "^4.1.4"
regexp.prototype.flags "^1.5.1"
side-channel "^1.0.4"
which-boxed-primitive "^1.0.2"
which-collection "^1.0.1"
which-typed-array "^1.1.13"
deepmerge@^4.2.2: deepmerge@^4.2.2:
version "4.3.1" version "4.3.1"
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz" resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz"
@ -4469,11 +4363,6 @@ dns-txt@^2.0.2:
dependencies: dependencies:
buffer-indexof "^1.0.0" buffer-indexof "^1.0.0"
dom-accessibility-api@^0.5.6, dom-accessibility-api@^0.5.9:
version "0.5.16"
resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz"
integrity sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==
dom-converter@^0.2.0: dom-converter@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz"
@ -4772,21 +4661,6 @@ es-errors@^1.3.0:
resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz"
integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==
es-get-iterator@^1.1.3:
version "1.1.3"
resolved "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz"
integrity sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==
dependencies:
call-bind "^1.0.2"
get-intrinsic "^1.1.3"
has-symbols "^1.0.3"
is-arguments "^1.1.1"
is-map "^2.0.2"
is-set "^2.0.2"
is-string "^1.0.7"
isarray "^2.0.5"
stop-iteration-iterator "^1.0.0"
es-module-lexer@^1.2.1: es-module-lexer@^1.2.1:
version "1.7.0" version "1.7.0"
resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz" resolved "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz"
@ -5352,7 +5226,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5:
resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-intrinsic@^1.1.3, get-intrinsic@^1.2.2, get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0:
version "1.3.0" version "1.3.0"
resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz"
integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==
@ -5904,11 +5778,6 @@ imurmurhash@^0.1.4:
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
inflight@^1.0.4: inflight@^1.0.4:
version "1.0.6" version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
@ -5986,7 +5855,7 @@ is-arguments@^1.0.4, is-arguments@^1.1.1:
call-bound "^1.0.2" call-bound "^1.0.2"
has-tostringtag "^1.0.2" has-tostringtag "^1.0.2"
is-array-buffer@^3.0.2, is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: is-array-buffer@^3.0.4, is-array-buffer@^3.0.5:
version "3.0.5" version "3.0.5"
resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz" resolved "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz"
integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A== integrity sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==
@ -6178,7 +6047,7 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
dependencies: dependencies:
is-extglob "^2.1.1" is-extglob "^2.1.1"
is-map@^2.0.2, is-map@^2.0.3: is-map@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==
@ -6267,12 +6136,12 @@ is-regex@^1.1.4, is-regex@^1.2.1:
has-tostringtag "^1.0.2" has-tostringtag "^1.0.2"
hasown "^2.0.2" hasown "^2.0.2"
is-set@^2.0.2, is-set@^2.0.3: is-set@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz" resolved "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz"
integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg== integrity sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==
is-shared-array-buffer@^1.0.2, is-shared-array-buffer@^1.0.4: is-shared-array-buffer@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz" resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz"
integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A== integrity sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==
@ -6289,7 +6158,7 @@ is-stream@^2.0.0:
resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz"
integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
is-string@^1.0.7, is-string@^1.1.1: is-string@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz" resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz"
integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA== integrity sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==
@ -7208,11 +7077,6 @@ lru-cache@^5.1.1:
dependencies: dependencies:
yallist "^3.0.2" yallist "^3.0.2"
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
magic-string@^0.25.7: magic-string@^0.25.7:
version "0.25.9" version "0.25.9"
resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz"
@ -7438,11 +7302,6 @@ mimic-response@^2.0.0:
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz" resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
min-indent@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz"
integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
mini-css-extract-plugin@^1.6.0: mini-css-extract-plugin@^1.6.0:
version "1.6.2" version "1.6.2"
resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz" resolved "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.2.tgz"
@ -8541,16 +8400,16 @@ pretty-error@^4.0.0:
lodash "^4.17.20" lodash "^4.17.20"
renderkid "^3.0.0" renderkid "^3.0.0"
pretty-format@^27.0.2: pretty-format@^29.0.0:
version "27.5.1" version "29.7.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
dependencies: dependencies:
ansi-regex "^5.0.1" "@jest/schemas" "^29.6.3"
ansi-styles "^5.0.0" ansi-styles "^5.0.0"
react-is "^17.0.1" react-is "^18.0.0"
pretty-format@^29.0.0, pretty-format@^29.7.0: pretty-format@^29.7.0:
version "29.7.0" version "29.7.0"
resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz"
integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ== integrity sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==
@ -8737,7 +8596,7 @@ raw-body@2.5.2:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
react-dom@^17.0.2, react-dom@<18.0.0, react-dom@>=16.8.0, react-dom@>=16.8.3: react-dom@^17.0.2, react-dom@>=16.8.0, react-dom@>=16.8.3:
version "17.0.2" version "17.0.2"
resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz" resolved "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
@ -8751,11 +8610,6 @@ react-is@^16.13.1:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-is@^17.0.1:
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-is@^18.0.0, react-is@^18.3.1: react-is@^18.0.0, react-is@^18.3.1:
version "18.3.1" version "18.3.1"
resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz"
@ -8771,7 +8625,7 @@ react-mentions@^4.3.1:
prop-types "^15.5.8" prop-types "^15.5.8"
substyle "^9.1.0" substyle "^9.1.0"
"react@^15.0.2 || ^16.0.0 || ^17.0.0", react@^17.0.2, react@<18.0.0, react@>=16.8.0, react@>=16.8.3, react@17.0.2: "react@^15.0.2 || ^16.0.0 || ^17.0.0", react@^17.0.2, react@>=16.8.0, react@>=16.8.3, react@17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz" resolved "https://registry.npmjs.org/react/-/react-17.0.2.tgz"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
@ -8848,14 +8702,6 @@ readdirp@^4.0.1:
resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz" resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz"
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg== integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
redent@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz"
integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==
dependencies:
indent-string "^4.0.0"
strip-indent "^3.0.0"
reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
version "1.0.10" version "1.0.10"
resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz" resolved "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz"
@ -9514,7 +9360,7 @@ side-channel-weakmap@^1.0.2:
object-inspect "^1.13.3" object-inspect "^1.13.3"
side-channel-map "^1.0.1" side-channel-map "^1.0.1"
side-channel@^1.0.4, side-channel@^1.0.6, side-channel@^1.1.0: side-channel@^1.0.6, side-channel@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz" resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz"
integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==
@ -9799,7 +9645,7 @@ statuses@2.0.1:
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
stop-iteration-iterator@^1.0.0, stop-iteration-iterator@^1.1.0: stop-iteration-iterator@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz" resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz"
integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==
@ -9959,13 +9805,6 @@ strip-final-newline@^2.0.0:
resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz"
integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
strip-indent@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz"
integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
dependencies:
min-indent "^1.0.0"
strip-json-comments@^3.1.1: strip-json-comments@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
@ -10831,7 +10670,15 @@ whatwg-url@^14.0.0, whatwg-url@^14.1.1:
tr46 "^5.1.0" tr46 "^5.1.0"
webidl-conversions "^7.0.0" webidl-conversions "^7.0.0"
whatwg-url@^15.0.0, whatwg-url@^15.1.0: whatwg-url@^15.0.0:
version "15.1.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
dependencies:
tr46 "^6.0.0"
webidl-conversions "^8.0.0"
whatwg-url@^15.1.0:
version "15.1.0" version "15.1.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz" resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz"
integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g== integrity sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==
@ -10847,7 +10694,7 @@ whatwg-url@^5.0.0:
tr46 "~0.0.3" tr46 "~0.0.3"
webidl-conversions "^3.0.0" webidl-conversions "^3.0.0"
which-boxed-primitive@^1.0.2, which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1: which-boxed-primitive@^1.1.0, which-boxed-primitive@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz" resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz"
integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA== integrity sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==
@ -10877,7 +10724,7 @@ which-builtin-type@^1.2.1:
which-collection "^1.0.2" which-collection "^1.0.2"
which-typed-array "^1.1.16" which-typed-array "^1.1.16"
which-collection@^1.0.1, which-collection@^1.0.2: which-collection@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz" resolved "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz"
integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
@ -10892,7 +10739,7 @@ which-module@^2.0.0:
resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz" resolved "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz"
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
which-typed-array@^1.1.13, which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2: which-typed-array@^1.1.16, which-typed-array@^1.1.19, which-typed-array@^1.1.2:
version "1.1.19" version "1.1.19"
resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz" resolved "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz"
integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw== integrity sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==
@ -10982,7 +10829,12 @@ ws@^7.3.1:
resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz" resolved "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz"
integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==
ws@^8.18.0, ws@^8.18.3: ws@^8.18.0:
version "8.18.3"
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
ws@^8.18.3:
version "8.18.3" version "8.18.3"
resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz" resolved "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg== integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==

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

View File

@ -1 +1 @@
!function(){"use strict";var n,e={4256:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=594,function(){var n={594:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(4256)});o=t.O(o)}(); !function(){"use strict";var n,e={4256:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=594,function(){var n={594:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(4256)}));o=t.O(o)}();

View File

@ -1 +1 @@
!function(){"use strict";var n,e={5879:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=543,function(){var n={543:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(5879)});o=t.O(o)}(); !function(){"use strict";var n,e={5879:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=543,function(){var n={543:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(5879)}));o=t.O(o)}();

View File

@ -1 +1 @@
!function(){"use strict";var n,e={1684:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every(function(n){return t.O[n](r[c])})?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=152,function(){var n={152:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some(function(e){return 0!==n[e]})){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],function(){return t(1684)});o=t.O(o)}(); !function(){"use strict";var n,e={1684:function(n,e,r){(0,r(2985).C)()}},r={};function t(n){var o=r[n];if(void 0!==o)return o.exports;var i=r[n]={exports:{}};return e[n].call(i.exports,i,i.exports,t),i.exports}t.m=e,n=[],t.O=function(e,r,o,i){if(!r){var u=1/0;for(l=0;l<n.length;l++){r=n[l][0],o=n[l][1],i=n[l][2];for(var f=!0,c=0;c<r.length;c++)(!1&i||u>=i)&&Object.keys(t.O).every((function(n){return t.O[n](r[c])}))?r.splice(c--,1):(f=!1,i<u&&(u=i));if(f){n.splice(l--,1);var a=o();void 0!==a&&(e=a)}}return e}i=i||0;for(var l=n.length;l>0&&n[l-1][2]>i;l--)n[l]=n[l-1];n[l]=[r,o,i]},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,{a:e}),e},t.d=function(n,e){for(var r in e)t.o(e,r)&&!t.o(n,r)&&Object.defineProperty(n,r,{enumerable:!0,get:e[r]})},t.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(n){if("object"==typeof window)return window}}(),t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.j=152,function(){var n={152:0};t.O.j=function(e){return 0===n[e]};var e=function(e,r){var o,i,u=r[0],f=r[1],c=r[2],a=0;if(u.some((function(e){return 0!==n[e]}))){for(o in f)t.o(f,o)&&(t.m[o]=f[o]);if(c)var l=c(t)}for(e&&e(r);a<u.length;a++)i=u[a],t.o(n,i)&&n[i]&&n[i][0](),n[i]=0;return t.O(l)},r=self.webpackChunkmediacms_frontend=self.webpackChunkmediacms_frontend||[];r.forEach(e.bind(null,0)),r.push=e.bind(null,r.push.bind(r))}();var o=t.O(void 0,[276],(function(){return t(1684)}));o=t.O(o)}();

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

File diff suppressed because one or more lines are too long