Compare commits

...

15 Commits

Author SHA1 Message Date
Yiannis
20682a543a chore: minor code enhancements 2026-03-21 17:17:55 +02:00
Yiannis
c8b47a7922 refactor(frontend): convert contexts layer to TS and align page integration 2026-03-11 02:52:51 +02:00
Yiannis
499196b0f6 chore(frontend): harden settings parsing and update store imports 2026-03-11 02:31:07 +02:00
Yiannis
374ae4de6e refactor(frontend): replace legacy settings init/settings pattern with typed config functions 2026-03-11 02:14:45 +02:00
Yiannis
7a5fca6fd8 refactor(frontend): replace legacy action files with TypeScript equivalents 2026-03-11 02:06:59 +02:00
Yiannis
e9af15582f feat(types): create typed schema for global cms and runtime config 2026-03-11 01:56:54 +02:00
Yiannis
1b8e8aae6a refactor(frontend): replace legacy utils JS files with typed TS equivalents 2026-03-11 01:51:53 +02:00
Yiannis
df4b0422d5 fix(version): bump VERSION to 7.9 after accidental downgrade 2026-03-08 02:31:25 +02:00
Yiannis
0434f24691 chore(frontend): update frontend/src/static (generated by make build-frontend) 2026-03-08 02:23:26 +02:00
Yiannis
c2043fafa1 feat: utils/hooks unit tests 2026-02-07 18:39:24 +02:00
Yiannis
9f9dd699b2 feat: utils/stores unit tests 2026-02-07 18:09:46 +02:00
Yiannis
e2bc9399b9 feat: utils/classes unit tests 2026-02-07 18:09:46 +02:00
Yiannis
45d94069b9 feat: utils/actions unit tests 2026-02-07 18:09:46 +02:00
semantic-release-bot
b7427869b6 chore(release): 7.6.0 [skip ci]
## [7.6.0](https://github.com/mediacms-io/mediacms/compare/v7.5.0...v7.6.0) (2026-02-07)

### Features

