mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-03-22 12:33:11 -04:00
Compare commits
15 Commits
ltimoodle1
...
frontend-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20682a543a | ||
|
|
c8b47a7922 | ||
|
|
499196b0f6 | ||
|
|
374ae4de6e | ||
|
|
7a5fca6fd8 | ||
|
|
e9af15582f | ||
|
|
1b8e8aae6a | ||
|
|
df4b0422d5 | ||
|
|
0434f24691 | ||
|
|
c2043fafa1 | ||
|
|
9f9dd699b2 | ||
|
|
e2bc9399b9 | ||
|
|
45d94069b9 | ||
|
|
b7427869b6 | ||
|
|
11449c2187 |
@@ -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
54
SECURITY.md
Normal 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.
|
||||
@@ -1 +1 @@
|
||||
VERSION = "7.5"
|
||||
VERSION = "7.9"
|
||||
|
||||
5
frontend/.vscode/settings.json
vendored
5
frontend/.vscode/settings.json
vendored
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
"editor.formatOnSave": true,
|
||||
"prettier.configPath": "../.prettierrc"
|
||||
}
|
||||
|
||||
@@ -5,5 +5,5 @@ module.exports = {
|
||||
'^.+\\.tsx?$': 'ts-jest',
|
||||
'^.+\\.jsx?$': 'babel-jest',
|
||||
},
|
||||
collectCoverageFrom: ['src/**'],
|
||||
collectCoverageFrom: ['src/**', '!src/static/lib/**'],
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
1
frontend/src/static/js/types/DeepPartial.ts
Normal file
1
frontend/src/static/js/types/DeepPartial.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
|
||||
212
frontend/src/static/js/types/GlobalMediaCMS.ts
Normal file
212
frontend/src/static/js/types/GlobalMediaCMS.ts
Normal 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;
|
||||
};
|
||||
200
frontend/src/static/js/types/MediaCMSConfig.ts
Normal file
200
frontend/src/static/js/types/MediaCMSConfig.ts
Normal 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;
|
||||
};
|
||||
3
frontend/src/static/js/types/index.ts
Normal file
3
frontend/src/static/js/types/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './DeepPartial';
|
||||
export * from './GlobalMediaCMS';
|
||||
export * from './MediaCMSConfig';
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
63
frontend/src/static/js/utils/actions/MediaPageActions.ts
Executable file
63
frontend/src/static/js/utils/actions/MediaPageActions.ts
Executable 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 });
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
13
frontend/src/static/js/utils/actions/PageActions.ts
Executable file
13
frontend/src/static/js/utils/actions/PageActions.ts
Executable 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 });
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
26
frontend/src/static/js/utils/actions/PlaylistPageActions.ts
Executable file
26
frontend/src/static/js/utils/actions/PlaylistPageActions.ts
Executable 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 });
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
13
frontend/src/static/js/utils/actions/PlaylistViewActions.ts
Executable file
13
frontend/src/static/js/utils/actions/PlaylistViewActions.ts
Executable 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' });
|
||||
}
|
||||
@@ -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',
|
||||
});
|
||||
}
|
||||
9
frontend/src/static/js/utils/actions/ProfilePageActions.ts
Executable file
9
frontend/src/static/js/utils/actions/ProfilePageActions.ts
Executable 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' });
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import Dispatcher from '../dispatcher.js';
|
||||
|
||||
export function requestPredictions(query) {
|
||||
Dispatcher.dispatch({
|
||||
type: 'REQUEST_PREDICTIONS',
|
||||
query,
|
||||
});
|
||||
}
|
||||
5
frontend/src/static/js/utils/actions/SearchFieldActions.ts
Executable file
5
frontend/src/static/js/utils/actions/SearchFieldActions.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
import { dispatcher } from '../dispatcher';
|
||||
|
||||
export function requestPredictions(query: string) {
|
||||
dispatcher.dispatch({ type: 'REQUEST_PREDICTIONS', query });
|
||||
}
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
23
frontend/src/static/js/utils/actions/VideoViewerActions.ts
Executable file
23
frontend/src/static/js/utils/actions/VideoViewerActions.ts
Executable 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 });
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
export { default as months } from './months';
|
||||
export { default as weekdays } from './weekdays';
|
||||
2
frontend/src/static/js/utils/constants/index.ts
Normal file
2
frontend/src/static/js/utils/constants/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './months';
|
||||
export * from './weekdays';
|
||||
@@ -1,14 +0,0 @@
|
||||
export default [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
14
frontend/src/static/js/utils/constants/months.ts
Executable file
14
frontend/src/static/js/utils/constants/months.ts
Executable file
@@ -0,0 +1,14 @@
|
||||
export const months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
] as const;
|
||||
@@ -1 +0,0 @@
|
||||
export default ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
1
frontend/src/static/js/utils/constants/weekdays.ts
Executable file
1
frontend/src/static/js/utils/constants/weekdays.ts
Executable file
@@ -0,0 +1 @@
|
||||
export const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;
|
||||
@@ -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;
|
||||
5
frontend/src/static/js/utils/contexts/ApiUrlContext.ts
Normal file
5
frontend/src/static/js/utils/contexts/ApiUrlContext.ts
Normal 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;
|
||||
@@ -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;
|
||||
130
frontend/src/static/js/utils/contexts/HeaderContext.ts
Normal file
130
frontend/src/static/js/utils/contexts/HeaderContext.ts
Normal 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;
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createContext } from 'react';
|
||||
import { config as mediacmsConfig } from '../settings/config';
|
||||
|
||||
export const PlaylistsContext = createContext(mediacmsConfig(window.MediaCMS).playlists);
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
5
frontend/src/static/js/utils/contexts/SidebarContext.ts
Normal file
5
frontend/src/static/js/utils/contexts/SidebarContext.ts
Normal 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;
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
95
frontend/src/static/js/utils/contexts/ThemeContext.tsx
Normal file
95
frontend/src/static/js/utils/contexts/ThemeContext.tsx
Normal 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;
|
||||
@@ -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;
|
||||
28
frontend/src/static/js/utils/contexts/UserContext.tsx
Normal file
28
frontend/src/static/js/utils/contexts/UserContext.tsx
Normal 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;
|
||||
@@ -1,2 +0,0 @@
|
||||
const Dispatcher = require('flux').Dispatcher;
|
||||
module.exports = new Dispatcher();
|
||||
3
frontend/src/static/js/utils/dispatcher.ts
Executable file
3
frontend/src/static/js/utils/dispatcher.ts
Executable file
@@ -0,0 +1,3 @@
|
||||
import { Dispatcher } from 'flux';
|
||||
|
||||
export const dispatcher = new Dispatcher();
|
||||
@@ -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;
|
||||
}
|
||||
18
frontend/src/static/js/utils/helpers/csrfToken.ts
Executable file
18
frontend/src/static/js/utils/helpers/csrfToken.ts
Executable 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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
95
frontend/src/static/js/utils/helpers/dom.ts
Executable file
95
frontend/src/static/js/utils/helpers/dom.ts
Executable 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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
15
frontend/src/static/js/utils/helpers/errors.ts
Executable file
15
frontend/src/static/js/utils/helpers/errors.ts
Executable 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;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import * as dispatcher from '../dispatcher.js';
|
||||
export default function (store, handler) {
|
||||
dispatcher.register(store[handler].bind(store));
|
||||
return store;
|
||||
}
|
||||
28
frontend/src/static/js/utils/helpers/exportStore.ts
Executable file
28
frontend/src/static/js/utils/helpers/exportStore.ts
Executable 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;
|
||||
// }
|
||||
@@ -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();
|
||||
}
|
||||
11
frontend/src/static/js/utils/helpers/formatInnerLink.ts
Executable file
11
frontend/src/static/js/utils/helpers/formatInnerLink.ts
Executable 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
15
frontend/src/static/js/utils/helpers/formatManagementTableDate.ts
Executable file
15
frontend/src/static/js/utils/helpers/formatManagementTableDate.ts
Executable 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;
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
17
frontend/src/static/js/utils/helpers/formatViewsNumber.ts
Executable file
17
frontend/src/static/js/utils/helpers/formatViewsNumber.ts
Executable 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']);
|
||||
@@ -1,7 +0,0 @@
|
||||
export const imageExtension = (img) => {
|
||||
if (!img) {
|
||||
return;
|
||||
}
|
||||
const ext = img.split('.');
|
||||
return ext[ext.length - 1];
|
||||
};
|
||||
5
frontend/src/static/js/utils/helpers/imageExtension.ts
Executable file
5
frontend/src/static/js/utils/helpers/imageExtension.ts
Executable file
@@ -0,0 +1,5 @@
|
||||
export const imageExtension = (img: string) => {
|
||||
if (img) {
|
||||
return img.split('.').pop();
|
||||
}
|
||||
};
|
||||
@@ -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';
|
||||
17
frontend/src/static/js/utils/helpers/index.ts
Normal file
17
frontend/src/static/js/utils/helpers/index.ts
Normal 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';
|
||||
@@ -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);
|
||||
9
frontend/src/static/js/utils/helpers/log.ts
Executable file
9
frontend/src/static/js/utils/helpers/log.ts
Executable 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);
|
||||
};
|
||||
@@ -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));
|
||||
10
frontend/src/static/js/utils/helpers/math.ts
Executable file
10
frontend/src/static/js/utils/helpers/math.ts
Executable 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));
|
||||
@@ -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 `' +
|
||||
@@ -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;
|
||||
}
|
||||
17
frontend/src/static/js/utils/helpers/publishedOnDate.ts
Executable file
17
frontend/src/static/js/utils/helpers/publishedOnDate.ts
Executable 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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
29
frontend/src/static/js/utils/helpers/quickSort.ts
Executable file
29
frontend/src/static/js/utils/helpers/quickSort.ts
Executable 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
47
frontend/src/static/js/utils/helpers/replacementStrings.ts
Normal file
47
frontend/src/static/js/utils/helpers/replacementStrings.ts
Normal 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;
|
||||
}*/
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
169
frontend/src/static/js/utils/helpers/requests.ts
Normal file
169
frontend/src/static/js/utils/helpers/requests.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// check templates/config/installation/translations.html for more
|
||||
|
||||
export function translateString(str) {
|
||||
return window.TRANSLATION?.[str] ?? str;
|
||||
}
|
||||
11
frontend/src/static/js/utils/helpers/translate.ts
Normal file
11
frontend/src/static/js/utils/helpers/translate.ts
Normal 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;
|
||||
}
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
15
frontend/src/static/js/utils/hoc/withBulkActions.tsx
Normal file
15
frontend/src/static/js/utils/hoc/withBulkActions.tsx
Normal 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} />;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
45
frontend/src/static/js/utils/settings/api.ts
Executable file
45
frontend/src/static/js/utils/settings/api.ts
Executable 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=',
|
||||
}),
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
50
frontend/src/static/js/utils/settings/config.ts
Normal file
50
frontend/src/static/js/utils/settings/config.ts
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
67
frontend/src/static/js/utils/settings/contents.ts
Executable file
67
frontend/src/static/js/utils/settings/contents.ts
Executable 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),
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
33
frontend/src/static/js/utils/settings/media.ts
Executable file
33
frontend/src/static/js/utils/settings/media.ts
Executable 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
98
frontend/src/static/js/utils/settings/member.ts
Executable file
98
frontend/src/static/js/utils/settings/member.ts
Executable 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
Reference in New Issue
Block a user