Compare commits

..

22 Commits

Author SHA1 Message Date
Yiannis Christodoulou 283d242325 build assets 2026-02-03 23:01:48 +02:00
Yiannis Christodoulou 2bf622f8f1 build assets 2026-02-03 22:59:15 +02:00
Yiannis Christodoulou 0753a9e803 Remove vertical aspect ratios from embed options
The aspect ratio dropdown in MediaShareEmbed now only includes horizontal orientation options (16:9, 4:3, 3:2) and the custom option. Vertical orientation options have been removed to simplify the selection.
2026-02-03 22:59:14 +02:00
Yiannis Christodoulou f293f584ec build assets 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou ecaa1ce37f feat: allow custom width/height in embed options without aspect ratio lock 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou ae2377f8d3 feat: remember user's embed video preferences in localStorage 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 59aaa6188b build assets 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 78214db44b Improve focus and scroll behavior for embedded video player
Prevent automatic focus on the video element when in embed mode to avoid unexpected behavior. Update scroll logic to only scroll the video into view if not in an iframe, or if explicitly requested via a URL parameter, improving user experience for embedded players.
2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 6e40b388e0 update static files 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 6729c6df13 Add full-screen video embed example
Introduces an HTML example for embedding a full-screen video using an iframe, styled for centered display and responsive aspect ratio.
2026-02-03 22:59:14 +02:00
Yiannis Christodoulou ff47daa7c9 Update MediaShareEmbed.jsx 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 2a0928aeaf Update default embed options in MediaShareEmbed
Changed initial states for keepAspectRatio, showTitle, and responsive options in MediaShareEmbed to improve default embed behavior. Also simplified aspect ratio change logic by removing unit options adjustments.
2026-02-03 22:59:14 +02:00
Yiannis Christodoulou b8b6801dac Remove test iframe HTML files
Deleted __test-iframe/index.html and __test-iframe/index2.html as they are no longer needed for testing embedded video functionality.
2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 9945e63a49 update static files 2026-02-03 22:59:14 +02:00
Yiannis Christodoulou 3d7a75a032 fix embed options 2026-02-03 22:57:20 +02:00
Yiannis Christodoulou 60d1137ac0 style: Improve embed options UI and logic
Refactored the embed options layout to use a grid for better alignment and spacing. The 'Link title' and 'Show user avatar' checkboxes are now disabled and visually dimmed when 'Show title' is unchecked, improving usability and clarity.
2026-02-03 22:53:20 +02:00
Yiannis Christodoulou f5613c932c feat: add linkTitle options to embed player
- Added 'linkTitle' to toggle between clickable and
2026-02-03 22:53:20 +02:00
Yiannis Christodoulou 694d80445c feat: add showUserAvatar option to video player and embed UI
- Added 'showUserAvatar' parameter to control author avatar visibility in embed info overlay
- Implemented UI toggle in MediaShareEmbed for the 'showUserAvatar' option
- Propagated 'showUserAvatar' through EmbedPage, VideoViewer, and VideoJSEmbed
- Updated test iframe index with all 8 combinations of showTitle, showRelated, and showUserAvatar
2026-02-03 22:53:20 +02:00
Yiannis Christodoulou 946304b46c feat: add showRelated option to video player and embed UI
- Added 'showRelated' parameter to control related videos visibility at video end
- Implemented UI toggle in MediaShareEmbed for the 'showRelated' option
- Updated EndScreenHandler to honor the 'showRelated' setting
- Modified EmbedPage and VideoJSEmbed to pass the parameter from URL to player
2026-02-03 22:53:19 +02:00
Yiannis Christodoulou 16468e173c build assets 2026-02-03 22:53:19 +02:00
Yiannis Christodoulou 217c3f7ebc feat: add showTitle option for embed videos
- Add showTitle prop support to EmbedInfoOverlay to conditionally show/hide title overlay
- Add showTitle checkbox option in MediaShareEmbed dialog with URL parameter support
- Update embed code generation to include showTitle parameter
- Add copy-url and copy-embed visual feedback icons to SeekIndicator
- Support showTitle prop in VideoJSEmbed component
2026-02-03 22:53:19 +02:00
Yiannis Christodoulou 91157da537 feat: add right-click context menu with share/embed options on video player
- Add VideoContextMenu component with copy URL, URL with timestamp, and embed code options
- Integrate context menu into VideoJSPlayer with proper event handling
- Add visual feedback via SeekIndicator when copying URLs/embed code
- Support embed mode with showTitle URL parameter handling
- Handle cross-origin iframe scenarios for embed URL generation
- Add helper functions for media ID, origin, and embed URL resolution
2026-02-03 22:53:19 +02:00
207 changed files with 3391 additions and 16864 deletions
@@ -1,22 +0,0 @@
name: "Lint PR"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
- reopened
permissions:
pull-requests: read
jobs:
main:
name: Validate PR title
runs-on: ubuntu-latest
environment: dev
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-47
View File
@@ -1,47 +0,0 @@
name: Semantic Release
on:
push:
branches:
- main
permissions:
contents: write
issues: write
jobs:
semantic-release:
runs-on: ubuntu-latest
environment: dev
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Setup SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.GA_DEPLOY_KEY }}
# use SSH url to ensure git commit using a deploy key bypasses the main
# branch protection rule
- name: Configure Git for SSH Push
run: git remote set-url origin "git@github.com:${{ github.repository }}.git"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "lts/*"
- name: Install Dependencies
run: npm clean-install
- name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
run: npm audit signatures
- name: Run Semantic Release
run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-100
View File
@@ -1,100 +0,0 @@
{
"branches": [
"main"
],
"plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [
{
"type": "feat",
"section": "Features"
},
{
"type": "fix",
"section": "Bug Fixes"
},
{
"type": "chore",
"hidden": true
},
{
"type": "docs",
"section": "Documentation"
},
{
"type": "style",
"hidden": true
},
{
"type": "refactor",
"section": "Refactors"
},
{
"type": "perf",
"section": "Performance"
},
{
"type": "test",
"hidden": true
},
{
"type": "depr",
"section": "Deprecations"
}
]
}
}
],
[
"semantic-release-replace-plugin",
{
"replacements": [
{
"files": [
"package.json"
],
"from": "\"version\": \".*\"",
"to": "\"version\": \"${nextRelease.version}\"",
"results": [
{
"file": "package.json",
"hasChanged": true,
"numMatches": 1,
"numReplacements": 1
}
],
"countMatches": true
}
]
}
],
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Changelog"
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"CHANGELOG.md"
],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
-43
View File
@@ -1,43 +0,0 @@
# 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
* bump version ([36d815c](https://github.com/mediacms-io/mediacms/commit/36d815c0cfbe21d3136541d410d545742b9ebecd))
## [7.4.0](https://github.com/mediacms-io/mediacms/compare/v7.3.0...v7.4.0) (2026-02-06)
### Features
* Add video player context menu with share/embed options ([#1472](https://github.com/mediacms-io/mediacms/issues/1472)) ([74952f6](https://github.com/mediacms-io/mediacms/commit/74952f68d79bc67617edb38eac62d2f5e7457565))
## [7.3.0](https://github.com/mediacms-io/mediacms/compare/v7.2.0...v7.3.0) (2026-02-06)
### Features
* add package json for semantic release ([b405a04](https://github.com/mediacms-io/mediacms/commit/b405a04e346ca81b7d3f4e099eb984e7785cdd0f))
* add semantic release github actions ([76a27ae](https://github.com/mediacms-io/mediacms/commit/76a27ae25609178c1bd47c947b9f1a082c791d61))
* frontend unit tests ([1c15880](https://github.com/mediacms-io/mediacms/commit/1c15880ae3ef1ce77f53d5b473dfc0cc448b4977))
* Implement persistent "Embed Mode" to hide UI shell via Session Storage ([#1484](https://github.com/mediacms-io/mediacms/issues/1484)) ([223e870](https://github.com/mediacms-io/mediacms/commit/223e87073f7d5e44130c9976854cac670db0ae66))
* Improve Visual Distinction Between Trim and Chapters Editors ([#1445](https://github.com/mediacms-io/mediacms/issues/1445)) ([d9b1d6c](https://github.com/mediacms-io/mediacms/commit/d9b1d6cab1d2bdfc16f799a0a27b64313e2e0d22))
* semantic release ([b76282f](https://github.com/mediacms-io/mediacms/commit/b76282f9e465a39c2da5e9a22184d1db23de3f56))
### Bug Fixes
* add delay to task creation ([1b3cdfd](https://github.com/mediacms-io/mediacms/commit/1b3cdfd302abc5e69ebe01ca52b5091f3b24c0b2))
* Add regex denoter and improve celerybeat gitignore ([#1446](https://github.com/mediacms-io/mediacms/issues/1446)) ([90331f3](https://github.com/mediacms-io/mediacms/commit/90331f3b4a2a5737de9dd75ab45c096944813c42))
* adjust poster url for audio ([01912ea](https://github.com/mediacms-io/mediacms/commit/01912ea1f99ef43793a65712539d6264f1f6410f))
* Chapter numbering and preserve custom titles on segment reorder ([#1435](https://github.com/mediacms-io/mediacms/issues/1435)) ([cd7dd4f](https://github.com/mediacms-io/mediacms/commit/cd7dd4f72c9f0bac466c680f686a9ecfdd3a38dd))
* Show default chapter names in textarea instead of placeholder text ([#1428](https://github.com/mediacms-io/mediacms/issues/1428)) ([5eb6faf](https://github.com/mediacms-io/mediacms/commit/5eb6fafb8c6928b8bc3fe5f0c7af315273f78a55))
* static files ([#1429](https://github.com/mediacms-io/mediacms/issues/1429)) ([ba2c31b](https://github.com/mediacms-io/mediacms/commit/ba2c31b1e65b7f508dee598b1f2d86f01f9bf036))
### Documentation
* update page link ([aeef828](https://github.com/mediacms-io/mediacms/commit/aeef8284bfba2a9a7f69c684f96c54f0e0e0cf92))
-54
View File
@@ -1,54 +0,0 @@
# 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
View File
@@ -1 +1 @@
VERSION = "7.9"
VERSION = "7.7"
+2 -3
View File
@@ -1,4 +1,3 @@
{
"editor.formatOnSave": true,
"prettier.configPath": "../.prettierrc"
}
"editor.formatOnSave": true
}
+1 -1
View File
@@ -5,5 +5,5 @@ module.exports = {
'^.+\\.tsx?$': 'ts-jest',
'^.+\\.jsx?$': 'babel-jest',
},
collectCoverageFrom: ['src/**', '!src/static/lib/**'],
collectCoverageFrom: ['src/**'],
};
-3
View File
@@ -21,9 +21,6 @@
"@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",
+1 -1
View File
@@ -55,7 +55,7 @@ export const HistoryPage: React.FC = () => {
const anonymousPage = isAnonymous || !PageStore.get('config-options').pages.profile.includeHistory;
if (!anonymousPage) {
addClassname(document.getElementById('page-history')!, 'profile-page-history');
addClassname(document.getElementById('page-history'), 'profile-page-history');
window.MediaCMS.profileId = username;
}
+3 -3
View File
@@ -76,7 +76,7 @@ export const HomePage: React.FC<HomePageProps> = ({
<MediaListRow
title={featured_title}
style={!visibleFeatured ? { display: 'none' } : undefined}
viewAllLink={featured_view_all_link ? links.featured : undefined}
viewAllLink={featured_view_all_link ? links.featured : null}
>
<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 : undefined}
viewAllLink={recommended_view_all_link ? links.recommended : null}
>
<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 : undefined}
viewAllLink={latest_view_all_link ? links.latest : null}
>
<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 +0,0 @@
export type DeepPartial<T> = T extends object ? { [P in keyof T]?: DeepPartial<T[P]> } : T;
@@ -1,212 +0,0 @@
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;
};
@@ -1,200 +0,0 @@
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
View File
@@ -1,3 +0,0 @@
export * from './DeepPartial';
export * from './GlobalMediaCMS';
export * from './MediaCMSConfig';
+90
View File
@@ -0,0 +1,90 @@
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,
});
}
@@ -1,63 +0,0 @@
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 });
}
+22
View File
@@ -0,0 +1,22 @@
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,
});
}
@@ -1,13 +0,0 @@
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 });
}
@@ -0,0 +1,41 @@
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,
});
}
@@ -1,26 +0,0 @@
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 });
}
@@ -0,0 +1,19 @@
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',
});
}
@@ -1,13 +0,0 @@
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' });
}
+13
View File
@@ -0,0 +1,13 @@
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',
});
}
@@ -1,9 +0,0 @@
import { dispatcher } from '../dispatcher';
export function load_author_data() {
dispatcher.dispatch({ type: 'LOAD_AUTHOR_DATA' });
}
export function remove_profile() {
dispatcher.dispatch({ type: 'REMOVE_PROFILE' });
}
@@ -0,0 +1,8 @@
import Dispatcher from '../dispatcher.js';
export function requestPredictions(query) {
Dispatcher.dispatch({
type: 'REQUEST_PREDICTIONS',
query,
});
}
@@ -1,5 +0,0 @@
import { dispatcher } from '../dispatcher';
export function requestPredictions(query: string) {
dispatcher.dispatch({ type: 'REQUEST_PREDICTIONS', query });
}
+36
View File
@@ -0,0 +1,36 @@
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,
});
}
@@ -1,23 +0,0 @@
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 });
}
@@ -0,0 +1,2 @@
export { default as months } from './months';
export { default as weekdays } from './weekdays';
@@ -1,2 +0,0 @@
export * from './months';
export * from './weekdays';
+14
View File
@@ -0,0 +1,14 @@
export default [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
@@ -1,14 +0,0 @@
export const months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
] as const;
+1
View File
@@ -0,0 +1 @@
export default ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
@@ -1 +0,0 @@
export const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] as const;
@@ -0,0 +1,5 @@
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;
@@ -1,5 +0,0 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const ApiUrlContext = createContext(mediacmsConfig(window.MediaCMS).api);
export const ApiUrlConsumer = ApiUrlContext.Consumer;
@@ -0,0 +1,130 @@
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;
@@ -1,130 +0,0 @@
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,15 +1,13 @@
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 React, { createContext, 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: NodeJS.Timeout | null = null;
let slidingSidebarTimeout;
function onSidebarVisibilityChange(visibleSidebar: boolean) {
if (slidingSidebarTimeout) {
clearTimeout(slidingSidebarTimeout);
}
function onSidebarVisibilityChange(visibleSidebar) {
clearTimeout(slidingSidebarTimeout);
addClassname(document.body, 'sliding-sidebar');
@@ -41,29 +39,18 @@ function onSidebarVisibilityChange(visibleSidebar: boolean) {
}, 20);
}
export const LayoutContext = createContext({
enabledSidebar: true,
visibleSidebar: true,
setVisibleSidebar: (_: boolean) => {},
visibleMobileSearch: false,
toggleMobileSearch: () => {},
toggleSidebar: () => {},
});
export const LayoutContext = createContext();
export const LayoutProvider = ({ children }: { children: ReactNode }) => {
export const LayoutProvider = ({ children }) => {
const site = useContext(SiteContext);
const cache = BrowserCache('MediaCMS[' + site.id + '][layout]', 86400);
const cache = new 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<boolean>(
cache instanceof Error
? true // @todo: Check this again
: cache.get('visible-sidebar')
);
const [visibleSidebar, setVisibleSidebar] = useState(cache.get('visible-sidebar'));
const [visibleMobileSearch, setVisibleMobileSearch] = useState(false);
const toggleMobileSearch = () => {
@@ -84,9 +71,7 @@ export const LayoutProvider = ({ children }: { children: ReactNode }) => {
}
if (!isEmbeddedApp && !isMediaPage && 1023 < window.innerWidth) {
if (!(cache instanceof Error)) {
cache.set('visible-sidebar', visibleSidebar);
}
cache.set('visible-sidebar', visibleSidebar);
}
}, [isEmbeddedApp, isMediaPage, visibleSidebar]);
@@ -1,5 +1,5 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const LinksContext = createContext(mediacmsConfig(window.MediaCMS).url);
export const LinksConsumer = LinksContext.Consumer;
@@ -1,5 +1,5 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const MemberContext = createContext(mediacmsConfig(window.MediaCMS).member);
export const MemberConsumer = MemberContext.Consumer;
@@ -0,0 +1,4 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const PlaylistsContext = createContext(mediacmsConfig(window.MediaCMS).playlists);
@@ -1,4 +0,0 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const PlaylistsContext = createContext(mediacmsConfig(window.MediaCMS).playlists);
@@ -0,0 +1,5 @@
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const ShareOptionsContext = createContext(mediacmsConfig(window.MediaCMS).media.share.options);
@@ -1,4 +0,0 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
export const ShareOptionsContext = createContext(mediacmsConfig(window.MediaCMS).media.share.options);
@@ -0,0 +1,5 @@
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;
@@ -1,5 +0,0 @@
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 { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
export const SiteContext = createContext(mediacmsConfig(window.MediaCMS).site);
export const SiteConsumer = SiteContext.Consumer;
@@ -1,9 +1,11 @@
import { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config';
import React, { createContext } from 'react';
import { config as mediacmsConfig } from '../settings/config.js';
const notifications = mediacmsConfig(window.MediaCMS).notifications.messages;
const texts = { notifications };
const texts = {
notifications,
};
export const TextsContext = createContext(texts);
@@ -0,0 +1,80 @@
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;
@@ -1,95 +0,0 @@
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;
@@ -0,0 +1,22 @@
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;
@@ -1,28 +0,0 @@
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;
+2
View File
@@ -0,0 +1,2 @@
const Dispatcher = require('flux').Dispatcher;
module.exports = new Dispatcher();
@@ -1,3 +0,0 @@
import { Dispatcher } from 'flux';
export const dispatcher = new Dispatcher();
+19
View File
@@ -0,0 +1,19 @@
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;
}
@@ -1,18 +0,0 @@
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;
}
+79
View File
@@ -0,0 +1,79 @@
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,
};
}
@@ -1,95 +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: 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;
+27
View File
@@ -0,0 +1,27 @@
// 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);
}
@@ -1,15 +0,0 @@
// @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;
}
+5
View File
@@ -0,0 +1,5 @@
import * as dispatcher from '../dispatcher.js';
export default function (store, handler) {
dispatcher.register(store[handler].bind(store));
return store;
}
@@ -1,28 +0,0 @@
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;
// }
+11
View File
@@ -0,0 +1,11 @@
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();
}
@@ -1,11 +0,0 @@
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();
}
@@ -0,0 +1,15 @@
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;
}
@@ -1,15 +0,0 @@
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;
}
+18
View File
@@ -0,0 +1,18 @@
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']);
}
@@ -1,17 +0,0 @@
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']);
+7
View File
@@ -0,0 +1,7 @@
export const imageExtension = (img) => {
if (!img) {
return;
}
const ext = img.split('.');
return ext[ext.length - 1];
};
@@ -1,5 +0,0 @@
export const imageExtension = (img: string) => {
if (img) {
return img.split('.').pop();
}
};
@@ -0,0 +1,17 @@
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';
@@ -1,17 +0,0 @@
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';
+4
View File
@@ -0,0 +1,4 @@
const log = (...x) => console[x[0]](...x.slice(1));
export const warn = (...x) => log('warn', ...x);
export const error = (...x) => log('error', ...x);
@@ -1,9 +0,0 @@
// @todo: Delete this file
export const warn = (...x: string[]) => {
console.warn(...x);
};
export const error = (...x: string[]) => {
console.error(...x);
};
+10
View File
@@ -0,0 +1,10 @@
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));
@@ -1,10 +0,0 @@
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 () {
return function (obj: Record<string, number>, key: string, comp: string) {
return obj[key] === undefined || isPositiveIntegerOrZero(obj[key])
const isPositiveIntegerOrZero = (x) => x === Math.trunc(x) && x >= 0;
return function (obj, key, comp) {
return void 0 === obj[key] || isPositiveIntegerOrZero(obj[key])
? null
: logErrorAndReturnError([
'Invalid prop `' +
@@ -20,10 +20,11 @@ export const PositiveIntegerOrZero = (function () {
};
})();
// @todo: Check this
export const PositiveInteger = (function () {
return function (obj: Record<string, number>, key: string, comp: string) {
return obj[key] === undefined || isPositiveInteger(obj[key])
const isPositiveInteger = (x) => x === Math.trunc(x) && x > 0;
return function (obj, key, comp) {
return void 0 === obj[key] || isPositiveInteger(obj[key])
? null
: logErrorAndReturnError([
'Invalid prop `' +
+17
View File
@@ -0,0 +1,17 @@
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;
}
@@ -1,17 +0,0 @@
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();
}
+35
View File
@@ -0,0 +1,35 @@
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;
}
@@ -1,29 +0,0 @@
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;
}
@@ -0,0 +1,15 @@
// 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;
}
@@ -1,47 +0,0 @@
// 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;
}*/
@@ -0,0 +1,135 @@
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);
}
}
@@ -1,169 +0,0 @@
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);
}
}
@@ -0,0 +1,5 @@
// check templates/config/installation/translations.html for more
export function translateString(str) {
return window.TRANSLATION?.[str] ?? str;
}
@@ -1,11 +0,0 @@
// 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;
}
@@ -0,0 +1,19 @@
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}
/>
);
};
}
@@ -1,15 +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<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} />;
};
}
+59
View File
@@ -0,0 +1,59 @@
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;
}
@@ -1,45 +0,0 @@
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=',
}),
};
}
@@ -0,0 +1,113 @@
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;
}
@@ -1,50 +0,0 @@
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;
}
+121
View File
@@ -0,0 +1,121 @@
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;
}
@@ -1,67 +0,0 @@
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),
});
+49
View File
@@ -0,0 +1,49 @@
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;
}

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