* Create SECURITY.md ([#1485](https://github.com/mediacms-io/mediacms/issues/1485)) ([11449c2](11449c2187))
2026-02-07 10:31:40 +00:00
LabPixel
11449c2187 feat: Create SECURITY.md (#1485) 2026-02-07 12:31:10 +02:00
203 changed files with 9763 additions and 3411 deletions

View File

@@ -1,5 +1,11 @@
# Changelog
## [7.6.0](https://github.com/mediacms-io/mediacms/compare/v7.5.0...v7.6.0) (2026-02-07)
### Features
* Create SECURITY.md ([#1485](https://github.com/mediacms-io/mediacms/issues/1485)) ([11449c2](https://github.com/mediacms-io/mediacms/commit/11449c2187d0f450b86915d88f92595a1825e4cf))
## [7.5.0](https://github.com/mediacms-io/mediacms/compare/v7.4.0...v7.5.0) (2026-02-06)
### Features

54
SECURITY.md Normal file
View File

@@ -0,0 +1,54 @@
# Security Policy
Thank you for helping improve the security of MediaCMS.
We take security vulnerabilities seriously and appreciate responsible disclosure.
---
## Reporting a Vulnerability
If you discover a security vulnerability in MediaCMS, **please do not open a public GitHub issue**.
Instead, report it using one of the following methods:
- **GitHub Security Advisories (preferred)**
Use the "Report a vulnerability" feature in this repository.
- **Contact Form**
Submit details via the official contact page:
https://mediacms.io/contact/
Please include as much of the following information as possible:
- Affected version(s)
- Detailed description of the issue
- Steps to reproduce (PoC if available)
- Impact assessment (e.g. RCE, XSS, privilege escalation)
- Any potential mitigations you are aware of
---
## Supported Versions
Security updates are provided for the **latest stable release** of MediaCMS.
Older versions may not receive security patches.
---
## Disclosure Policy
- We aim to acknowledge reports within **7 days**
- We aim to provide a fix or mitigation within **90 days**, depending on severity
- Please allow us time to investigate before any public disclosure
We follow responsible disclosure practices and will coordinate disclosure timelines when appropriate.
---
## Recognition
At this time, MediaCMS does not operate a formal bug bounty program.
However, we are happy to acknowledge valid security reports in release notes or advisories (with your permission).
---
Thank you for helping keep MediaCMS secure.

View File

@@ -1 +1 @@
VERSION = "7.5"
VERSION = "7.9"

View File

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

View File

@@ -5,5 +5,5 @@ module.exports = {
'^.+\\.tsx?$': 'ts-jest',
'^.+\\.jsx?$': 'babel-jest',
},
collectCoverageFrom: ['src/**'],
collectCoverageFrom: ['src/**', '!src/static/lib/**'],
};

View File

@@ -21,6 +21,9 @@
"@babel/core": "^7.26.9",
"@babel/preset-env": "^7.26.9",
"@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/jest": "^29.5.12",
"@types/minimatch": "^5.1.2",

View File

@@ -55,7 +55,7 @@ export const HistoryPage: React.FC = () => {
const anonymousPage = isAnonymous || !PageStore.get('config-options').pages.profile.includeHistory;
if (!anonymousPage) {
addClassname(document.getElementById('page-history'), 'profile-page-history');
addClassname(document.getElementById('page-history')!, 'profile-page-history');
window.MediaCMS.profileId = username;
}

View File

@@ -76,7 +76,7 @@ export const HomePage: React.FC<HomePageProps> = ({
<MediaListRow
title={featured_title}
style={!visibleFeatured ? { display: 'none' } : undefined}
viewAllLink={featured_view_all_link ? links.featured : null}
viewAllLink={featured_view_all_link ? links.featured : undefined}
>
<InlineSliderItemListAsync
requestUrl={apiUrl.featured}
@@ -93,7 +93,7 @@ export const HomePage: React.FC<HomePageProps> = ({
<MediaListRow
title={recommended_title}
style={!visibleRecommended ? { display: 'none' } : undefined}
viewAllLink={recommended_view_all_link ? links.recommended : null}
viewAllLink={recommended_view_all_link ? links.recommended : undefined}
>
<InlineSliderItemListAsync
requestUrl={apiUrl.recommended}
@@ -108,7 +108,7 @@ export const HomePage: React.FC<HomePageProps> = ({
<MediaListRow
title={latest_title}
style={!visibleLatest ? { display: 'none' } : undefined}
viewAllLink={latest_view_all_link ? links.latest : null}
viewAllLink={latest_view_all_link ? links.latest : undefined}
>
<ItemListAsync
pageItems={30}

View File

@@ -55,7 +55,7 @@ export const LikedMediaPage: React.FC = () => {
const anonymousPage = isAnonymous || !PageStore.get('config-options').pages.profile.includeLikedMedia;
if (!anonymousPage) {
addClassname(document.getElementById('page-liked'), 'profile-page-liked');
addClassname(document.getElementById('page-liked')!, 'profile-page-liked');
window.MediaCMS.profileId = username;
}

View File

@@ -0,0 +1 @@
export type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;

View File

@@ -0,0 +1,212 @@
type GlobalMediaCMSApi = {
actions: string;
categories: string;
comments: string;
history: string;
liked: string;
manage_comments: string;
manage_media: string;
manage_users: string;
media: string;
members: string;
playlists: string;
search: string;
tags: string;
};
type GlobalMediaCMSContents = {
header: {
right: string;
onLogoRight: string;
};
notifications: {
messages: {
addToLiked: string;
removeFromLiked: string;
addToDisliked: string;
removeFromDisliked: string;
};
};
sidebar: {
belowNavMenu: string;
belowThemeSwitcher: string;
footer: string;
mainMenuExtraItems: { text: string; link: string; icon: string; className?: string }[]; // @todo: Check "className"
navMenuItems: { text: string; link: string; icon: string; className?: string }[]; // @todo: Check "className"
};
uploader: {
belowUploadArea: string;
postUploadMessage: string;
};
};
type GlobalMediaCMSFeatures = {
embeddedVideo: {
initialDimensions: {
width: number;
height: number;
};
};
headerBar: {
hideLogin: boolean;
hideRegister: boolean;
};
sideBar: {
hideHomeLink: boolean;
hideTagsLink: boolean;
hideCategoriesLink: boolean;
};
media: {
actions: {
share: boolean;
report: boolean;
like: boolean;
dislike: boolean;
download: boolean;
comment: boolean;
comment_mention: boolean;
save: boolean;
};
shareOptions: ('embed' | 'email')[];
};
mediaItem: {
hideDate: boolean;
hideViews: boolean;
hideAuthor: boolean;
};
playlists: {
mediaTypes: ('audio' | 'video')[];
};
};
type GlobalCMSPages = {
home: {
sections: {
latest: { title: string };
featured: { title: string };
recommended: { title: string };
};
};
media: {
categoriesWithTitle: boolean;
htmlInDescription: boolean;
hideViews: boolean;
related: { initialSize: number };
};
profile: {
htmlInDescription: boolean;
includeHistory: boolean;
includeLikedMedia: boolean;
};
search: { advancedFilters: boolean };
};
type GlobalCMSSite = {
api: string;
devEnv: boolean;
id: string;
logo: {
lightMode: { img: string; svg: string };
darkMode: { img: string; svg: string };
};
pages: {
featured: { enabled: boolean; title: string };
latest: { enabled: boolean; title: string };
members: { enabled: boolean; title: string };
recommended: { enabled: boolean; title: string };
};
taxonomies: {
categories: { enabled: boolean; title: string };
tags: { enabled: boolean; title: string };
};
theme: {
mode: 'light' | 'dark';
switch: { enabled: boolean; position: 'header' | 'sidebar' };
};
title: string;
url: string;
useRoundedCorners: boolean;
userPages: {
history: { enabled: boolean; title: string };
liked: { enabled: boolean; title: string };
};
version: string;
};
type GlobalCMSUrl = {
addMedia: string; // eg: "./add-media.html";
admin: string; // eg: "/admin";
categories: string; // eg: "./categories.html";
changePassword: string; // eg: "./change-password.html";
editChannel: string; // eg: "./edit-channel.html";
editProfile: string; // eg: "./edit-profile.html";
error404: string; // eg: "./error.html";
featuredMedia: string; // eg: "./featured.html";
history: string; // eg: "./history.html";
home: string; // eg: "./index.html";
latestMedia: string; // eg: "./latest.html";
likedMedia: string; // eg: "./liked.html";
manageComments: string; // eg: "./manage-comments.html";
manageMedia: string; // eg: "./manage-media.html";
manageUsers: string; // eg: "./manage-users.html";
members: string; // eg: "./members.html";
recommendedMedia: string; // eg: "./recommended.html";
register: string; // eg: "./register.html";
search: string; // eg: "./search.html";
signin: string; // eg: "./signin.html";
signout: string; // eg: "./signout.html";
tags: string; // eg: "./tags.html";
};
type GlobalCMSUser = {
name: string;
username: string;
thumbnail: string;
is: {
admin: boolean;
anonymous: boolean;
};
can: {
// a
addComment: boolean;
addMedia: boolean;
// c
canSeeMembersPage: boolean;
changePassword: boolean;
contactUser: boolean;
// d
deleteComment: boolean;
deleteMedia: boolean;
deleteProfile: boolean;
// e
editMedia: boolean;
editProfile: boolean;
editSubtitle: boolean;
// l
// m
manageComments: boolean;
manageMedia: boolean;
manageUsers: boolean;
mentionComment: boolean;
// r
readComment: boolean;
// u
usersNeedsToBeApproved: boolean;
};
pages: {
about: string;
media: string;
playlists: string;
};
};
export type GlobalMediaCMS = {
api: GlobalMediaCMSApi;
contents: GlobalMediaCMSContents;
features: GlobalMediaCMSFeatures;
pages: GlobalCMSPages;
profileId?: string;
site: GlobalCMSSite;
url: GlobalCMSUrl;
user: GlobalCMSUser;
};

View File

@@ -0,0 +1,200 @@
import { GlobalMediaCMS } from './GlobalMediaCMS';
type MediaCMSConfigApi = {
archive: {
tags: string;
categories: string;
};
featured: string;
manage: {
media: string;
users: string;
comments: string;
};
media: string;
playlists: string;
recommended: string;
search: {
query: string;
titles: string;
tag: string;
category: string;
};
user: {
liked: string;
history: string;
playlists: string;
};
users: string; // @todo: "users" or "members"?
};
type MediaCMSConfigContents = Omit<GlobalMediaCMS['contents'], 'notifications' | 'sidebar'> & {
sidebar: {
belowNavMenu: GlobalMediaCMS['contents']['sidebar']['belowNavMenu'];
belowThemeSwitcher: GlobalMediaCMS['contents']['sidebar']['belowThemeSwitcher'];
footer: GlobalMediaCMS['contents']['sidebar']['footer'];
mainMenuExtra: { items: GlobalMediaCMS['contents']['sidebar']['mainMenuExtraItems'] };
navMenu: { items: GlobalMediaCMS['contents']['sidebar']['navMenuItems'] };
};
};
type MediaCMSConfigEnabled = Pick<GlobalMediaCMS['site'], 'taxonomies'> & {
pages: GlobalMediaCMS['site']['pages'] & GlobalMediaCMS['site']['userPages'];
};
type MediaCMSConfigMember = {
name: GlobalMediaCMS['user']['name'] | null;
username: GlobalMediaCMS['user']['username'] | null;
thumbnail: GlobalMediaCMS['user']['thumbnail'] | null;
is: GlobalMediaCMS['user']['is'];
can: {
// a
addComment: boolean;
addMedia: boolean;
// c
canSeeMembersPage: boolean; // @note: This sould be renamed
changePassword: boolean;
contactUser: boolean;
// d
deleteComment: boolean;
deleteMedia: boolean;
deleteProfile: boolean;
dislikeMedia: boolean;
downloadMedia: boolean;
// e
editMedia: boolean;
editProfile: boolean;
editSubtitle: boolean;
// l
likeMedia: boolean;
login: boolean;
// m
manageComments: boolean;
manageMedia: boolean;
manageUsers: boolean;
mentionComment: boolean;
// r
readComment: boolean;
register: boolean;
reportMedia: boolean;
// s
saveMedia: boolean;
shareMedia: boolean;
// u
usersNeedsToBeApproved: boolean;
};
pages: {
home: string | null; // @todo: Check this again
about: GlobalMediaCMS['user']['pages']['about'] | null;
media: GlobalMediaCMS['user']['pages']['media'] | null;
playlists: GlobalMediaCMS['user']['pages']['playlists'] | null;
};
};
type MediaCMSConfigMedia = {
item: {
displayAuthor: boolean;
displayViews: boolean;
displayPublishDate: boolean;
};
share: { options: string[] };
};
type MediaCMSConfigNotifications = GlobalMediaCMS['contents']['notifications'];
type MediaCMSConfigOptions = {
pages: {
home: GlobalMediaCMS['pages']['home'];
search: GlobalMediaCMS['pages']['search'];
media: Omit<GlobalMediaCMS['pages']['media'], 'hideViews'> & {
displayViews: boolean;
};
profile: GlobalMediaCMS['pages']['profile'];
};
embedded: {
video: {
dimensions: {
width: number;
widthUnit: 'px';
// widthUnit: 'px' | 'percent'; // @note: The unit value "percent" is not used
height: number;
heightUnit: 'px';
// heightUnit: 'px' | 'percent'; // @note: The unit value "percent" is not used
};
};
};
};
type MediaCMSConfigPlaylists = GlobalMediaCMS['features']['playlists'];
type MediaCMSConfigSidebar = GlobalMediaCMS['features']['sideBar'];
type MediaCMSConfigSite = {
api: string;
id: string;
title: string;
url: string;
useRoundedCorners: boolean;
version: string;
};
type MediaCMSConfigTheme = Pick<GlobalMediaCMS['site'], 'logo'> & GlobalMediaCMS['site']['theme'];
type MediaCMSConfigUrl = {
admin: string; // eg: '/admin'
archive: {
categories: string; // eg: './categories.html'
tags: string; // eg: './tags.html';
};
changePassword: string; // eg: './change-password.html';
embed: string; // eg: 'http://localhost/embed?m=';
error404: string; // eg: './error.html';
featured: string; // eg: './featured.html';
home: string; // eg: './index.html'
latest: string; // eg: './latest.html';
manage: {
comments: string; // eg: './manage-comments.html'
media: string; // eg: './manage-media.html';
users: string; // eg: './manage-users.html';
};
members: string; // eg: './members.html';
profile: {
about: string; // eg: './profile-about.html';
media: string; // eg: './profile-media.html';
playlists: string; // eg: './profile-playlists.html';
shared_by_me: string; // eg: './profile-media.html/shared_by_me';
shared_with_me: string; // eg: './profile-media.html/shared_with_me';
};
recommended: string; // eg: './recommended.html';
register: string; // eg: './register.html';
search: {
base: string; // eg: './search.html';
category: string; // eg: './search.html?c=';
query: string; // eg: './search.html?q=';
tag: string; // eg: './search.html?t=';
};
signin: string; // eg: './signin.html';
signout: string; // eg: './signout.html';
user: {
addMedia: string; // eg: './add-media.html';
editChannel: string; // eg: './edit-channel.html';
editProfile: string; // eg: './edit-profile.html';
history: string; // eg: './history.html';
liked: string; // eg: './liked.html';
};
};
export type MediaCMSConfig = {
api: MediaCMSConfigApi;
contents: MediaCMSConfigContents;
enabled: MediaCMSConfigEnabled;
member: MediaCMSConfigMember;
media: MediaCMSConfigMedia;
notifications: MediaCMSConfigNotifications;
options: MediaCMSConfigOptions;
playlists: MediaCMSConfigPlaylists;
sidebar: MediaCMSConfigSidebar;
site: MediaCMSConfigSite;
theme: MediaCMSConfigTheme;
url: MediaCMSConfigUrl;
};

View File

@@ -0,0 +1,3 @@
export * from './DeepPartial';
export * from './GlobalMediaCMS';
export * from './MediaCMSConfig';

View File

@@ -1,90 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function loadMediaData() {
Dispatcher.dispatch({
type: 'LOAD_MEDIA_DATA',
});
}
export function likeMedia() {
Dispatcher.dispatch({
type: 'LIKE_MEDIA',
});
}
export function dislikeMedia() {
Dispatcher.dispatch({
type: 'DISLIKE_MEDIA',
});
}
export function reportMedia(reportDescription) {
Dispatcher.dispatch({
type: 'REPORT_MEDIA',
reportDescription: !!reportDescription ? reportDescription.replace(/\s/g, '') : '',
});
}
export function copyShareLink(inputElem) {
Dispatcher.dispatch({
type: 'COPY_SHARE_LINK',
inputElement: inputElem,
});
}
export function copyEmbedMediaCode(inputElem) {
Dispatcher.dispatch({
type: 'COPY_EMBED_MEDIA_CODE',
inputElement: inputElem,
});
}
export function removeMedia() {
Dispatcher.dispatch({
type: 'REMOVE_MEDIA',
});
}
export function submitComment(commentText) {
Dispatcher.dispatch({
type: 'SUBMIT_COMMENT',
commentText,
});
}
export function deleteComment(commentId) {
Dispatcher.dispatch({
type: 'DELETE_COMMENT',
commentId,
});
}
export function createPlaylist(playlist_data) {
Dispatcher.dispatch({
type: 'CREATE_PLAYLIST',
playlist_data,
});
}
export function addMediaToPlaylist(playlist_id, media_id) {
Dispatcher.dispatch({
type: 'ADD_MEDIA_TO_PLAYLIST',
playlist_id,
media_id,
});
}
export function removeMediaFromPlaylist(playlist_id, media_id) {
Dispatcher.dispatch({
type: 'REMOVE_MEDIA_FROM_PLAYLIST',
playlist_id,
media_id,
});
}
export function addNewPlaylist(playlist_data) {
Dispatcher.dispatch({
type: 'APPEND_NEW_PLAYLIST',
playlist_data,
});
}

View File

@@ -0,0 +1,63 @@
import { dispatcher } from '../dispatcher';
export function loadMediaData() {
dispatcher.dispatch({ type: 'LOAD_MEDIA_DATA' });
}
export function likeMedia() {
dispatcher.dispatch({ type: 'LIKE_MEDIA' });
}
export function dislikeMedia() {
dispatcher.dispatch({ type: 'DISLIKE_MEDIA' });
}
// @todo: Revisit this
export function reportMedia(reportDescription?: string | null) {
dispatcher.dispatch({
type: 'REPORT_MEDIA',
reportDescription: typeof reportDescription === 'string' ? reportDescription.replace(/\s/g, '') : '',
});
}
export function copyShareLink(inputElem: HTMLInputElement) {
dispatcher.dispatch({ type: 'COPY_SHARE_LINK', inputElement: inputElem });
}
export function copyEmbedMediaCode(inputElem: HTMLTextAreaElement) {
dispatcher.dispatch({ type: 'COPY_EMBED_MEDIA_CODE', inputElement: inputElem });
}
export function removeMedia() {
dispatcher.dispatch({ type: 'REMOVE_MEDIA' });
}
export function submitComment(commentText: string) {
dispatcher.dispatch({ type: 'SUBMIT_COMMENT', commentText });
}
export function deleteComment(commentId: string | number) {
dispatcher.dispatch({ type: 'DELETE_COMMENT', commentId });
}
export function createPlaylist(playlist_data: { title: string; description: string }) {
dispatcher.dispatch({ type: 'CREATE_PLAYLIST', playlist_data });
}
export function addMediaToPlaylist(playlist_id: string, media_id: string) {
dispatcher.dispatch({ type: 'ADD_MEDIA_TO_PLAYLIST', playlist_id, media_id });
}
export function removeMediaFromPlaylist(playlist_id: string, media_id: string) {
dispatcher.dispatch({ type: 'REMOVE_MEDIA_FROM_PLAYLIST', playlist_id, media_id });
}
export function addNewPlaylist(playlist_data: {
playlist_id: string;
add_date: Date; // @todo: Revisit this
description: string;
title: string;
media_list: string[]; // @todo: Revisit this
}) {
dispatcher.dispatch({ type: 'APPEND_NEW_PLAYLIST', playlist_data });
}

View File

@@ -1,22 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function initPage(page) {
Dispatcher.dispatch({
type: 'INIT_PAGE',
page,
});
}
export function toggleMediaAutoPlay() {
Dispatcher.dispatch({
type: 'TOGGLE_AUTO_PLAY',
});
}
export function addNotification(notification, notificationId) {
Dispatcher.dispatch({
type: 'ADD_NOTIFICATION',
notification,
notificationId,
});
}

View File

@@ -0,0 +1,13 @@
import { dispatcher } from '../dispatcher';
export function initPage(page: string) {
dispatcher.dispatch({ type: 'INIT_PAGE', page });
}
export function toggleMediaAutoPlay() {
dispatcher.dispatch({ type: 'TOGGLE_AUTO_PLAY' });
}
export function addNotification(notification: string, notificationId: string) {
dispatcher.dispatch({ type: 'ADD_NOTIFICATION', notification, notificationId });
}

View File

@@ -1,41 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function loadPlaylistData() {
Dispatcher.dispatch({
type: 'LOAD_PLAYLIST_DATA',
});
}
export function toggleSave() {
Dispatcher.dispatch({
type: 'TOGGLE_SAVE',
});
}
export function updatePlaylist(playlist_data) {
Dispatcher.dispatch({
type: 'UPDATE_PLAYLIST',
playlist_data,
});
}
export function removePlaylist() {
Dispatcher.dispatch({
type: 'REMOVE_PLAYLIST',
});
}
export function removedMediaFromPlaylist(media_id, playlist_id) {
Dispatcher.dispatch({
type: 'MEDIA_REMOVED_FROM_PLAYLIST',
media_id,
playlist_id,
});
}
export function reorderedMediaInPlaylist(newMediaData) {
Dispatcher.dispatch({
type: 'PLAYLIST_MEDIA_REORDERED',
playlist_media: newMediaData,
});
}

View File

@@ -0,0 +1,26 @@
import { dispatcher } from '../dispatcher';
export function loadPlaylistData() {
dispatcher.dispatch({ type: 'LOAD_PLAYLIST_DATA' });
}
export function toggleSave() {
dispatcher.dispatch({ type: 'TOGGLE_SAVE' });
}
export function updatePlaylist(playlist_data: { title: string; description: string }) {
dispatcher.dispatch({ type: 'UPDATE_PLAYLIST', playlist_data });
}
export function removePlaylist() {
dispatcher.dispatch({ type: 'REMOVE_PLAYLIST' });
}
export function removedMediaFromPlaylist(media_id: string, playlist_id: string) {
dispatcher.dispatch({ type: 'MEDIA_REMOVED_FROM_PLAYLIST', media_id, playlist_id });
}
// @todo: Revisit this
export function reorderedMediaInPlaylist(newMediaData: { [k: string]: any; thumbnail_url: string; url: string }[]) {
dispatcher.dispatch({ type: 'PLAYLIST_MEDIA_REORDERED', playlist_media: newMediaData });
}

View File

@@ -1,19 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function toggleLoop() {
Dispatcher.dispatch({
type: 'TOGGLE_LOOP',
});
}
export function toggleShuffle() {
Dispatcher.dispatch({
type: 'TOGGLE_SHUFFLE',
});
}
export function toggleSave() {
Dispatcher.dispatch({
type: 'TOGGLE_SAVE',
});
}

View File

@@ -0,0 +1,13 @@
import { dispatcher } from '../dispatcher';
export function toggleLoop() {
dispatcher.dispatch({ type: 'TOGGLE_LOOP' });
}
export function toggleShuffle() {
dispatcher.dispatch({ type: 'TOGGLE_SHUFFLE' });
}
export function toggleSave() {
dispatcher.dispatch({ type: 'TOGGLE_SAVE' });
}

View File

@@ -1,13 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function load_author_data() {
Dispatcher.dispatch({
type: 'LOAD_AUTHOR_DATA',
});
}
export function remove_profile() {
Dispatcher.dispatch({
type: 'REMOVE_PROFILE',
});
}

View File

@@ -0,0 +1,9 @@
import { dispatcher } from '../dispatcher';
export function load_author_data() {
dispatcher.dispatch({ type: 'LOAD_AUTHOR_DATA' });
}
export function remove_profile() {
dispatcher.dispatch({ type: 'REMOVE_PROFILE' });
}

View File

@@ -1,8 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function requestPredictions(query) {
Dispatcher.dispatch({
type: 'REQUEST_PREDICTIONS',
query,
});
}

View File

@@ -0,0 +1,5 @@
import { dispatcher } from '../dispatcher';
export function requestPredictions(query: string) {
dispatcher.dispatch({ type: 'REQUEST_PREDICTIONS', query });
}

View File

@@ -1,36 +0,0 @@
import Dispatcher from '../dispatcher.js';
export function set_viewer_mode(inTheaterMode) {
Dispatcher.dispatch({
type: 'SET_VIEWER_MODE',
inTheaterMode,
});
}
export function set_player_volume(playerVolume) {
Dispatcher.dispatch({
type: 'SET_PLAYER_VOLUME',
playerVolume,
});
}
export function set_player_sound_muted(playerSoundMuted) {
Dispatcher.dispatch({
type: 'SET_PLAYER_SOUND_MUTED',
playerSoundMuted,
});
}
export function set_video_quality(quality) {
Dispatcher.dispatch({
type: 'SET_VIDEO_QUALITY',
quality,
});
}
export function set_video_playback_speed(playbackSpeed) {
Dispatcher.dispatch({
type: 'SET_VIDEO_PLAYBACK_SPEED',
playbackSpeed,
});
}

View File

@@ -0,0 +1,23 @@
import { dispatcher } from '../dispatcher';
export function set_viewer_mode(inTheaterMode: boolean) {
dispatcher.dispatch({ type: 'SET_VIEWER_MODE', inTheaterMode });
}
export function set_player_volume(playerVolume: number) {
dispatcher.dispatch({ type: 'SET_PLAYER_VOLUME', playerVolume });
}
export function set_player_sound_muted(playerSoundMuted: boolean) {
dispatcher.dispatch({ type: 'SET_PLAYER_SOUND_MUTED', playerSoundMuted });
}
export function set_video_quality(
quality: 'auto' | number // @todo: Check this again
) {
dispatcher.dispatch({ type: 'SET_VIDEO_QUALITY', quality });
}
export function set_video_playback_speed(playbackSpeed: number) {
dispatcher.dispatch({ type: 'SET_VIDEO_PLAYBACK_SPEED', playbackSpeed });
}

View File

@@ -1,2 +0,0 @@
export { default as months } from './months';
export { default as weekdays } from './weekdays';

View File

@@ -0,0 +1,2 @@
export * from './months';
export * from './weekdays';

View File

@@ -1,14 +0,0 @@
export default [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];

View File

@@ -0,0 +1,14 @@
export const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
] as const;

View File

@@ -1 +0,0 @@
export default ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

View File

@@ -0,0 +1 @@
export const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;

View File

@@ -1,5 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const ApiUrlContext = createContext(mediacmsConfig(window.MediaCMS).api);
export const ApiUrlConsumer = ApiUrlContext.Consumer;

View File

@@ -0,0 +1,5 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const ApiUrlContext = createContext(mediacmsConfig(window.MediaCMS).api);
export const ApiUrlConsumer = ApiUrlContext.Consumer;

View File

@@ -1,130 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
import { translateString } from '../../utils/helpers/';
const config = mediacmsConfig(window.MediaCMS);
const links = config.url;
const theme = config.theme;
const user = config.member;
const hasThemeSwitcher = theme.switch.enabled && 'header' === theme.switch.position;
function popupTopNavItems() {
const items = [];
if (!user.is.anonymous) {
if (user.can.addMedia) {
items.push({
link: links.user.addMedia,
icon: 'video_call',
text: translateString('Upload media'),
itemAttr: {
className: 'visible-only-in-small',
},
});
if (user.pages.media) {
items.push({
link: user.pages.media,
icon: 'video_library',
text: translateString('My media'),
});
}
}
items.push({
link: links.signout,
icon: 'exit_to_app',
text: translateString('Sign out'),
});
}
return items;
}
function popupMiddleNavItems() {
const items = [];
if (hasThemeSwitcher) {
items.push({
itemType: 'open-subpage',
icon: 'brightness_4',
iconPos: 'left',
text: 'Switch theme',
buttonAttr: {
className: 'change-page',
'data-page-id': 'switch-theme',
},
});
}
if (user.is.anonymous) {
if (user.can.login) {
items.push({
itemType: 'link',
icon: 'login',
iconPos: 'left',
text: translateString('Sign in'),
link: links.signin,
linkAttr: {
className: hasThemeSwitcher ? 'visible-only-in-small' : 'visible-only-in-extra-small',
},
});
}
if (user.can.register) {
items.push({
itemType: 'link',
icon: 'person_add',
iconPos: 'left',
text: translateString('Register'),
link: links.register,
linkAttr: {
className: hasThemeSwitcher ? 'visible-only-in-small' : 'visible-only-in-extra-small',
},
});
}
} else {
items.push({
link: links.user.editProfile,
icon: 'brush',
text: translateString('Edit profile'),
});
if (user.can.changePassword) {
items.push({
link: links.changePassword,
icon: 'lock',
text: translateString('Change password'),
});
}
}
return items;
}
function popupBottomNavItems() {
const items = [];
if (user.is.admin) {
items.push({
link: links.admin,
icon: 'admin_panel_settings',
text: 'MediaCMS administration',
});
}
return items;
}
export const HeaderContext = createContext({
hasThemeSwitcher,
popupNavItems: {
top: popupTopNavItems(),
middle: popupMiddleNavItems(),
bottom: popupBottomNavItems(),
},
});
export const HeaderConsumer = HeaderContext.Consumer;

View File

@@ -0,0 +1,130 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
import { translateString } from '../helpers';
const config = mediacmsConfig(window.MediaCMS);
const links = config.url;
const theme = config.theme;
const user = config.member;
const hasThemeSwitcher = theme.switch.enabled && 'header' === theme.switch.position;
function popupTopNavItems() {
const items = [];
if (!user.is.anonymous) {
if (user.can.addMedia) {
items.push({
link: links.user.addMedia,
icon: 'video_call',
text: translateString('Upload media'),
itemAttr: {
className: 'visible-only-in-small',
},
});
if (user.pages.media) {
items.push({
link: user.pages.media,
icon: 'video_library',
text: translateString('My media'),
});
}
}
items.push({
link: links.signout,
icon: 'exit_to_app',
text: translateString('Sign out'),
});
}
return items;
}
function popupMiddleNavItems() {
const items = [];
if (hasThemeSwitcher) {
items.push({
itemType: 'open-subpage',
icon: 'brightness_4',
iconPos: 'left',
text: 'Switch theme',
buttonAttr: {
className: 'change-page',
'data-page-id': 'switch-theme',
},
});
}
if (user.is.anonymous) {
if (user.can.login) {
items.push({
itemType: 'link',
icon: 'login',
iconPos: 'left',
text: translateString('Sign in'),
link: links.signin,
linkAttr: {
className: hasThemeSwitcher ? 'visible-only-in-small' : 'visible-only-in-extra-small',
},
});
}
if (user.can.register) {
items.push({
itemType: 'link',
icon: 'person_add',
iconPos: 'left',
text: translateString('Register'),
link: links.register,
linkAttr: {
className: hasThemeSwitcher ? 'visible-only-in-small' : 'visible-only-in-extra-small',
},
});
}
} else {
items.push({
link: links.user.editProfile,
icon: 'brush',
text: translateString('Edit profile'),
});
if (user.can.changePassword) {
items.push({
link: links.changePassword,
icon: 'lock',
text: translateString('Change password'),
});
}
}
return items;
}
function popupBottomNavItems() {
const items = [];
if (user.is.admin) {
items.push({
link: links.admin,
icon: 'admin_panel_settings',
text: 'MediaCMS administration',
});
}
return items;
}
export const HeaderContext = createContext({
hasThemeSwitcher,
popupNavItems: {
top: popupTopNavItems(),
middle: popupMiddleNavItems(),
bottom: popupBottomNavItems(),
},
});
export const HeaderConsumer = HeaderContext.Consumer;

View File

@@ -1,13 +1,15 @@
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { BrowserCache } from '../classes/';
import { PageStore } from '../stores/';
import { addClassname, removeClassname, inEmbeddedApp } from '../helpers/';
import React, { createContext, ReactNode, useContext, useEffect, useMemo, useState } from 'react';
import { BrowserCache } from '../classes';
import { PageStore } from '../stores';
import { addClassname, removeClassname, inEmbeddedApp } from '../helpers';
import SiteContext from './SiteContext';
let slidingSidebarTimeout;
let slidingSidebarTimeout: NodeJS.Timeout | null = null;
function onSidebarVisibilityChange(visibleSidebar) {
clearTimeout(slidingSidebarTimeout);
function onSidebarVisibilityChange(visibleSidebar: boolean) {
if (slidingSidebarTimeout) {
clearTimeout(slidingSidebarTimeout);
}
addClassname(document.body, 'sliding-sidebar');
@@ -39,18 +41,29 @@ function onSidebarVisibilityChange(visibleSidebar) {
}, 20);
}
export const LayoutContext = createContext();
export const LayoutContext = createContext({
enabledSidebar: true,
visibleSidebar: true,
setVisibleSidebar: (_: boolean) => {},
visibleMobileSearch: false,
toggleMobileSearch: () => {},
toggleSidebar: () => {},
});
export const LayoutProvider = ({ children }) => {
export const LayoutProvider = ({ children }: { children: ReactNode }) => {
const site = useContext(SiteContext);
const cache = new BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
const cache = BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
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<boolean>(
cache instanceof Error
? true // @todo: Check this again
: cache.get('visible-sidebar')
);
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
const toggleMobileSearch = () => {
@@ -71,7 +84,9 @@ export const LayoutProvider = ({ children }) => {
}
if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
cache.set('visible-sidebar', visibleSidebar);
if (!(cache instanceof Error)) {
cache.set('visible-sidebar', visibleSidebar);
}
}
}, [isEmbeddedApp, isMediaPage, visibleSidebar]);

View File

@@ -1,5 +1,5 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const LinksContext = createContext(mediacmsConfig(window.MediaCMS).url);
export const LinksConsumer = LinksContext.Consumer;

View File

@@ -1,5 +1,5 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const MemberContext = createContext(mediacmsConfig(window.MediaCMS).member);
export const MemberConsumer = MemberContext.Consumer;

View File

@@ -1,4 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const PlaylistsContext = createContext(mediacmsConfig(window.MediaCMS).playlists);

View File

@@ -0,0 +1,4 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const PlaylistsContext = createContext(mediacmsConfig(window.MediaCMS).playlists);

View File

@@ -1,5 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const ShareOptionsContext = createContext(mediacmsConfig(window.MediaCMS).media.share.options);

View File

@@ -0,0 +1,4 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const ShareOptionsContext = createContext(mediacmsConfig(window.MediaCMS).media.share.options);

View File

@@ -1,5 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const SidebarContext = createContext(mediacmsConfig(window.MediaCMS).sidebar);
export const SidebarConsumer = SidebarContext.Consumer;

View File

@@ -0,0 +1,5 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const SidebarContext = createContext(mediacmsConfig(window.MediaCMS).sidebar);
export const SidebarConsumer = SidebarContext.Consumer;

View File

@@ -1,5 +1,5 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const SiteContext = createContext(mediacmsConfig(window.MediaCMS).site);
export const SiteConsumer = SiteContext.Consumer;

View File

@@ -1,11 +1,9 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
const notifications = mediacmsConfig(window.MediaCMS).notifications.messages;
const texts = {
notifications,
};
const texts = { notifications };
export const TextsContext = createContext(texts);

View File

@@ -1,80 +0,0 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { BrowserCache } from '../classes/';
import { addClassname, removeClassname, supportsSvgAsImg } from '../helpers/';
import { config as mediacmsConfig } from '../settings/config.js';
import SiteContext from './SiteContext';
const config = mediacmsConfig(window.MediaCMS);
function initLogo(logo) {
let light = null;
let dark = null;
if (void 0 !== logo.darkMode) {
if (supportsSvgAsImg() && void 0 !== logo.darkMode.svg && '' !== logo.darkMode.svg) {
dark = logo.darkMode.svg;
} else if (void 0 !== logo.darkMode.img && '' !== logo.darkMode.img) {
dark = logo.darkMode.img;
}
}
if (void 0 !== logo.lightMode) {
if (supportsSvgAsImg() && void 0 !== logo.lightMode.svg && '' !== logo.lightMode.svg) {
light = logo.lightMode.svg;
} else if (void 0 !== logo.lightMode.img && '' !== logo.lightMode.img) {
light = logo.lightMode.img;
}
}
if (null !== light || null !== dark) {
if (null === light) {
light = dark;
} else if (null === dark) {
dark = light;
}
}
return {
light,
dark,
};
}
function initMode(cachedValue, defaultValue) {
return 'light' === cachedValue || 'dark' === cachedValue ? cachedValue : defaultValue;
}
export const ThemeContext = createContext();
export const ThemeProvider = ({ children }) => {
const site = useContext(SiteContext);
const cache = new BrowserCache('MediaCMS[' + site.id + '][theme]', 86400);
const [themeMode, setThemeMode] = useState(initMode(cache.get('mode'), config.theme.mode));
const logos = initLogo(config.theme.logo);
const [logo, setLogo] = useState(logos[themeMode]);
const changeMode = () => {
setThemeMode('light' === themeMode ? 'dark' : 'light');
};
useEffect(() => {
if ('dark' === themeMode) {
addClassname(document.body, 'dark_theme');
} else {
removeClassname(document.body, 'dark_theme');
}
cache.set('mode', themeMode);
setLogo(logos[themeMode]);
}, [themeMode]);
const value = {
logo,
currentThemeMode: themeMode,
changeThemeMode: changeMode,
themeModeSwitcher: config.theme.switch,
};
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
};
export const ThemeConsumer = ThemeContext.Consumer;

View File

@@ -0,0 +1,95 @@
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
import { GlobalMediaCMS } from '../../types';
import { BrowserCache } from '../classes';
import { addClassname, removeClassname, supportsSvgAsImg } from '../helpers';
import { config as mediacmsConfig } from '../settings/config';
import SiteContext from './SiteContext';
const config = mediacmsConfig(window.MediaCMS);
function initLogo(logo: GlobalMediaCMS['site']['logo']) {
let light = null;
let dark = null;
if (void 0 !== logo.darkMode) {
if (supportsSvgAsImg() && void 0 !== logo.darkMode.svg && '' !== logo.darkMode.svg) {
dark = logo.darkMode.svg;
} else if (void 0 !== logo.darkMode.img && '' !== logo.darkMode.img) {
dark = logo.darkMode.img;
}
}
if (void 0 !== logo.lightMode) {
if (supportsSvgAsImg() && void 0 !== logo.lightMode.svg && '' !== logo.lightMode.svg) {
light = logo.lightMode.svg;
} else if (void 0 !== logo.lightMode.img && '' !== logo.lightMode.img) {
light = logo.lightMode.img;
}
}
if (null !== light || null !== dark) {
if (null === light) {
light = dark;
} else if (null === dark) {
dark = light;
}
}
return {
light,
dark,
};
}
function initMode(cachedValue: string | undefined, defaultValue: GlobalMediaCMS['site']['theme']['mode']) {
return 'light' === cachedValue || 'dark' === cachedValue ? cachedValue : defaultValue;
}
export const ThemeContext = createContext({
logo: initLogo(config.theme.logo)[config.theme.mode],
currentThemeMode: config.theme.mode,
changeThemeMode: () => {},
themeModeSwitcher: config.theme.switch,
});
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const site = useContext(SiteContext);
const cache = BrowserCache('MediaCMS[' + site.id + '][theme]', 86400);
const [themeMode, setThemeMode] = useState(
initMode(cache instanceof Error ? undefined : cache.get('mode'), config.theme.mode)
);
const logos = initLogo(config.theme.logo);
const [logo, setLogo] = useState(logos[themeMode]);
const changeMode = () => {
setThemeMode('light' === themeMode ? 'dark' : 'light');
};
useEffect(() => {
if ('dark' === themeMode) {
addClassname(document.body, 'dark_theme');
} else {
removeClassname(document.body, 'dark_theme');
}
if (!(cache instanceof Error)) {
cache.set('mode', themeMode);
}
setLogo(logos[themeMode]);
}, [themeMode]);
const value = {
logo,
currentThemeMode: themeMode,
changeThemeMode: changeMode,
themeModeSwitcher: config.theme.switch,
};
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
};
export const ThemeConsumer = ThemeContext.Consumer;

View File

@@ -1,22 +0,0 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const UserContext = createContext();
const member = mediacmsConfig(window.MediaCMS).member;
export const UserProvider = ({ children }) => {
const value = {
isAnonymous: member.is.anonymous,
username: member.username,
thumbnail: member.thumbnail,
userCan: member.can,
pages: member.pages,
};
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
export const UserConsumer = UserContext.Consumer;
export default UserContext;

View File

@@ -0,0 +1,28 @@
import React from 'react';
import { createContext, ReactNode } from 'react';
import { config as mediacmsConfig } from '../settings/config';
const member = mediacmsConfig(window.MediaCMS).member;
export const UserContext = createContext({
isAnonymous: member.is.anonymous,
username: member.username,
thumbnail: member.thumbnail,
userCan: member.can,
pages: member.pages,
});
export function UserProvider({ children }: { children: ReactNode }) {
const value = {
isAnonymous: member.is.anonymous,
username: member.username,
thumbnail: member.thumbnail,
userCan: member.can,
pages: member.pages,
};
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
export const UserConsumer = UserContext.Consumer;
export default UserContext;

View File

@@ -1,2 +0,0 @@
const Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();

View File

@@ -0,0 +1,3 @@
import { Dispatcher } from 'flux';
export const dispatcher = new Dispatcher();

View File

@@ -1,19 +0,0 @@
export function csrfToken() {
var i,
cookies,
cookie,
cookieVal = null;
if (document.cookie && '' !== document.cookie) {
cookies = document.cookie.split(';');
i = 0;
while (i < cookies.length) {
cookie = cookies[i].trim();
if ('csrftoken=' === cookie.substring(0, 10)) {
cookieVal = decodeURIComponent(cookie.substring(10));
break;
}
i += 1;
}
}
return cookieVal;
}

View File

@@ -0,0 +1,18 @@
export function csrfToken() {
let cookieVal = null;
if (document.cookie && '' !== document.cookie) {
const cookies = document.cookie.split(';');
let i = 0;
while (i < cookies.length) {
const cookie = cookies[i].trim();
if ('csrftoken=' === cookie.substring(0, 10)) {
cookieVal = decodeURIComponent(cookie.substring(10));
break;
}
i += 1;
}
}
return cookieVal;
}

View File

@@ -1,79 +0,0 @@
export function supportsSvgAsImg() {
// @link: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
return document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
}
export function removeClassname(el, cls) {
if (el.classList) {
el.classList.remove(cls);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + cls.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
export function addClassname(el, cls) {
if (el.classList) {
el.classList.add(cls);
} else {
el.className += ' ' + cls;
}
}
export function hasClassname(el, cls) {
return el.className && new RegExp('(\\s|^)' + cls + '(\\s|$)').test(el.className);
}
export const cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
export const requestAnimationFrame =
window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
export function BrowserEvents() {
const callbacks = {
document: {
visibility: [],
},
window: {
resize: [],
scroll: [],
},
};
function onDocumentVisibilityChange() {
callbacks.document.visibility.map((fn) => fn());
}
function onWindowResize() {
callbacks.window.resize.map((fn) => fn());
}
function onWindowScroll() {
callbacks.window.scroll.map((fn) => fn());
}
function windowEvents(resizeCallback, scrollCallback) {
if ('function' === typeof resizeCallback) {
callbacks.window.resize.push(resizeCallback);
}
if ('function' === typeof scrollCallback) {
callbacks.window.scroll.push(scrollCallback);
}
}
function documentEvents(visibilityChangeCallback) {
if ('function' === typeof visibilityChangeCallback) {
callbacks.document.visibility.push(visibilityChangeCallback);
}
}
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
window.addEventListener('resize', onWindowResize);
window.addEventListener('scroll', onWindowScroll);
return {
doc: documentEvents,
win: windowEvents,
};
}

View File

@@ -0,0 +1,95 @@
export function supportsSvgAsImg() {
// @link: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/svg/asimg.js
return document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#Image', '1.1');
}
export function removeClassname(el: HTMLElement, cls: string) {
if (el.classList) {
el.classList.remove(cls);
} else {
el.className = el.className.replace(new RegExp('(^|\\b)' + cls.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
}
}
export function addClassname(el: HTMLElement, cls: string) {
if (el.classList) {
el.classList.add(cls);
} else {
el.className += ' ' + cls;
}
}
export function hasClassname(el: HTMLElement, cls: string) {
return el.className && new RegExp('(\\s|^)' + cls + '(\\s|$)').test(el.className);
}
type LegacyWindow = Window & {
mozCancelAnimationFrame?: Window['cancelAnimationFrame'];
mozRequestAnimationFrame?: Window['requestAnimationFrame'];
msRequestAnimationFrame?: Window['requestAnimationFrame'];
webkitRequestAnimationFrame?: Window['requestAnimationFrame'];
};
const legacyWindow = window as LegacyWindow;
export const cancelAnimationFrame: Window['cancelAnimationFrame'] =
legacyWindow.cancelAnimationFrame ||
legacyWindow.mozCancelAnimationFrame ||
((id: number) => window.clearTimeout(id));
export const requestAnimationFrame: Window['requestAnimationFrame'] =
legacyWindow.requestAnimationFrame ||
legacyWindow.mozRequestAnimationFrame ||
legacyWindow.webkitRequestAnimationFrame ||
legacyWindow.msRequestAnimationFrame ||
((callback: FrameRequestCallback) => window.setTimeout(() => callback(performance.now()), 16));
export function BrowserEvents() {
const callbacks = {
document: {
visibility: [] as Function[],
},
window: {
resize: [] as Function[],
scroll: [] as Function[],
},
};
function onDocumentVisibilityChange() {
callbacks.document.visibility.map((fn) => fn());
}
function onWindowResize() {
callbacks.window.resize.map((fn) => fn());
}
function onWindowScroll() {
callbacks.window.scroll.map((fn) => fn());
}
function windowEvents(resizeCallback?: Function, scrollCallback?: Function) {
if ('function' === typeof resizeCallback) {
callbacks.window.resize.push(resizeCallback);
}
if ('function' === typeof scrollCallback) {
callbacks.window.scroll.push(scrollCallback);
}
}
function documentEvents(visibilityChangeCallback?: Function) {
if ('function' === typeof visibilityChangeCallback) {
callbacks.document.visibility.push(visibilityChangeCallback);
}
}
document.addEventListener('visibilitychange', onDocumentVisibilityChange);
window.addEventListener('resize', onWindowResize);
window.addEventListener('scroll', onWindowScroll);
return {
doc: documentEvents,
win: windowEvents,
};
}

View File

@@ -7,7 +7,7 @@ export function inEmbeddedApp() {
sessionStorage.setItem('media_cms_embed_mode', 'true');
return true;
}
if (mode === 'standard') {
sessionStorage.removeItem('media_cms_embed_mode');
return false;

View File

@@ -1,27 +0,0 @@
// TODO: Improve or (even better) remove this file code.
import { error as logErrFn, warn as logWarnFn } from './log';
function logAndReturnError(logFn, msgArr, ErrorConstructor) {
let err;
switch (ErrorConstructor) {
case TypeError:
case RangeError:
case SyntaxError:
case ReferenceError:
err = new ErrorConstructor(msgArr[0]);
break;
default:
err = new Error(msgArr[0]);
}
logFn(err.message, ...msgArr.slice(1));
return err;
}
export function logErrorAndReturnError(msgArr, ErrorConstructor) {
return logAndReturnError(logErrFn, msgArr, ErrorConstructor);
}
export function logWarningAndReturnError(msgArr, ErrorConstructor) {
return logAndReturnError(logWarnFn, msgArr, ErrorConstructor);
}

View File

@@ -0,0 +1,15 @@
// @todo: Improve or (even better) remove this file.
import { error, warn } from './log';
export function logErrorAndReturnError(msgArr: string[]) {
const err = new Error(msgArr[0]);
error(...msgArr);
return err;
}
export function logWarningAndReturnError(msgArr: string[]) {
const err = new Error(msgArr[0]);
warn(...msgArr);
return err;
}

View File

@@ -1,5 +0,0 @@
import * as dispatcher from '../dispatcher.js';
export default function (store, handler) {
dispatcher.register(store[handler].bind(store));
return store;
}

View File

@@ -0,0 +1,28 @@
import EventEmitter from 'events';
import { dispatcher } from '../dispatcher';
// type ClassProperties<C> = {
// [Key in keyof C as C[Key] extends Function ? never : Key]: C[Key];
// };
type ClassMethods<C> = {
[Key in keyof C as C[Key] extends Function ? Key : never]: C[Key];
};
// @todo: Check this again
export function exportStore<TStore extends EventEmitter, THandler extends keyof ClassMethods<TStore>>(
store: TStore,
handler: THandler
) {
const method = store[handler] as Function;
const callback: (payload: unknown) => void = method.bind(store);
dispatcher.register(callback);
return store;
}
// @todo: Remove older vesion.
// export function exportStore_OLD(store, handler) {
// const callback = store[handler].bind(store);
// dispatcher.register(callback);
// return store;
// }

View File

@@ -1,11 +0,0 @@
import urlParse from 'url-parse';
export function formatInnerLink(url, baseUrl) {
let link = urlParse(url, {});
if ('' === link.origin || 'null' === link.origin || !link.origin) {
link = urlParse(baseUrl + '/' + url.replace(/^\//g, ''), {});
}
return link.toString();
}

View File

@@ -0,0 +1,11 @@
import urlParse from 'url-parse';
export function formatInnerLink(url: string, baseUrl: string) {
let link = urlParse(url, {});
if ('' === link.origin || 'null' === link.origin || !link.origin) {
link = urlParse(baseUrl + '/' + url.replace(/^\//g, ''), {});
}
return link.toString();
}

View File

@@ -1,15 +0,0 @@
import { months as monthList } from '../constants/';
export function formatManagementTableDate(date) {
const day = date.getDate();
const month = monthList[date.getMonth()].substring(0, 3);
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
let ret = month + ' ' + day + ', ' + year;
ret += ' ' + (hours < 10 ? '0' : '') + hours;
ret += ':' + (minutes < 10 ? '0' : '') + minutes;
ret += ':' + (seconds < 10 ? '0' : '') + seconds;
return ret;
}

View File

@@ -0,0 +1,15 @@
import { months as monthList } from '../constants';
export function formatManagementTableDate(date: Date) {
const day = date.getDate();
const month = monthList[date.getMonth()].substring(0, 3);
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
let ret = month + ' ' + day + ', ' + year;
ret += ' ' + (hours < 10 ? '0' : '') + hours;
ret += ':' + (minutes < 10 ? '0' : '') + minutes;
ret += ':' + (seconds < 10 ? '0' : '') + seconds;
return ret;
}

View File

@@ -1,18 +0,0 @@
export default function (views_number, fullNumber) {
function formattedValue(val, lim, unit) {
return Number(parseFloat(val / lim).toFixed(val < 10 * lim ? 1 : 0)) + unit;
}
function format(i, views, mult, compare, limit, units) {
while (views >= compare) {
limit *= mult;
compare *= mult;
i += 1;
}
return i < units.length
? formattedValue(views, limit, units[i])
: formattedValue(views * (mult * (i - (units.length - 1))), limit, units[units.length - 1]);
}
return fullNumber ? views_number.toLocaleString() : format(0, views_number, 1000, 1000, 1, ['', 'K', 'M', 'B', 'T']);
}

View File

@@ -0,0 +1,17 @@
const formattedValue = (val: number, lim: number, unit: string) =>
Number((val / lim).toFixed(val < 10 * lim ? 1 : 0)) + unit;
function format(cntr: number, views: number, mult: number, compare: number, limit: number, units: string[]) {
let i = cntr;
while (views >= compare) {
limit *= mult;
compare *= mult;
i += 1;
}
return i < units.length
? formattedValue(views, limit, units[i])
: formattedValue(views * (mult * (i - (units.length - 1))), limit, units[units.length - 1]);
}
export const formatViewsNumber = (views_number: number, fullNumber?: boolean) =>
fullNumber ? views_number.toLocaleString() : format(0, views_number, 1000, 1000, 1, ['', 'K', 'M', 'B', 'T']);

View File

@@ -1,7 +0,0 @@
export const imageExtension = (img) => {
if (!img) {
return;
}
const ext = img.split('.');
return ext[ext.length - 1];
};

View File

@@ -0,0 +1,5 @@
export const imageExtension = (img: string) => {
if (img) {
return img.split('.').pop();
}
};

View File

@@ -1,17 +0,0 @@
export * from './dom';
export * from './errors';
export { default as exportStore } from './exportStore';
export { formatInnerLink } from './formatInnerLink';
export * from './formatManagementTableDate';
export { default as formatViewsNumber } from './formatViewsNumber';
export * from './csrfToken';
export { imageExtension } from './imageExtension';
export * from './log';
export * from './math';
export * from './propTypeFilters';
export { default as publishedOnDate } from './publishedOnDate';
export * from './quickSort';
export * from './requests';
export { translateString } from './translate';
export { replaceString } from './replacementStrings';
export * from './embeddedApp';

View File

@@ -0,0 +1,17 @@
export * from './csrfToken';
export * from './dom';
export * from './embeddedApp';
export * from './errors';
export * from './exportStore';
export * from './formatInnerLink';
export * from './formatManagementTableDate';
export * from './formatViewsNumber';
export * from './imageExtension';
export * from './log';
export * from './math';
export * from './propTypeFilters';
export * from './publishedOnDate';
export * from './quickSort';
export * from './requests';
export * from './translate';
export * from './replacementStrings';

View File

@@ -1,4 +0,0 @@
const log = (...x) => console[x[0]](...x.slice(1));
export const warn = (...x) => log('warn', ...x);
export const error = (...x) => log('error', ...x);

View File

@@ -0,0 +1,9 @@
// @todo: Delete this file
export const warn = (...x: string[]) => {
console.warn(...x);
};
export const error = (...x: string[]) => {
console.error(...x);
};

View File

@@ -1,10 +0,0 @@
export const isGt = (x, y) => x > y;
export const isZero = (x) => 0 === x;
export const isNumber = (x) => !isNaN(x) && x === 0 + x;
export const isInteger = (x) => x === Math.trunc(x);
export const isPositive = (x) => isGt(x, 0);
export const isPositiveNumber = (x) => isNumber(x) && isPositive(x);
export const isPositiveInteger = (x) => isInteger(x) && isPositive(x);
export const isPositiveIntegerOrZero = (x) => isInteger(x) && (isPositive(x) || isZero(x));
export const greaterCommonDivision = (a, b) => (!b ? a : greaterCommonDivision(b, a % b));

View File

@@ -0,0 +1,10 @@
export const isGt = (x: number, y: number) => x > y;
export const isZero = (x: number) => 0 === x;
export const isNumber = (x: number) => 'number' === typeof x && !Number.isNaN(x);
export const isInteger = (x: number) => x === Math.trunc(x);
export const isPositive = (x: number) => isGt(x, 0);
export const isPositiveNumber = (x: number) => isNumber(x) && isPositive(x);
export const isPositiveInteger = (x: number) => isInteger(x) && isPositive(x);
export const isPositiveIntegerOrZero = (x: number) => isInteger(x) && (isPositive(x) || isZero(x));
export const greaterCommonDivision = (a: number, b: number): number => (!b ? a : greaterCommonDivision(b, a % b));

View File

@@ -1,10 +1,10 @@
import { logErrorAndReturnError } from './errors';
import { isPositiveInteger, isPositiveIntegerOrZero } from './math';
// @todo: Check this
export const PositiveIntegerOrZero = (function () {
const isPositiveIntegerOrZero = (x) => x === Math.trunc(x) && x >= 0;
return function (obj, key, comp) {
return void 0 === obj[key] || isPositiveIntegerOrZero(obj[key])
return function (obj: Record<string, number>, key: string, comp: string) {
return obj[key] === undefined || isPositiveIntegerOrZero(obj[key])
? null
: logErrorAndReturnError([
'Invalid prop `' +
@@ -20,11 +20,10 @@ export const PositiveIntegerOrZero = (function () {
};
})();
// @todo: Check this
export const PositiveInteger = (function () {
const isPositiveInteger = (x) => x === Math.trunc(x) && x > 0;
return function (obj, key, comp) {
return void 0 === obj[key] || isPositiveInteger(obj[key])
return function (obj: Record<string, number>, key: string, comp: string) {
return obj[key] === undefined || isPositiveInteger(obj[key])
? null
: logErrorAndReturnError([
'Invalid prop `' +

View File

@@ -1,17 +0,0 @@
import { months } from '../constants';
export default function publishedOnDate(date, type) {
if (date instanceof Date) {
type = 0 + type;
type = 0 < type ? type : 1;
switch (type) {
case 1:
return months[date.getMonth()].substring(0, 3) + ' ' + date.getDate() + ', ' + date.getFullYear();
case 2:
return date.getDate() + ' ' + months[date.getMonth()].substring(0, 3) + ' ' + date.getFullYear();
case 3:
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear();
}
}
return null;
}

View File

@@ -0,0 +1,17 @@
import { months } from '../constants';
export function publishedOnDate(date: Date, type: 1 | 2 | 3 = 1) {
if (!(date instanceof Date)) {
return null;
}
if (type === 2) {
return date.getDate() + ' ' + months[date.getMonth()].substring(0, 3) + ' ' + date.getFullYear();
}
if (type === 3) {
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear();
}
return months[date.getMonth()].substring(0, 3) + ' ' + date.getDate() + ', ' + date.getFullYear();
}

View File

@@ -1,35 +0,0 @@
function swap(arr, i, j) {
var temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function partition(arr, pivot, left, right) {
var pivotValue = arr[pivot],
partitionIndex = left;
for (var i = left; i < right; i++) {
if (arr[i] < pivotValue) {
swap(arr, i, partitionIndex);
partitionIndex++;
}
}
swap(arr, right, partitionIndex);
return partitionIndex;
}
export function quickSort(arr, left, right) {
var len = arr.length,
pivot,
partitionIndex;
if (left < right) {
pivot = right;
partitionIndex = partition(arr, pivot, left, right);
//sort left and right
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}

View File

@@ -0,0 +1,29 @@
function swap(arr: unknown[], i: number, j: number) {
const temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
function partition(arr: number[], pivot: number, left: number, right: number) {
const pivotValue = arr[pivot];
let partitionIndex = left;
for (let i = left; i < right; i++) {
if (arr[i] < pivotValue) {
swap(arr, i, partitionIndex);
partitionIndex++;
}
}
swap(arr, right, partitionIndex);
return partitionIndex;
}
export function quickSort(arr: number[], left: number, right: number) {
if (left < right) {
const pivot = right;
const partitionIndex = partition(arr, pivot, left, right);
//sort left and right
quickSort(arr, left, partitionIndex - 1);
quickSort(arr, partitionIndex + 1, right);
}
return arr;
}

View File

@@ -1,15 +0,0 @@
// check templates/config/installation/translations.html for more
export function replaceString(word) {
if (!window.REPLACEMENTS) {
return word;
}
let result = word;
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
result = result.split(search).join(replacement);
}
return result;
}

View File

@@ -0,0 +1,47 @@
// check templates/config/installation/translations.html for more
declare global {
interface Window {
REPLACEMENTS?: Record<string, string>;
}
}
export function replaceString(word: string) {
if (!window.REPLACEMENTS) {
return word;
}
let result = word;
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
result = result.split(search).join(replacement);
}
return result;
}
// @todo: Check this alterative.
/*function replaceStringRegExp(word: string) {
if (!window.REPLACEMENTS) {
return word;
}
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
let result = word;
for (const [search, replacement] of Object.entries(window.REPLACEMENTS)) {
const regex = new RegExp(escapeRegExp(search), 'g');
result = result.replace(regex, replacement);
}
return result;
}*/
// @todo: Remove older vesion.
/*export function replaceString_OLD(string: string) {
for (const key in window.REPLACEMENTS) {
string = string.replace(key, window.REPLACEMENTS[key]);
}
return string;
}*/

View File

@@ -1,135 +0,0 @@
import axios from 'axios';
export async function getRequest(url, sync, callback, errorCallback) {
const requestConfig = {
timeout: null,
maxContentLength: null,
};
function responseHandler(result) {
if (callback instanceof Function || typeof callback === 'function') {
callback(result);
}
}
function errorHandler(error) {
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
let err = error;
if (void 0 === error.response) {
err = {
type: 'network',
error: error,
};
} else if (void 0 !== error.response.status) {
// TODO: Improve this, it's valid only in case of media requests.
switch (error.response.status) {
case 401:
err = {
type: 'private',
error: error,
message: 'Media is private',
};
break;
case 400:
err = {
type: 'unavailable',
error: error,
message: 'Media is unavailable',
};
break;
}
}
errorCallback(err);
}
}
if (sync) {
await axios.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function postRequest(url, postData, configData, sync, callback, errorCallback) {
postData = postData || {};
function responseHandler(result) {
if (callback instanceof Function || typeof callback === 'function') {
callback(result);
}
}
function errorHandler(error) {
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
errorCallback(error);
}
}
if (sync) {
await axios.post(url, postData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios.post(url, postData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function putRequest(url, putData, configData, sync, callback, errorCallback) {
putData = putData || {};
function responseHandler(result) {
if (callback instanceof Function || typeof callback === 'function') {
callback(result);
}
}
function errorHandler(error) {
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
errorCallback(error);
}
}
if (sync) {
await axios.put(url, putData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios.put(url, putData, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function deleteRequest(url, configData, sync, callback, errorCallback) {
configData = configData || {};
function responseHandler(result) {
if (callback instanceof Function || typeof callback === 'function') {
callback(result);
}
}
function errorHandler(error) {
if (errorCallback instanceof Function || typeof errorCallback === 'function') {
errorCallback(error);
}
}
if (sync) {
await axios
.delete(url, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios
.delete(url, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}
}

View File

@@ -0,0 +1,169 @@
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
export async function getRequest(
url: string,
sync: boolean = false,
callback?: (response: AxiosResponse<any, any, {}>) => void,
errorCallback?: (err: any) => void
) {
const requestConfig = {
timeout: undefined,
maxContentLength: undefined,
};
function responseHandler(result: AxiosResponse<any, any, {}>) {
if (callback) {
callback(result);
}
}
function errorHandler(reason: any) {
if (!errorCallback) {
return;
}
let err = reason;
if (reason.response === undefined) {
err = {
type: 'network',
error: reason,
};
} else if (reason.response.status !== undefined) {
// @todo: Improve this, it's valid only in case of media requests.
switch (reason.response.status) {
case 401:
err = {
type: 'private',
error: reason,
message: 'Media is private',
};
break;
case 400:
err = {
type: 'unavailable',
error: reason,
message: 'Media is unavailable',
};
break;
}
}
errorCallback(err);
}
if (sync) {
await axios
.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios
.get(url, requestConfig)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function postRequest(
url: string,
postData: any,
configData?: AxiosRequestConfig<any>,
sync: boolean = false,
callback?: (response: AxiosResponse<any, any, {}>) => void,
errorCallback?: (error: any) => void
) {
postData = postData || {};
function responseHandler(result: AxiosResponse<any, any, {}>) {
if (callback) {
callback(result);
}
}
function errorHandler(error: any) {
if (errorCallback) {
errorCallback(error);
}
}
if (sync) {
await axios
.post(url, postData, configData)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios
.post(url, postData, configData)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function putRequest(
url: string,
putData: any,
configData?: AxiosRequestConfig<any>,
sync: boolean = false,
callback?: (response: AxiosResponse<any, any, {}>) => void,
errorCallback?: (error: any) => void
) {
putData = putData || {};
function responseHandler(result: AxiosResponse<any, any, {}>) {
if (callback) {
callback(result);
}
}
function errorHandler(error: any) {
if (errorCallback) {
errorCallback(error);
}
}
if (sync) {
await axios
.put(url, putData, configData)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios
.put(url, putData, configData)
.then(responseHandler)
.catch(errorHandler || null);
}
}
export async function deleteRequest(
url: string,
configData?: AxiosRequestConfig<any>,
sync: boolean = false,
callback?: (response: AxiosResponse<any, any, {}>) => void,
errorCallback?: (error: any) => void
) {
configData = configData || {};
function responseHandler(result: AxiosResponse<any, any, {}>) {
if (callback) {
callback(result);
}
}
function errorHandler(error: any) {
if (errorCallback) {
errorCallback(error);
}
}
if (sync) {
await axios
.delete(url, configData)
.then(responseHandler)
.catch(errorHandler || null);
} else {
axios
.delete(url, configData || null)
.then(responseHandler)
.catch(errorHandler || null);
}
}

View File

@@ -1,5 +0,0 @@
// check templates/config/installation/translations.html for more
export function translateString(str) {
return window.TRANSLATION?.[str] ?? str;
}

View File

@@ -0,0 +1,11 @@
// check templates/config/installation/translations.html for more
declare global {
interface Window {
TRANSLATION?: Record<string, string>;
}
}
export function translateString(word: string) {
return window.TRANSLATION?.[word] ?? word;
}

View File

@@ -1,19 +0,0 @@
import React from 'react';
import { useBulkActions } from '../hooks/useBulkActions';
/**
* Higher-Order Component that provides bulk actions functionality
* to class components via props
*/
export function withBulkActions(WrappedComponent) {
return function WithBulkActionsComponent(props) {
const bulkActions = useBulkActions();
return (
<WrappedComponent
{...props}
bulkActions={bulkActions}
/>
);
};
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
import { useBulkActions } from '../hooks/useBulkActions';
/**
* Higher-Order Component that provides bulk actions functionality
* to class components via props
*/
export function withBulkActions<P extends { bulkActions: ReturnType<typeof useBulkActions> }>(
WrappedComponent: React.ComponentType<P>
) {
return function WithBulkActionsComponent(props: Omit<P, 'bulkActions'>) {
const bulkActions = useBulkActions();
return <WrappedComponent {...(props as P)} bulkActions={bulkActions} />;
};
}

View File

@@ -1,59 +0,0 @@
const urlParse = require('url-parse');
let BASE_URL = null;
let ENDPOINTS = null;
function endpointsIter(ret, endpoints) {
const baseUrl = BASE_URL.toString().replace(/\/+$/, '');
for (let k in endpoints) {
if ('string' === typeof endpoints[k]) {
ret[k] = baseUrl + '/' + endpoints[k].replace(/^\//g, '');
} else {
endpointsIter(ret[k], endpoints[k]);
}
}
}
function formatEndpoints(endpoints) {
const baseUrl = BASE_URL.toString();
const ret = endpoints;
endpointsIter(ret, endpoints);
return ret;
}
export function init(base_url, endpoints) {
BASE_URL = urlParse(base_url);
ENDPOINTS = formatEndpoints({
media: endpoints.media,
featured: endpoints.media + '?show=featured',
recommended: endpoints.media + '?show=recommended',
playlists: endpoints.playlists,
users: endpoints.members,
user: {
liked: endpoints.liked,
history: endpoints.history,
playlists: endpoints.playlists + '?author=',
},
archive: {
tags: endpoints.tags,
categories: endpoints.categories,
},
manage: {
media: endpoints.manage_media,
users: endpoints.manage_users,
comments: endpoints.manage_comments,
},
search: {
query: endpoints.search + '?q=',
titles: endpoints.search + '?show=titles&q=',
tag: endpoints.search + '?t=',
category: endpoints.search + '?c=',
},
});
}
export function endpoints() {
return ENDPOINTS;
}

View File

@@ -0,0 +1,45 @@
import urlParse from 'url-parse'; // @todo: It's not necessary, 'URL.parse(...)' is sufficient
import { GlobalMediaCMS, MediaCMSConfig } from '../../types';
function formatEndpoints<K extends string = string>(baseUrl: string, endpoints: Record<K, string>) {
for (let k in endpoints) {
endpoints[k] = baseUrl + '/' + endpoints[k].replace(/^\//g, '');
}
return endpoints;
}
export function apiConfig(
apiUrl: GlobalMediaCMS['site']['api'],
endpoints: GlobalMediaCMS['api']
): MediaCMSConfig['api'] {
const baseUrl = urlParse(apiUrl).toString().replace(/\/+$/, '');
return {
...formatEndpoints(baseUrl, {
media: endpoints.media,
featured: endpoints.media + '?show=featured',
recommended: endpoints.media + '?show=recommended',
playlists: endpoints.playlists,
users: endpoints.members,
}),
user: formatEndpoints(baseUrl, {
liked: endpoints.liked,
history: endpoints.history,
playlists: endpoints.playlists + '?author=',
}),
archive: formatEndpoints(baseUrl, {
tags: endpoints.tags,
categories: endpoints.categories,
}),
manage: formatEndpoints(baseUrl, {
media: endpoints.manage_media,
users: endpoints.manage_users,
comments: endpoints.manage_comments,
}),
search: formatEndpoints(baseUrl, {
query: endpoints.search + '?q=',
titles: endpoints.search + '?show=titles&q=',
tag: endpoints.search + '?t=',
category: endpoints.search + '?c=',
}),
};
}

View File

@@ -1,113 +0,0 @@
import * as api from './api.js';
import * as media from './media.js';
import * as site from './site.js';
import * as theme from './theme.js';
import * as url from './url.js';
import * as member from './member.js';
import * as contents from './contents.js';
import * as pages from './pages.js';
import * as sidebar from './sidebar.js';
import * as taxonomies from './taxonomies.js';
import * as optionsPages from './optionsPages.js';
import * as optionsEmbedded from './optionsEmbedded.js';
import * as playlists from './playlists.js';
import * as notifications from './notifications.js';
let DATA = null;
export function config(glbl) {
if (DATA) {
return DATA;
}
pages.init({ ...glbl.site.pages, ...glbl.site.userPages });
optionsPages.init(glbl.pages.home, glbl.pages.search, glbl.pages.media, glbl.pages.profile, pages.settings());
url.init({
home: glbl.url.home,
admin: !glbl.user.is.anonymous && glbl.user.is.admin ? glbl.url.admin : '',
error404: glbl.url.error404,
embed: glbl.site.url.replace(/\/+$/, '') + '/embed?m=',
latest: glbl.url.latestMedia,
featured: glbl.url.featuredMedia,
recommended: glbl.url.recommendedMedia,
signin: glbl.url.signin,
signout: !glbl.user.is.anonymous ? glbl.url.signout : '',
register: glbl.url.register,
changePassword: !glbl.user.is.anonymous ? glbl.url.changePassword : '',
members: glbl.url.members,
search: {
base: glbl.url.search,
query: glbl.url.search + '?q=',
tag: glbl.url.search + '?t=',
category: glbl.url.search + '?c=',
},
profile: !!glbl.site.devEnv
? {
media: glbl.user.pages.media,
about: glbl.user.pages.about,
playlists: glbl.user.pages.playlists,
shared_by_me: glbl.user.pages.media + '/shared_by_me',
shared_with_me: glbl.user.pages.media + '/shared_with_me',
}
: {
media: glbl.site.url.replace(/\/$/, '') + '/user/' + glbl.profileId,
about: glbl.site.url.replace(/\/$/, '') + '/user/' + glbl.profileId + '/about',
playlists: glbl.site.url.replace(/\/$/, '') + '/user/' + glbl.profileId + '/playlists',
shared_by_me: glbl.site.url.replace(/\/$/, '') + '/user/' + glbl.profileId + '/shared_by_me',
shared_with_me: glbl.site.url.replace(/\/$/, '') + '/user/' + glbl.profileId + '/shared_with_me',
},
user: {
liked: glbl.url.likedMedia,
history: glbl.url.history,
addMedia: glbl.url.addMedia,
editChannel: glbl.url.editChannel,
editProfile: glbl.url.editProfile,
},
archive: {
tags: glbl.url.tags,
categories: glbl.url.categories,
},
manage: {
media: !glbl.user.is.anonymous ? glbl.url.manageMedia : '',
users: !glbl.user.is.anonymous ? glbl.url.manageUsers : '',
comments: !glbl.user.is.anonymous ? glbl.url.manageComments : '',
},
});
site.init(glbl.site);
contents.init(glbl.contents);
api.init(glbl.site.api, glbl.api);
sidebar.init(glbl.features.sideBar);
taxonomies.init(glbl.site.taxonomies);
member.init(glbl.user, glbl.features);
theme.init(glbl.site.theme, glbl.site.logo);
optionsEmbedded.init(glbl.features.embeddedVideo);
media.init(glbl.features.mediaItem, glbl.features.media.shareOptions);
playlists.init(glbl.features.playlists);
notifications.init(glbl.contents.notifications);
DATA = {
site: site.settings(),
theme: theme.settings(),
member: member.settings(),
media: media.settings(),
playlists: playlists.settings(),
url: url.pages(),
api: api.endpoints(),
sidebar: sidebar.settings(),
contents: contents.settings(),
options: {
pages: optionsPages.settings(),
embedded: optionsEmbedded.settings(),
},
enabled: {
pages: pages.settings(),
taxonomies: taxonomies.settings(),
},
notifications: notifications.settings(),
};
return DATA;
}

View File

@@ -0,0 +1,50 @@
import { GlobalMediaCMS, MediaCMSConfig } from '../../types';
import { apiConfig } from './api';
import { contentsConfig } from './contents';
import { mediaConfig } from './media';
import { memberConfig } from './member';
import { notificationsConfig } from './notifications';
import { optionsEmbeddedConfig } from './optionsEmbedded';
import { optionsPagesConfig } from './optionsPages';
import { pagesConfig } from './pages';
import { playlistsConfig } from './playlists';
import { sidebarConfig } from './sidebar';
import { siteConfig } from './site';
import { taxonomiesConfig } from './taxonomies';
import { themeConfig } from './theme';
import { urlConfig } from './url';
let DATA: MediaCMSConfig | null = null;
export function config(globalObj: GlobalMediaCMS) {
if (DATA) {
return DATA;
}
const { api, contents, features, pages, site, user } = globalObj;
const enabledPages = pagesConfig({ ...site.pages, ...site.userPages });
DATA = {
api: apiConfig(site.api, api),
contents: contentsConfig(contents),
enabled: {
pages: enabledPages,
taxonomies: taxonomiesConfig(site.taxonomies),
},
media: mediaConfig(features.mediaItem, features.media.shareOptions),
member: memberConfig(user, features),
notifications: notificationsConfig(contents.notifications),
options: {
pages: optionsPagesConfig(pages.home, pages.search, pages.media, pages.profile, enabledPages),
embedded: optionsEmbeddedConfig(features.embeddedVideo),
},
playlists: playlistsConfig(features.playlists),
sidebar: sidebarConfig(features.sideBar),
site: siteConfig(site),
theme: themeConfig(site.theme, site.logo),
url: urlConfig(globalObj),
};
return DATA;
}

View File

@@ -1,121 +0,0 @@
let CONTENTS = null;
function headerContents(contents) {
const ret = {
right: '',
onLogoRight: '',
};
if (void 0 !== contents) {
if ('string' === typeof contents.right) {
ret.right = contents.right.trim();
}
if ('string' === typeof contents.onLogoRight) {
ret.onLogoRight = contents.onLogoRight.trim();
}
}
return ret;
}
function sidebarContents(contents) {
const ret = {
navMenu: {
items: [],
},
mainMenuExtra: {
items: [],
},
belowNavMenu: '',
belowThemeSwitcher: '',
footer: '',
};
if (undefined !== contents) {
if (undefined !== contents.mainMenuExtraItems) {
let i = 0;
while (i < contents.mainMenuExtraItems.length) {
if (
'string' === typeof contents.mainMenuExtraItems[i].text &&
'string' === typeof contents.mainMenuExtraItems[i].link &&
'string' === typeof contents.mainMenuExtraItems[i].icon
) {
ret.mainMenuExtra.items.push({
text: contents.mainMenuExtraItems[i].text,
link: contents.mainMenuExtraItems[i].link,
icon: contents.mainMenuExtraItems[i].icon,
className: contents.mainMenuExtraItems[i].className,
});
}
i += 1;
}
}
if (undefined !== contents.navMenuItems) {
let i = 0;
while (i < contents.navMenuItems.length) {
if (
'string' === typeof contents.navMenuItems[i].text &&
'string' === typeof contents.navMenuItems[i].link &&
'string' === typeof contents.navMenuItems[i].icon
) {
ret.navMenu.items.push({
text: contents.navMenuItems[i].text,
link: contents.navMenuItems[i].link,
icon: contents.navMenuItems[i].icon,
className: contents.navMenuItems[i].className,
});
}
i += 1;
}
}
if ('string' === typeof contents.belowNavMenu) {
ret.belowNavMenu = contents.belowNavMenu.trim();
}
if ('string' === typeof contents.belowThemeSwitcher) {
ret.belowThemeSwitcher = contents.belowThemeSwitcher.trim();
}
if ('string' === typeof contents.footer) {
ret.footer = contents.footer.trim();
}
}
return ret;
}
function uploaderContents(contents) {
const ret = {
belowUploadArea: '',
postUploadMessage: '',
};
if (void 0 !== contents) {
if ('string' === typeof contents.belowUploadArea) {
ret.belowUploadArea = contents.belowUploadArea.trim();
}
if ('string' === typeof contents.postUploadMessage) {
ret.postUploadMessage = contents.postUploadMessage.trim();
}
}
return ret;
}
export function init(contents) {
CONTENTS = {
header: headerContents(contents.header),
sidebar: sidebarContents(contents.sidebar),
uploader: uploaderContents(contents.uploader),
};
}
export function settings() {
return CONTENTS;
}

View File

@@ -0,0 +1,67 @@
import { DeepPartial, GlobalMediaCMS, MediaCMSConfig } from '../../types';
const headerContents = (settings?: DeepPartial<GlobalMediaCMS['contents']['header']>) => ({
right: settings?.right !== undefined ? settings.right.trim() : '',
onLogoRight: settings?.onLogoRight !== undefined ? settings.onLogoRight.trim() : '',
});
function sidebarContents(settings?: DeepPartial<GlobalMediaCMS['contents']['sidebar']>) {
const sidebar: MediaCMSConfig['contents']['sidebar'] = {
belowNavMenu: settings?.belowNavMenu ? settings.belowNavMenu.trim() : '',
belowThemeSwitcher: settings?.belowThemeSwitcher ? settings.belowThemeSwitcher.trim() : '',
footer: settings?.footer ? settings.footer.trim() : '',
mainMenuExtra: { items: [] },
navMenu: { items: [] },
};
if (settings?.mainMenuExtraItems) {
for (const item of settings.mainMenuExtraItems) {
if (!item) {
continue;
}
const text = item.text ? item.text.trim() : '';
const link = item.link ? item.link.trim() : '';
const icon = item.icon ? item.icon.trim() : '';
const className = item.className ? item.className.trim() : '';
if (text && link && icon) {
sidebar.mainMenuExtra.items.push({ text, link, icon, className });
}
}
}
if (settings?.navMenuItems) {
for (const item of settings.navMenuItems) {
if (!item) {
continue;
}
const text = item.text ? item.text.trim() : '';
const link = item.link ? item.link.trim() : '';
const icon = item.icon ? item.icon.trim() : '';
const className = item.className ? item.className.trim() : '';
if (text && link && icon) {
sidebar.navMenu.items.push({ text, link, icon, className });
}
}
}
return sidebar;
}
const uploaderContents = (settings?: DeepPartial<GlobalMediaCMS['contents']['uploader']>) => ({
belowUploadArea: settings?.belowUploadArea ? settings?.belowUploadArea.trim() : '',
postUploadMessage: settings?.postUploadMessage ? settings?.postUploadMessage.trim() : '',
});
export const contentsConfig = (
settings?: DeepPartial<Omit<GlobalMediaCMS['contents'], 'notifications'>>
): MediaCMSConfig['contents'] => ({
header: headerContents(settings?.header),
sidebar: sidebarContents(settings?.sidebar),
uploader: uploaderContents(settings?.uploader),
});

View File

@@ -1,49 +0,0 @@
let MEDIA = null;
export function init(item, shareOptions) {
MEDIA = {
item: {
displayAuthor: true,
displayViews: true,
displayPublishDate: true,
},
share: {
options: [],
},
};
if (void 0 !== item) {
if (true === item.hideAuthor) {
MEDIA.item.displayAuthor = false;
}
if (true === item.hideViews) {
MEDIA.item.displayViews = false;
}
if (true === item.hideDate) {
MEDIA.item.displayPublishDate = false;
}
}
if (void 0 !== shareOptions) {
const validShareOptions = [
'embed',
'email',
];
let i = 0;
while (i < shareOptions.length) {
if (-1 < validShareOptions.indexOf(shareOptions[i])) {
MEDIA.share.options.push(shareOptions[i]);
}
i += 1;
}
}
}
export function settings() {
return MEDIA;
}

View File

@@ -0,0 +1,33 @@
import { DeepPartial, GlobalMediaCMS, MediaCMSConfig } from '../../types';
export function mediaConfig(
item?: DeepPartial<GlobalMediaCMS['features']['mediaItem']>,
shareOptions?: DeepPartial<GlobalMediaCMS['features']['media']['shareOptions']>
) {
const ret: MediaCMSConfig['media'] = {
item: {
displayAuthor: item?.hideAuthor === true ? false : true,
displayViews: item?.hideViews === true ? false : true,
displayPublishDate: item?.hideDate === true ? false : true,
},
share: { options: [] },
};
if (shareOptions) {
const validShareOptions = ['embed', 'email']; // @todo: Check this
for (const option of shareOptions) {
if (!option) {
continue;
}
const opt = option.trim();
if (validShareOptions.includes(opt)) {
ret.share.options.push(opt);
}
}
}
return ret;
}

View File

@@ -1,135 +0,0 @@
let MEMBER = null;
export function init(user, features) {
MEMBER = {
name: null,
username: null,
thumbnail: null,
is: {
admin: false,
anonymous: true,
},
can: {
login: true,
register: true,
addMedia: false,
editProfile: false,
canSeeMembersPage: true,
usersNeedsToBeApproved: true,
changePassword: true,
deleteProfile: false,
readComment: true,
addComment: false,
mentionComment: false,
deleteComment: false,
editMedia: false,
deleteMedia: false,
editSubtitle: false,
manageMedia: false,
manageUsers: false,
manageComments: false,
reportMedia: false,
downloadMedia: false,
saveMedia: false,
likeMedia: true,
dislikeMedia: true,
shareMedia: true,
contactUser: false,
},
pages: {
home: null,
about: null,
media: null,
playlists: null,
},
};
if (void 0 !== user) {
MEMBER.is.anonymous = true === user.is.anonymous ? true : false;
if (!MEMBER.is.anonymous) {
MEMBER.is.admin = true === user.is.admin;
MEMBER.name = 'string' === typeof user.name ? user.name.trim() : '';
MEMBER.name = '' === MEMBER.name ? null : MEMBER.name;
MEMBER.username = 'string' === typeof user.username ? user.username.trim() : '';
MEMBER.username = '' === MEMBER.username ? null : MEMBER.username;
MEMBER.thumbnail = 'string' === typeof user.thumbnail ? user.thumbnail.trim() : '';
MEMBER.thumbnail = '' === MEMBER.thumbnail ? null : MEMBER.thumbnail;
MEMBER.can.changePassword = false === user.can.changePassword ? false : MEMBER.can.changePassword;
MEMBER.can.deleteProfile = true === user.can.deleteProfile;
MEMBER.can.addComment = true === user.can.addComment;
MEMBER.can.mentionComment = true === user.can.mentionComment;
MEMBER.can.deleteComment = true === user.can.deleteComment;
MEMBER.can.editMedia = true === user.can.editMedia;
MEMBER.can.deleteMedia = true === user.can.deleteMedia;
MEMBER.can.editSubtitle = true === user.can.editSubtitle;
MEMBER.can.manageMedia = true === user.can.manageMedia;
MEMBER.can.manageUsers = true === user.can.manageUsers;
MEMBER.can.manageComments = true === user.can.manageComments;
MEMBER.can.contactUser = true === user.can.contactUser;
if (void 0 !== user.pages) {
if ('string' === typeof user.pages.about) {
MEMBER.pages.about = user.pages.about.trim();
MEMBER.pages.about = '' === MEMBER.pages.about ? null : MEMBER.pages.about;
}
if ('string' === typeof user.pages.media) {
MEMBER.pages.media = user.pages.media.trim();
MEMBER.pages.media = '' === MEMBER.pages.media ? null : MEMBER.pages.media;
}
if ('string' === typeof user.pages.playlists) {
MEMBER.pages.playlists = user.pages.playlists.trim();
MEMBER.pages.playlists = '' === MEMBER.pages.playlists ? null : MEMBER.pages.playlists;
}
}
}
MEMBER.can.canSeeMembersPage = true === user.can.canSeeMembersPage;
MEMBER.can.usersNeedsToBeApproved = true === user.can.usersNeedsToBeApproved;
MEMBER.can.addMedia = true === user.can.addMedia;
MEMBER.can.editProfile = true === user.can.editProfile;
MEMBER.can.readComment = false === user.can.readComment ? false : true;
}
if (void 0 !== features) {
if (void 0 !== features.media) {
if (void 0 !== features.media.actions) {
const mediaActions = features.media.actions;
MEMBER.can.addComment = MEMBER.can.addComment && true === mediaActions.comment;
MEMBER.can.mentionComment = MEMBER.can.mentionComment && true === mediaActions.comment_mention;
MEMBER.can.likeMedia = false === mediaActions.like ? false : true;
MEMBER.can.dislikeMedia = false === mediaActions.dislike ? false : true;
MEMBER.can.reportMedia = false === mediaActions.report ? false : true;
MEMBER.can.downloadMedia = true === mediaActions.download;
MEMBER.can.saveMedia = true === mediaActions.save;
MEMBER.can.shareMedia = true === mediaActions.share;
}
}
if (void 0 !== features.headerBar) {
if (true === features.headerBar.hideLogin) {
MEMBER.can.login = false;
}
if (true === features.headerBar.hideRegister) {
MEMBER.can.register = false;
}
}
}
}
export function settings() {
return MEMBER;
}

View File

@@ -0,0 +1,98 @@
import { DeepPartial, GlobalMediaCMS, MediaCMSConfig } from '../../types';
export function memberConfig(
user?: DeepPartial<GlobalMediaCMS['user']>,
features?: {
headerBar?: DeepPartial<GlobalMediaCMS['features']['headerBar']>;
media?: { actions?: DeepPartial<GlobalMediaCMS['features']['media']['actions']> };
}
) {
const ret: MediaCMSConfig['member'] = {
name: null,
username: null,
thumbnail: null,
is: { admin: false, anonymous: true },
can: {
login: true,
register: true,
addMedia: false,
editProfile: false,
canSeeMembersPage: true,
usersNeedsToBeApproved: true,
changePassword: true,
deleteProfile: false,
readComment: true,
addComment: false,
mentionComment: false,
deleteComment: false,
editMedia: false,
deleteMedia: false,
editSubtitle: false,
manageMedia: false,
manageUsers: false,
manageComments: false,
reportMedia: false,
downloadMedia: false,
saveMedia: false,
likeMedia: true,
dislikeMedia: true,
shareMedia: true,
contactUser: false,
},
pages: { home: null, about: null, media: null, playlists: null },
};
if (user) {
ret.is.anonymous = user.is?.anonymous === false ? false : true;
if (!ret.is.anonymous) {
ret.is.admin = user.is?.admin === true;
ret.name = (user.name ? user.name.trim() : null) || null;
ret.username = (user.username ? user.username.trim() : null) || null;
ret.thumbnail = (user.thumbnail ? user.thumbnail.trim() : null) || null;
ret.can.changePassword = user.can?.changePassword === false ? false : true;
ret.can.deleteProfile = user.can?.deleteProfile === true;
ret.can.addComment = user.can?.addComment === true;
ret.can.mentionComment = user.can?.mentionComment === true;
ret.can.deleteComment = user.can?.deleteComment === true;
ret.can.editMedia = user.can?.editMedia === true;
ret.can.deleteMedia = user.can?.deleteMedia === true;
ret.can.editSubtitle = user.can?.editSubtitle === true;
ret.can.manageMedia = user.can?.manageMedia === true;
ret.can.manageUsers = user.can?.manageUsers === true;
ret.can.manageComments = user.can?.manageComments === true;
ret.can.contactUser = user.can?.contactUser === true;
ret.pages.about = (user.pages?.about ? user.pages.about.trim() : null) || null;
ret.pages.media = (user.pages?.media ? user.pages.media.trim() : null) || null;
ret.pages.playlists = (user.pages?.playlists ? user.pages.playlists.trim() : null) || null;
}
ret.can.canSeeMembersPage = user.can?.canSeeMembersPage === false ? false : true;
ret.can.usersNeedsToBeApproved = user.can?.usersNeedsToBeApproved === false ? false : true;
ret.can.addMedia = user.can?.addMedia === true;
ret.can.editProfile = user.can?.editProfile === true;
ret.can.readComment = user.can?.readComment === false ? false : true;
}
const mediaActions = features?.media?.actions;
if (mediaActions) {
ret.can.addComment = ret.can.addComment && mediaActions?.comment === true;
ret.can.mentionComment = ret.can.mentionComment && mediaActions?.comment_mention === true;
ret.can.likeMedia = mediaActions?.like === false ? false : true;
ret.can.dislikeMedia = mediaActions?.dislike === false ? false : true;
ret.can.reportMedia = mediaActions?.report === false ? false : true;
ret.can.downloadMedia = mediaActions?.download === true;
ret.can.saveMedia = mediaActions?.save === true;
ret.can.shareMedia = mediaActions?.share === true;
}
ret.can.login = features?.headerBar?.hideLogin === true ? false : true;
ret.can.register = features?.headerBar?.hideRegister === true ? false : true;
return ret;
}

Some files were not shown because too many files have changed in this diff Show More