mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-21 13:57:57 -05:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa6520daac | ||
|
|
060bb45725 | ||
|
|
1f0cc4ff87 | ||
|
|
c28a39fa47 | ||
|
|
d17b3b4153 | ||
|
|
950adcdd9d | ||
|
|
235efbe151 | ||
|
|
8145ba0914 | ||
|
|
f74d3c4b57 | ||
|
|
9b9a718a18 | ||
|
|
26804dce40 | ||
|
|
de30fe68f1 | ||
|
|
4f72c00598 | ||
|
|
d3a3934ce7 | ||
|
|
ddbaa51285 | ||
|
|
7fae5992e7 | ||
|
|
46384a3c49 | ||
|
|
cdcf148b72 | ||
|
|
09e565e99b | ||
|
|
7bda0acd8b | ||
|
|
748d4bae4b | ||
|
|
171b9f84d7 | ||
|
|
853b28130d |
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
35
.github/workflows/python.yml
vendored
Normal file
35
.github/workflows/python.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Python Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Build the Stack
|
||||
run: docker-compose -f docker-compose-dev.yaml build
|
||||
|
||||
- name: Start containers
|
||||
run: docker-compose -f docker-compose-dev.yaml up -d
|
||||
|
||||
- name: List containers
|
||||
run: docker ps
|
||||
|
||||
- name: Sleep for 60 seconds
|
||||
run: sleep 60s
|
||||
shell: bash
|
||||
|
||||
- name: Run Django Tests
|
||||
run: docker-compose -f docker-compose-dev.yaml exec -T web pytest
|
||||
|
||||
- name: Tear down the Stack
|
||||
run: docker-compose -f docker-compose-dev.yaml down
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -12,5 +12,6 @@ static/ckeditor/
|
||||
static/debug_toolbar/
|
||||
static/mptt/
|
||||
static/rest_framework/
|
||||
static/drf-yasg
|
||||
cms/local_settings.py
|
||||
deploy/docker/local_settings.py
|
||||
|
||||
@@ -7,7 +7,7 @@ ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs,pids} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
RUN mkdir -p /home/mediacms.io/mediacms/{logs} && cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
# Install dependencies:
|
||||
COPY requirements.txt .
|
||||
|
||||
16
Dockerfile-dev
Normal file
16
Dockerfile-dev
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM mediacms/mediacms:latest
|
||||
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
|
||||
# Set up virtualenv
|
||||
ENV VIRTUAL_ENV=/home/mediacms.io
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
ENV PIP_NO_CACHE_DIR=1
|
||||
|
||||
RUN cd /home/mediacms.io && python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
COPY requirements.txt .
|
||||
COPY requirements-dev.txt .
|
||||
RUN pip install -r requirements-dev.txt
|
||||
|
||||
WORKDIR /home/mediacms.io/mediacms
|
||||
97
README.md
97
README.md
@@ -1,4 +1,13 @@
|
||||

|
||||
# MediaCMS
|
||||
|
||||
[](https://lgtm.com/projects/g/mediacms-io/mediacms/context:python)
|
||||
[](https://lgtm.com/projects/g/mediacms-io/mediacms/context:javascript)
|
||||
<br/>
|
||||
[](https://raw.githubusercontent.com/mediacms-io/mediacms/main/LICENSE.txt)
|
||||
[](https://github.com/mediacms-io/mediacms/releases/)
|
||||
[](https://hub.docker.com/repository/docker/mediacms/mediacms/)
|
||||
|
||||
|
||||
|
||||
MediaCMS is a modern, fully featured open source video and media CMS. It is developed to meet the needs of modern web platforms for viewing and sharing media. It can be used to build a small to medium video and media portal within minutes.
|
||||
|
||||
@@ -34,6 +43,7 @@ A demo is available at https://demo.mediacms.io
|
||||
- **Subtitles/CC**: support for multilingual subtitle files
|
||||
- **Scalable transcoding**: transcoding through priorities. Experimental support for remote workers
|
||||
- **Chunked file uploads**: for pausable/resumable upload of content
|
||||
- **REST API**: Documented through Swagger
|
||||
|
||||
|
||||
## Example cases
|
||||
@@ -73,95 +83,26 @@ For a small to medium installation, with a few hours of video uploaded daily, an
|
||||
In terms of disk space, think of what the needs will be. A general rule is to multiply by three the size of the expected uploaded videos (since the system keeps original versions, encoded versions plus HLS), so if you receive 1G of videos daily and maintain all of them, you should consider a 1T disk across a year (1G * 3 * 365).
|
||||
|
||||
|
||||
## Installation
|
||||
## Installation / Maintanance
|
||||
|
||||
There are two ways to run MediaCMS, through Docker Compose and through installing it on a server via an automation script that installs and configures all needed services.
|
||||
There are two ways to run MediaCMS, through Docker Compose and through installing it on a server via an automation script that installs and configures all needed services. Find the related pages:
|
||||
|
||||
### Docker Compose installation
|
||||
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
* [Single Server](docs/Single_Server.md) page
|
||||
* [Docker Compose](docs/Docker_Compose.md) page
|
||||
|
||||
Run as root
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd mediacms
|
||||
```
|
||||
|
||||
The default option is to serve MediaCMS on all ips available of the server (including localhost).
|
||||
|
||||
Now run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
|
||||
|
||||
For more instructions, checkout the docs on the [Docker deployment](docs/Docker_deployment.md) page. Docker Compose support has been contributed by @swiftugandan.
|
||||
|
||||
|
||||
### Single server installation
|
||||
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
|
||||
|
||||
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
|
||||
Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
|
||||
|
||||
```bash
|
||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd /home/mediacms.io/mediacms/ && bash ./install.sh
|
||||
```
|
||||
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
If you've used the above way to install MediaCMS, update with the following:
|
||||
|
||||
```bash
|
||||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||
source /home/mediacms.io/bin/activate # use virtualenv
|
||||
git pull # update code
|
||||
python manage.py migrate # run Django migrations
|
||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
||||
```
|
||||
|
||||
|
||||
## Configure
|
||||
## Configuration
|
||||
|
||||
Several options are available on cms/settings.py, most of the things that are allowed or should be disallowed are described there. It is advisable to override any of them by adding it to cms/local_settings.py. All configuration options will be documented gradually on the [Configuration](docs/Configuration.md) page.
|
||||
|
||||
## Authors
|
||||
MediaCMS is developed by Yiannis Stergiou and Markos Gogoulos. We are Wordgames - https://wordgames.gr.
|
||||
|
||||
|
||||
## Technology
|
||||
This software uses the following list of awesome technologies:
|
||||
- Python
|
||||
- Django
|
||||
- Django Rest Framework
|
||||
- Celery
|
||||
- PostgreSQL
|
||||
- Redis
|
||||
- Nginx
|
||||
- uWSGI
|
||||
- React
|
||||
- Fine Uploader
|
||||
- video.js
|
||||
- FFMPEG
|
||||
- Bento4
|
||||
This software uses the following list of awesome technologies: Python, Django, Django Rest Framework, Celery, PostgreSQL, Redis, Nginx, uWSGI, React, Fine Uploader, video.js, FFMPEG, Bento4
|
||||
|
||||
|
||||
## Who is using it
|
||||
|
||||
- **Cinemata** non-profit media, technology and culture organization - https://cinemata.org
|
||||
|
||||
- **Critical Commons** public media archive and fair use advocacy network - https://criticalcommons.org
|
||||
|
||||
- **Heritales** International Heritage Film Festival - https://stage.heritales.org
|
||||
|
||||
|
||||
@@ -178,10 +119,10 @@ If you like the project, here's a few things you can do
|
||||
|
||||
## Developers info
|
||||
|
||||
- API documentation available under /swagger URL (example https://demo.mediacms.io/swagger/)
|
||||
- API documentation through Swagger is available under /swagger URL of your installation -example https://demo.mediacms.io/swagger/
|
||||
- We're working on proper documentation for users, managers and developers, until then checkout what's available on the docs/ folder of this repository
|
||||
- Before you send a PR, make sure your code is properly formatted. For that, use `pre-commit install` to install a pre-commit hook and run `pre-commit run --all` and fix everything before you commit. This pre-commit will check for your code lint everytime you commit a code.
|
||||
- Checkout the [Code of conduct page](CODE_OF_CONDUCT.md) if you want to contribute to this repository
|
||||
|
||||
## Contact
|
||||
info@mediacms.io
|
||||
info@mediacms.io
|
||||
@@ -349,6 +349,16 @@ FILE_UPLOAD_HANDLERS = [
|
||||
|
||||
LOGS_DIR = os.path.join(BASE_DIR, "logs")
|
||||
|
||||
error_filename = os.path.join(LOGS_DIR, "debug.log")
|
||||
if not os.path.exists(LOGS_DIR):
|
||||
try:
|
||||
os.mkdir(LOGS_DIR)
|
||||
except PermissionError:
|
||||
pass
|
||||
|
||||
if not os.path.isfile(error_filename):
|
||||
open(error_filename, 'a').close()
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
@@ -356,7 +366,7 @@ LOGGING = {
|
||||
"file": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.FileHandler",
|
||||
"filename": os.path.join(LOGS_DIR, "debug.log"),
|
||||
"filename": error_filename,
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
@@ -427,9 +437,13 @@ CELERY_BEAT_SCHEDULE = {
|
||||
|
||||
LOCAL_INSTALL = False
|
||||
|
||||
# this is an option to make the whole portal available to logged in users only
|
||||
# it is placed here so it can be overrided on local_settings.py
|
||||
GLOBAL_LOGIN_REQUIRED = False
|
||||
|
||||
try:
|
||||
# keep a local_settings.py file for local overrides
|
||||
from .local_settings import *
|
||||
from .local_settings import * # noqa
|
||||
|
||||
# ALLOWED_HOSTS needs a url/ip
|
||||
ALLOWED_HOSTS.append(FRONTEND_HOST.replace("http://", "").replace("https://", ""))
|
||||
@@ -446,3 +460,12 @@ if LOCAL_INSTALL:
|
||||
SSL_FRONTEND_HOST = FRONTEND_HOST.replace("http", "https")
|
||||
else:
|
||||
SSL_FRONTEND_HOST = FRONTEND_HOST
|
||||
|
||||
if GLOBAL_LOGIN_REQUIRED:
|
||||
# this should go after the AuthenticationMiddleware middleware
|
||||
MIDDLEWARE.insert(5, "login_required.middleware.LoginRequiredMiddleware")
|
||||
LOGIN_REQUIRED_IGNORE_PATHS = [
|
||||
r'/accounts/login/$',
|
||||
r'/accounts/logout/$',
|
||||
r'/accounts/signup/$',
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@ import debug_toolbar
|
||||
from django.conf.urls import include, url
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path
|
||||
from django.views.generic.base import TemplateView
|
||||
from drf_yasg import openapi
|
||||
from drf_yasg.views import get_schema_view
|
||||
from rest_framework.permissions import AllowAny
|
||||
@@ -15,6 +16,10 @@ schema_view = get_schema_view(
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^__debug__/", include(debug_toolbar.urls)),
|
||||
path(
|
||||
"robots.txt",
|
||||
TemplateView.as_view(template_name="robots.txt", content_type="text/plain"),
|
||||
),
|
||||
url(r"^", include("files.urls")),
|
||||
url(r"^", include("users.urls")),
|
||||
url(r"^accounts/", include("allauth.urls")),
|
||||
|
||||
5
conftest.py
Normal file
5
conftest.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pytest_factoryboy import register
|
||||
|
||||
from tests.users.factories import UserFactory
|
||||
|
||||
register(UserFactory)
|
||||
@@ -7,11 +7,11 @@ ln -sf /dev/stdout /var/log/nginx/mediacms.io.access.log && ln -sf /dev/stderr /
|
||||
|
||||
cp /home/mediacms.io/mediacms/deploy/docker/local_settings.py /home/mediacms.io/mediacms/cms/local_settings.py
|
||||
|
||||
mkdir -p /home/mediacms.io/mediacms/{logs,pids,media_files/hls}
|
||||
mkdir -p /home/mediacms.io/mediacms/{logs,media_files/hls}
|
||||
touch /home/mediacms.io/mediacms/logs/debug.log
|
||||
|
||||
# Remove any dangling pids
|
||||
rm -rf /home/mediacms.io/mediacms/pids/*
|
||||
mkdir -p /var/run/mediacms
|
||||
chown www-data:www-data /var/run/mediacms
|
||||
|
||||
TARGET_GID=$(stat -c "%g" /home/mediacms.io/mediacms/)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
client_max_body_size 1g;
|
||||
client_max_body_size 5800M;
|
||||
|
||||
@@ -9,4 +9,4 @@ user=www-data
|
||||
directory=/home/mediacms.io/mediacms
|
||||
priority=300
|
||||
startinorder=true
|
||||
command=/home/mediacms.io/bin/celery beat --pidfile=/home/mediacms.io/mediacms/pids/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log
|
||||
command=/home/mediacms.io/bin/celery beat --pidfile=/var/run/mediacms/beat%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_beat.log
|
||||
|
||||
@@ -10,4 +10,4 @@ directory=/home/mediacms.io/mediacms
|
||||
priority=500
|
||||
startinorder=true
|
||||
startsecs=0
|
||||
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/home/mediacms.io/mediacms/pids/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks
|
||||
command=/home/mediacms.io/bin/celery multi start long1 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_long.log -Ofair --prefetch-multiplier=1 -Q long_tasks
|
||||
|
||||
@@ -9,4 +9,4 @@ user=www-data
|
||||
directory=/home/mediacms.io/mediacms
|
||||
priority=400
|
||||
startinorder=true
|
||||
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/home/mediacms.io/mediacms/pids/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks
|
||||
command=/home/mediacms.io/bin/celery multi start short1 short2 --pidfile=/var/run/mediacms/%%n.pid --loglevel=INFO --logfile=/home/mediacms.io/mediacms/logs/celery_short.log --soft-time-limit=300 -c10 -Q short_tasks
|
||||
|
||||
13
deploy/local_install/dhparams.pem
Normal file
13
deploy/local_install/dhparams.pem
Normal file
@@ -0,0 +1,13 @@
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIICCAKCAgEAo3MMiEY/fNbu+usIM0cDi6x8G3JBApv0Lswta4kiyedWT1WN51iQ
|
||||
9zhOFpmcu6517f/fR9MUdyhVKHxxSqWQTcmTEFtz4P3VLTS/W1N5VbKE2VEMLpIi
|
||||
wr350aGvV1Er0ujcp5n4O4h0I1tn4/fNyDe7+pHCdwM+hxe8hJ3T0/tKtad4fnIs
|
||||
WHDjl4f7m7KuFfheiK7Efb8MsT64HDDAYXn+INjtDZrbE5XPw20BqyWkrf07FcPx
|
||||
8o9GW50Ox7/FYq7jVMI/skEu0BRc8u6uUD9+UOuWUQpdeHeFcvLOgW53Z03XwWuX
|
||||
RXosUKzBPuGtUDAaKD/HsGW6xmGr2W9yRmu27jKpfYLUb/eWbbnRJwCw04LdzPqv
|
||||
jmtq02Gioo3lf5H5wYV9IYF6M8+q/slpbttsAcKERimD1273FBRt5VhSugkXWKjr
|
||||
XDhoXu6vZgj8Opei38qPa8pI1RUFoXHFlCe6WpZQmU8efL8gAMrJr9jUIY8eea1n
|
||||
u20t5B9ueb9JMjrNafcq6QkKhZLi6fRDDTUyeDvc0dN9R/3Yts97SXfdi1/lX7HS
|
||||
Ht4zXd5hEkvjo8GcnjsfZpAC39QfHWkDaeUGEqsl3jXjVMfkvoVY51OuokPWZzrJ
|
||||
M5+wyXNpfGbH67dPk7iHgN7VJvgX0SYscDPTtms50Vk7RwEzLeGuSHMCAQI=
|
||||
-----END DH PARAMETERS-----
|
||||
@@ -46,6 +46,12 @@ server {
|
||||
|
||||
ssl_certificate_key /etc/letsencrypt/live/localhost/privkey.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/localhost/fullchain.pem;
|
||||
ssl_dhparam /etc/nginx/dhparams/dhparams.pem;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_ecdh_curve secp521r1:secp384r1;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
gzip on;
|
||||
access_log /var/log/nginx/mediacms.io.access.log;
|
||||
|
||||
@@ -19,10 +19,7 @@ http {
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Dropping SSLv3, ref: POODLE
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
|
||||
66
docker-compose-dev.yaml
Normal file
66
docker-compose-dev.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
version: "3"
|
||||
|
||||
services:
|
||||
frontend:
|
||||
image: node:14
|
||||
volumes:
|
||||
- ${PWD}/frontend:/home/mediacms.io/mediacms/frontend/
|
||||
working_dir: /home/mediacms.io/mediacms/frontend/
|
||||
command: bash -c "npm install && npm run start"
|
||||
env_file:
|
||||
- ${PWD}/frontend/.env
|
||||
ports:
|
||||
- "8088:8088"
|
||||
depends_on:
|
||||
- web
|
||||
web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile-dev
|
||||
image: mediacms/mediacms-dev:latest
|
||||
ports:
|
||||
- "80:80"
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
selenium_hub:
|
||||
container_name: selenium_hub
|
||||
image: selenium/hub
|
||||
ports:
|
||||
- "4444:4444"
|
||||
selenium_chrome:
|
||||
container_name: selenium_chrome
|
||||
image: selenium/node-chrome-debug
|
||||
environment:
|
||||
- HUB_PORT_4444_TCP_ADDR=selenium_hub
|
||||
- HUB_PORT_4444_TCP_PORT=4444
|
||||
ports:
|
||||
- "5900:5900"
|
||||
depends_on:
|
||||
- selenium_hub
|
||||
db:
|
||||
image: postgres
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
115
docker-compose-letsencrypt.yaml
Normal file
115
docker-compose-letsencrypt.yaml
Normal file
@@ -0,0 +1,115 @@
|
||||
version: "3"
|
||||
|
||||
# Uses https://github.com/nginx-proxy/acme-companion
|
||||
|
||||
services:
|
||||
nginx-proxy:
|
||||
image: nginxproxy/nginx-proxy
|
||||
container_name: nginx-proxy
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- dhparam:/etc/nginx/dhparam
|
||||
- certs:/etc/nginx/certs:ro
|
||||
- /var/run/docker.sock:/tmp/docker.sock:ro
|
||||
- ./deploy/docker/reverse_proxy/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro
|
||||
|
||||
acme-companion:
|
||||
image: nginxproxy/acme-companion
|
||||
container_name: nginx-proxy-acme
|
||||
volumes_from:
|
||||
- nginx-proxy
|
||||
volumes:
|
||||
- certs:/etc/nginx/certs:rw
|
||||
- acme:/etc/acme.sh
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
migrations:
|
||||
image: mediacms/mediacms:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
depends_on:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
db:
|
||||
condition: service_healthy
|
||||
web:
|
||||
image: mediacms/mediacms:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
VIRTUAL_HOST: 'mediacms.52.209.5.113.nip.io'
|
||||
LETSENCRYPT_HOST: 'mediacms.52.209.5.113.nip.io'
|
||||
LETSENCRYPT_EMAIL: 'email@example.com'
|
||||
depends_on:
|
||||
- migrations
|
||||
celery_beat:
|
||||
image: mediacms/mediacms:latest
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_SHORT: 'no'
|
||||
ENABLE_CELERY_LONG: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- redis
|
||||
celery_worker:
|
||||
image: mediacms/mediacms:latest
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./:/home/mediacms.io/mediacms/
|
||||
environment:
|
||||
ENABLE_UWSGI: 'no'
|
||||
ENABLE_NGINX: 'no'
|
||||
ENABLE_CELERY_BEAT: 'no'
|
||||
ENABLE_MIGRATIONS: 'no'
|
||||
depends_on:
|
||||
- migrations
|
||||
db:
|
||||
image: postgres
|
||||
volumes:
|
||||
- ../postgres_data:/var/lib/postgresql/data/
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_USER: mediacms
|
||||
POSTGRES_PASSWORD: mediacms
|
||||
POSTGRES_DB: mediacms
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mediacms"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 5
|
||||
redis:
|
||||
image: "redis:alpine"
|
||||
restart: always
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli","ping"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
volumes:
|
||||
conf:
|
||||
vhost:
|
||||
html:
|
||||
dhparam:
|
||||
certs:
|
||||
acme:
|
||||
133
docs/Add A Static Page To The Sidebar.md
Normal file
133
docs/Add A Static Page To The Sidebar.md
Normal file
@@ -0,0 +1,133 @@
|
||||
## How To Add A Static Page To The Sidebar
|
||||
|
||||
### 1. Create your html page in templates/cms/
|
||||
e.g. duplicate and rename about.html
|
||||
```
|
||||
sudo cp templates/cms/about.html templates/cms/volunteer.html
|
||||
```
|
||||
|
||||
### 2. Create your css file in static/css/
|
||||
```
|
||||
touch static/css/volunteer.css
|
||||
```
|
||||
|
||||
### 3. In your html file, update block headermeta to reflect your new page
|
||||
```
|
||||
{% block headermeta %}
|
||||
<meta property="og:title" content="Volunteer - {{PORTAL_NAME}}">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:description" content="">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "{{PORTAL_NAME}}",
|
||||
"item": {
|
||||
"@type": "WebPage",
|
||||
"@id": "{{FRONTEND_HOST}}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "Volunteer",
|
||||
"item": {
|
||||
"@type": "VolunteerPage",
|
||||
"@id": "{{FRONTEND_HOST}}/volunteer"
|
||||
}
|
||||
}]
|
||||
}
|
||||
</script>
|
||||
<link href="{% static "css/volunteer.css" %}" rel="stylesheet"/>
|
||||
{% endblock headermeta %}
|
||||
```
|
||||
|
||||
### 4. In your html file, update block innercontent to reflect your actual content
|
||||
Write whatever you like.
|
||||
|
||||
### 5. In your css file, write matching styles for you html file.
|
||||
Write whatever you like.
|
||||
|
||||
### 6. Add your view to files/views.py
|
||||
```
|
||||
def volunteer(request):
|
||||
"""Volunteer view"""
|
||||
context = {}
|
||||
return render(request, "cms/volunteer.html", context)
|
||||
```
|
||||
|
||||
### 7. Add your url pattern to files/urls.py
|
||||
```
|
||||
urlpatterns = [
|
||||
url(r"^$", views.index),
|
||||
url(r"^about", views.about, name="about"),
|
||||
url(r"^volunteer", views.volunteer, name="volunteer"),
|
||||
```
|
||||
|
||||
### 8. Add your page to the left sidebar
|
||||
To add a link to your page as a menu item in the left sidebar,
|
||||
add the following code after the last line in _commons.js
|
||||
```
|
||||
/* Checks that a given selector has loaded. */
|
||||
const checkElement = async selector => {
|
||||
while ( document.querySelector(selector) === null) {
|
||||
await new Promise( resolve => requestAnimationFrame(resolve) )
|
||||
}
|
||||
return document.querySelector(selector);
|
||||
};
|
||||
|
||||
/* Checks that sidebar nav menu has loaded, then adds menu item. */
|
||||
checkElement('.nav-menu')
|
||||
.then((element) => {
|
||||
(function(){
|
||||
var a = document.createElement('a');
|
||||
a.href = "/volunteer";
|
||||
a.title = "Volunteer";
|
||||
|
||||
var s = document.createElement('span');
|
||||
s.className = "menu-item-icon";
|
||||
|
||||
var icon = document.createElement('i');
|
||||
icon.className = "material-icons";
|
||||
icon.setAttribute("data-icon", "people");
|
||||
|
||||
s.appendChild(icon);
|
||||
a.appendChild(s);
|
||||
|
||||
var linkText = document.createTextNode("Volunteer");
|
||||
var t = document.createElement('span');
|
||||
|
||||
t.appendChild(linkText);
|
||||
a.appendChild(t);
|
||||
|
||||
var listItem = document.createElement('li');
|
||||
listItem.className = "link-item";
|
||||
listItem.appendChild(a);
|
||||
|
||||
//if signed out use 3rd nav-menu
|
||||
var elem = document.querySelector(".nav-menu:nth-child(3) nav ul");
|
||||
var loc = elem.innerText;
|
||||
if (loc.includes("About")){
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
} else { //if signed in use 4th nav-menu
|
||||
elem = document.querySelector(".nav-menu:nth-child(4) nav ul");
|
||||
elem.insertBefore(listItem, elem.children[2]);
|
||||
}
|
||||
})();
|
||||
});
|
||||
```
|
||||
|
||||
### 9. Restart the mediacms web server
|
||||
On docker:
|
||||
```
|
||||
sudo docker stop mediacms_web_1 && sudo docker start mediacms_web_1
|
||||
```
|
||||
|
||||
Otherwise
|
||||
```
|
||||
sudo systemctl restart mediacms
|
||||
```
|
||||
@@ -8,12 +8,19 @@ In case of a the single server installation, add to `cms/local_settings.py` .
|
||||
|
||||
In case of a docker compose installation, add to `deploy/docker/local_settings.py` . This will automatically overwrite `cms/local_settings.py` .
|
||||
|
||||
Any change needs restart of MediaCMS in order to take effect. So edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
Any change needs restart of MediaCMS in order to take effect.
|
||||
|
||||
```
|
||||
Single server installation: edit `cms/local_settings.py`, make a change and restart MediaCMS
|
||||
|
||||
```bash
|
||||
#systemctl restart mediacms
|
||||
```
|
||||
|
||||
Docker Compose installation: edit `deploy/docker/local_settings.py`, make a change and restart MediaCMS containers
|
||||
|
||||
```bash
|
||||
#docker-compose restart web celery_worker celery_beat
|
||||
```
|
||||
|
||||
### change portal logo
|
||||
|
||||
@@ -254,3 +261,10 @@ ADMINS_NOTIFICATIONS = {
|
||||
- NEW_USER: a new user is added
|
||||
- MEDIA_ADDED: a media is added
|
||||
- MEDIA_REPORTED: the report for a media was hit
|
||||
|
||||
|
||||
|
||||
### Google Analytics
|
||||
|
||||
Checkout the instructions by alberto98fx on [Google Analytics](/docs/robots_and_analytics.md) page.
|
||||
|
||||
|
||||
52
docs/Docker_Compose.md
Normal file
52
docs/Docker_Compose.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Docker Compose
|
||||
|
||||
## Installation
|
||||
Install a recent version of [Docker](https://docs.docker.com/get-docker/), and [Docker Compose](https://docs.docker.com/compose/install/).
|
||||
|
||||
For Ubuntu 18/20 systems this is:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
```
|
||||
|
||||
Then run as root
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd mediacms
|
||||
```
|
||||
|
||||
The default option is to serve MediaCMS on all ips available of the server (including localhost).
|
||||
If you want to explore more options (including setup of https with letsencrypt certificate) checkout the docs on the [Docker deployment](/docs/Docker_deployment.md) page for different docker-compose setups to use.
|
||||
|
||||
Run
|
||||
|
||||
```bash
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
This will download all MediaCMS related Docker images and start all containers. Once it finishes, MediaCMS will be installed and available on http://localhost or http://ip
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
Get latest MediaCMS image and stop/start containers
|
||||
|
||||
```bash
|
||||
cd /path/to/mediacms/installation
|
||||
docker pull mediacms/mediacms
|
||||
docker-compose down
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Checkout the configuration docs on [Configuration](/docs/Configuration.md) page.
|
||||
|
||||
|
||||
## Maintenance
|
||||
Database is stored on ../postgres_data/ and media_files on media_files/
|
||||
|
||||
|
||||
@@ -17,23 +17,36 @@ See example deployments in the sections below. These example deployments have be
|
||||
|
||||
To run, update the configs above if necessary, build the image by running `docker-compose build`, then run `docker-compose run`
|
||||
|
||||
## Simple Deployment, accessed as http://localhost
|
||||
# Simple Deployment, accessed as http://localhost
|
||||
|
||||
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
|
||||
The main container runs migrations, mediacms_web, celery_beat, celery_workers (celery_short and celery_long services), exposed on port 80 supported by redis and postgres database.
|
||||
|
||||
## Advanced Deployment, accessed as http://localhost:8000
|
||||
The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://localhost, on the docker host machine.
|
||||
|
||||
# Server with ssl certificate through letsencrypt service, accessed as https://my_domain.com
|
||||
Before trying this out make sure the ip points to my_domain.com.
|
||||
|
||||
With this method [this deployment](../docker-compose-letsencrypt.yaml) is used.
|
||||
|
||||
Edit this file and set `VIRTUAL_HOST` as my_domain.com, `LETSENCRYPT_HOST` as my_domain.com, and your email on `LETSENCRYPT_EMAIL`
|
||||
|
||||
Edit `deploy/docker/local_settings.py` and set https://my_domain.com as `FRONTEND_HOST`
|
||||
|
||||
Now run docker-compose -f docker-compose-letsencrypt.yaml up, when installation finishes you will be able to access https://my_domain.com using a valid Letsencrypt certificate!
|
||||
|
||||
# Advanced Deployment, accessed as http://localhost:8000
|
||||
|
||||
Here we can run 1 mediacms_web instance, with the FRONTEND_HOST in `deploy/docker/local_settings.py` configured as http://localhost:8000. This is bootstrapped by a single migrations instance and supported by a single celery_beat instance and 1 or more celery_worker instances. Redis and postgres containers are also used for persistence. Clients can access the service on http://localhost:8000, on the docker host machine. This is similar to [this deployment](../docker-compose.yaml), with a `port` defined in FRONTEND_HOST.
|
||||
|
||||
## Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
|
||||
# Advanced Deployment, with reverse proxy, accessed as http://mediacms.io
|
||||
|
||||
Here we can use `jwilder/nginx-proxy` to reverse proxy to 1 or more instances of mediacms_web supported by other services as mentioned in the previous deployment. The FRONTEND_HOST in `deploy/docker/local_settings.py` is configured as http://mediacms.io, nginx-proxy has port 80 exposed. Clients can access the service on http://mediacms.io (Assuming DNS or the hosts file is setup correctly to point to the IP of the nginx-proxy instance). This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
## Advanced Deployment, with reverse proxy, accessed as https://localhost
|
||||
# Advanced Deployment, with reverse proxy, accessed as https://localhost
|
||||
|
||||
The reverse proxy (`jwilder/nginx-proxy`) can be configured to provide SSL termination using self-signed certificates, letsencrypt or CA signed certificates (see: https://hub.docker.com/r/jwilder/nginx-proxy or [LetsEncrypt Example](https://www.singularaspect.com/use-nginx-proxy-and-letsencrypt-companion-to-host-multiple-websites/) ). In this case the FRONTEND_HOST should be set to https://mediacms.io. This is similar to [this deployment](../docker-compose-http-proxy.yaml).
|
||||
|
||||
## A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
|
||||
# A Scaleable Deployment Architecture (Docker, Swarm, Kubernetes)
|
||||
|
||||
The architecture below generalises all the deployment scenarios above, and provides a conceptual design for other deployments based on kubernetes and docker swarm. It allows for horizontal scaleability through the use of multiple mediacms_web instances and celery_workers. For large deployments, managed postgres, redis and storage may be adopted.
|
||||
|
||||
|
||||
38
docs/Single_Server.md
Normal file
38
docs/Single_Server.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Single Server
|
||||
|
||||
## Installation
|
||||
|
||||
The core dependencies are Python3, Django3, Celery, PostgreSQL, Redis, ffmpeg. Any system that can have these dependencies installed, can run MediaCMS. But we strongly suggest installing on Linux Ubuntu 18 or 20 versions.
|
||||
|
||||
Installation on a Ubuntu 18 or 20 system with git utility installed should be completed in a few minutes with the following steps.
|
||||
Make sure you run it as user root, on a clear system, since the automatic script will install and configure the following services: Celery/PostgreSQL/Redis/Nginx and will override any existing settings.
|
||||
|
||||
Automated script - tested on Ubuntu 18, Ubuntu 20, and Debian Buster
|
||||
|
||||
```bash
|
||||
mkdir /home/mediacms.io && cd /home/mediacms.io/
|
||||
git clone https://github.com/mediacms-io/mediacms
|
||||
cd /home/mediacms.io/mediacms/ && bash ./install.sh
|
||||
```
|
||||
|
||||
The script will ask if you have a URL where you want to deploy MediaCMS, otherwise it will use localhost. If you provide a URL, it will use Let's Encrypt service to install a valid ssl certificate.
|
||||
|
||||
|
||||
## Update
|
||||
|
||||
If you've used the above way to install MediaCMS, update with the following:
|
||||
|
||||
```bash
|
||||
cd /home/mediacms.io/mediacms # enter mediacms directory
|
||||
source /home/mediacms.io/bin/activate # use virtualenv
|
||||
git pull # update code
|
||||
python manage.py migrate # run Django migrations
|
||||
sudo systemctl restart mediacms celery_long celery_short # restart services
|
||||
```
|
||||
|
||||
## Configuration
|
||||
Checkout the configuration docs on [Configuration](/docs/Configuration.md) page.
|
||||
|
||||
|
||||
## Maintenance
|
||||
Database can be backed up with pg_dump and media_files on /home/mediacms.io/mediacms/media_files include original files and encoded/transcoded versions
|
||||
55
docs/robots_and_analytics.md
Normal file
55
docs/robots_and_analytics.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Google Analytics
|
||||
|
||||
1. Create a file:
|
||||
|
||||
``` touch $DIR/mediacms/templates/tracking.html ```
|
||||
|
||||
2. Add the Gtag/Analytics script
|
||||
|
||||
3. Inside ``` $DIR/mediacms/templates/root.html``` you'll see a file like this one:
|
||||
|
||||
```
|
||||
<head>
|
||||
{% block head %}
|
||||
|
||||
<title>{% block headtitle %}{{PORTAL_NAME}}{% endblock headtitle %}</title>
|
||||
|
||||
{% include "common/head-meta.html" %}
|
||||
|
||||
{% block headermeta %}
|
||||
|
||||
<meta property="og:title" content="{{PORTAL_NAME}}">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
{%endblock headermeta %}
|
||||
|
||||
{% block externallinks %}{% endblock externallinks %}
|
||||
|
||||
{% include "common/head-links.html" %}
|
||||
|
||||
{% block topimports %}{%endblock topimports %}
|
||||
|
||||
{% include "config/index.html" %}
|
||||
|
||||
{% endblock head %}
|
||||
|
||||
</head>
|
||||
```
|
||||
|
||||
4. Add ``` {% include "tracking.html" %} ``` at the end inside the section ```<head>```
|
||||
|
||||
5. If you are using Docker and didn't mount the entire dir you need to bind a new volume:
|
||||
```
|
||||
|
||||
web:
|
||||
image: mediacms/mediacms:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
deploy:
|
||||
replicas: 1
|
||||
volumes:
|
||||
- ./templates/root.html:/home/mediacms.io/mediacms/templates/root.html
|
||||
- ./templates/tracking.html://home/mediacms.io/mediacms/templates/tracking.html
|
||||
|
||||
```
|
||||
@@ -1,12 +1,11 @@
|
||||
from django.conf import settings
|
||||
from django.contrib.postgres.search import SearchQuery
|
||||
from django.contrib.syndication.views import Feed
|
||||
from django.db.models import Q
|
||||
from django.urls import reverse
|
||||
from django.utils.feedgenerator import Rss201rev2Feed
|
||||
|
||||
from . import helpers
|
||||
from .models import Category, Media
|
||||
from .models import Media
|
||||
from .stop_words import STOP_WORDS
|
||||
|
||||
|
||||
|
||||
@@ -381,7 +381,6 @@ def media_file_info(input_file):
|
||||
}
|
||||
|
||||
if has_audio:
|
||||
audio_duration = 1
|
||||
if "duration" in audio_info.keys():
|
||||
audio_duration = float(audio_info["duration"])
|
||||
elif "tags" in audio_info.keys() and "DURATION" in audio_info["tags"]:
|
||||
|
||||
@@ -24,7 +24,6 @@ from imagekit.processors import ResizeToFit
|
||||
from mptt.models import MPTTModel, TreeForeignKey
|
||||
|
||||
from . import helpers
|
||||
from .methods import notify_users
|
||||
from .stop_words import STOP_WORDS
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -1343,6 +1342,8 @@ def media_save(sender, instance, created, **kwargs):
|
||||
# SOS: do not put anything here, as if more logic is added,
|
||||
# we have to disconnect signal to avoid infinite recursion
|
||||
if created:
|
||||
from .methods import notify_users
|
||||
|
||||
instance.media_init()
|
||||
notify_users(friendly_token=instance.friendly_token, action="media_added")
|
||||
|
||||
@@ -1507,7 +1508,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
# to avoid that this is run twice
|
||||
if (
|
||||
len(orig_chunks)
|
||||
== Encoding.objects.filter(
|
||||
== Encoding.objects.filter( # noqa
|
||||
media=instance.media,
|
||||
profile=instance.profile,
|
||||
chunks_info=instance.chunks_info,
|
||||
@@ -1537,7 +1538,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
chunks_paths = [f.media_file.path for f in chunks]
|
||||
|
||||
all_logs = "\n".join([st.logs for st in chunks])
|
||||
encoding.logs = "{0}\n{1}\n{2}".format(chunks_paths, all_logs)
|
||||
encoding.logs = "{0}\n{1}".format(chunks_paths, all_logs)
|
||||
workers = list(set([st.worker for st in chunks]))
|
||||
encoding.worker = json.dumps({"workers": workers})
|
||||
start_date = min([st.add_date for st in chunks])
|
||||
@@ -1548,7 +1549,7 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
who = Encoding.objects.filter(media=encoding.media, profile=encoding.profile).exclude(id=encoding.id)
|
||||
|
||||
who.delete()
|
||||
pass # TODO: merge with above if, do not repeat code
|
||||
# TODO: merge with above if, do not repeat code
|
||||
else:
|
||||
if instance.status in ["fail", "success"]:
|
||||
instance.media.post_encode_actions(encoding=instance, action="add")
|
||||
@@ -1556,7 +1557,6 @@ def encoding_file_save(sender, instance, created, **kwargs):
|
||||
encodings = set([encoding.status for encoding in Encoding.objects.filter(media=instance.media)])
|
||||
if ("running" in encodings) or ("pending" in encodings):
|
||||
return
|
||||
workers = list(set([encoding.worker for encoding in Encoding.objects.filter(media=instance.media)]))
|
||||
|
||||
|
||||
@receiver(post_delete, sender=Encoding)
|
||||
|
||||
@@ -38,7 +38,6 @@ class MediaSerializer(serializers.ModelSerializer):
|
||||
"friendly_token",
|
||||
"user",
|
||||
"add_date",
|
||||
"views",
|
||||
"media_type",
|
||||
"state",
|
||||
"duration",
|
||||
@@ -49,6 +48,7 @@ class MediaSerializer(serializers.ModelSerializer):
|
||||
"reported_times",
|
||||
"size",
|
||||
"is_reviewed",
|
||||
"featured",
|
||||
)
|
||||
fields = (
|
||||
"friendly_token",
|
||||
@@ -64,8 +64,6 @@ class MediaSerializer(serializers.ModelSerializer):
|
||||
"duration",
|
||||
"thumbnail_url",
|
||||
"is_reviewed",
|
||||
"url",
|
||||
"api_url",
|
||||
"preview_url",
|
||||
"author_name",
|
||||
"author_profile",
|
||||
|
||||
@@ -592,7 +592,7 @@ def save_user_action(user_or_session, friendly_token=None, action="watch", extra
|
||||
if not (user or session_key):
|
||||
return False
|
||||
|
||||
if action in ["like", "dislike", "report"]:
|
||||
if action in ["like", "dislike", "watch", "report"]:
|
||||
if not pre_save_action(
|
||||
media=media,
|
||||
user=user,
|
||||
|
||||
109
files/views.py
109
files/views.py
@@ -366,13 +366,18 @@ class MediaList(APIView):
|
||||
"""Media listings views"""
|
||||
|
||||
permission_classes = (IsAuthorizedToAdd,)
|
||||
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
|
||||
parser_classes = (MultiPartParser, FormParser, FileUploadParser)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name='page', type=openapi.TYPE_INTEGER, in_=openapi.IN_QUERY, description='Page number'),
|
||||
openapi.Parameter(name='author', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='username'),
|
||||
openapi.Parameter(name='show', type=openapi.TYPE_STRING, in_=openapi.IN_QUERY, description='show', enum=['recommended', 'featured', 'latest']),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='List Media',
|
||||
operation_description='Lists all media',
|
||||
responses={200: MediaSerializer(many=True)},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
# Show media
|
||||
@@ -414,10 +419,15 @@ class MediaList(APIView):
|
||||
return paginator.get_paginated_response(serializer.data)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name="media_file", in_=openapi.IN_FORM, type=openapi.TYPE_FILE, required=True, description="media_file"),
|
||||
openapi.Parameter(name="description", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="description"),
|
||||
openapi.Parameter(name="title", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="title"),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='Add new Media',
|
||||
operation_description='Adds a new media, for authenticated users',
|
||||
responses={201: openapi.Response('response description', MediaSerializer), 401: 'bad request'},
|
||||
)
|
||||
def post(self, request, format=None):
|
||||
# Add new media
|
||||
@@ -435,7 +445,7 @@ class MediaDetail(APIView):
|
||||
"""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsUserOrEditor)
|
||||
parser_classes = (JSONParser, MultiPartParser, FormParser, FileUploadParser)
|
||||
parser_classes = (MultiPartParser, FormParser, FileUploadParser)
|
||||
|
||||
def get_object(self, friendly_token, password=None):
|
||||
try:
|
||||
@@ -461,10 +471,13 @@ class MediaDetail(APIView):
|
||||
)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='Get information for Media',
|
||||
operation_description='Get information for a media',
|
||||
responses={200: SingleMediaSerializer(), 400: 'bad request'},
|
||||
)
|
||||
def get(self, request, friendly_token, format=None):
|
||||
# Get media details
|
||||
@@ -492,10 +505,23 @@ class MediaDetail(APIView):
|
||||
return Response(ret)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
|
||||
openapi.Parameter(name='type', type=openapi.TYPE_STRING, in_=openapi.IN_FORM, description='action to perform', enum=['encode', 'review']),
|
||||
openapi.Parameter(
|
||||
name='encoding_profiles',
|
||||
type=openapi.TYPE_ARRAY,
|
||||
items=openapi.Items(type=openapi.TYPE_STRING),
|
||||
in_=openapi.IN_FORM,
|
||||
description='if action to perform is encode, need to specify list of ids of encoding profiles',
|
||||
),
|
||||
openapi.Parameter(name='result', type=openapi.TYPE_BOOLEAN, in_=openapi.IN_FORM, description='if action is review, this is the result (True for reviewed, False for not reviewed)'),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='Run action on Media',
|
||||
operation_description='Actions for a media, for MediaCMS editors and managers',
|
||||
responses={201: 'action created', 400: 'bad request'},
|
||||
operation_id='media_manager_actions',
|
||||
)
|
||||
def post(self, request, friendly_token, format=None):
|
||||
"""superuser actions
|
||||
@@ -514,7 +540,6 @@ class MediaDetail(APIView):
|
||||
action = request.data.get("type")
|
||||
profiles_list = request.data.get("encoding_profiles")
|
||||
result = request.data.get("result", True)
|
||||
|
||||
if action == "encode":
|
||||
# Create encoding tasks for specific profiles
|
||||
valid_profiles = []
|
||||
@@ -548,10 +573,15 @@ class MediaDetail(APIView):
|
||||
)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name="description", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="description"),
|
||||
openapi.Parameter(name="title", in_=openapi.IN_FORM, type=openapi.TYPE_STRING, required=False, description="title"),
|
||||
openapi.Parameter(name="media_file", in_=openapi.IN_FORM, type=openapi.TYPE_FILE, required=False, description="media_file"),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='Update Media',
|
||||
operation_description='Update a Media, for Media uploader',
|
||||
responses={201: openapi.Response('response description', MediaSerializer), 401: 'bad request'},
|
||||
)
|
||||
def put(self, request, friendly_token, format=None):
|
||||
# Update a media object
|
||||
@@ -561,16 +591,24 @@ class MediaDetail(APIView):
|
||||
|
||||
serializer = MediaSerializer(media, data=request.data, context={"request": request})
|
||||
if serializer.is_valid():
|
||||
media_file = request.data["media_file"]
|
||||
serializer.save(user=request.user, media_file=media_file)
|
||||
if request.data.get('media_file'):
|
||||
media_file = request.data["media_file"]
|
||||
serializer.save(user=request.user, media_file=media_file)
|
||||
else:
|
||||
serializer.save(user=request.user)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
manual_parameters=[
|
||||
openapi.Parameter(name='friendly_token', type=openapi.TYPE_STRING, in_=openapi.IN_PATH, description='unique identifier', required=True),
|
||||
],
|
||||
tags=['Media'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='Delete Media',
|
||||
operation_description='Delete a Media, for MediaCMS editors and managers',
|
||||
responses={
|
||||
204: 'no content',
|
||||
},
|
||||
)
|
||||
def delete(self, request, friendly_token, format=None):
|
||||
# Delete a media object
|
||||
@@ -757,7 +795,7 @@ class MediaSearch(APIView):
|
||||
media = media.filter(user__username=author)
|
||||
|
||||
if upload_date:
|
||||
gte = lte = None
|
||||
gte = None
|
||||
if upload_date == 'today':
|
||||
gte = datetime.now().date()
|
||||
if upload_date == 'this_week':
|
||||
@@ -769,8 +807,6 @@ class MediaSearch(APIView):
|
||||
if upload_date == 'this_year':
|
||||
year = datetime.now().date().year
|
||||
gte = datetime(year, 1, 1)
|
||||
if lte:
|
||||
media = media.filter(add_date__lte=lte)
|
||||
if gte:
|
||||
media = media.filter(add_date__gte=gte)
|
||||
|
||||
@@ -803,6 +839,9 @@ class PlaylistList(APIView):
|
||||
tags=['Playlists'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
responses={
|
||||
200: openapi.Response('response description', PlaylistSerializer(many=True)),
|
||||
},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
|
||||
@@ -1005,8 +1044,8 @@ class EncodingDetail(APIView):
|
||||
chunk=chunk,
|
||||
chunk_file_path=chunk_file_path,
|
||||
).count()
|
||||
> 1
|
||||
and force is False
|
||||
> 1 # noqa
|
||||
and force is False # noqa
|
||||
):
|
||||
Encoding.objects.filter(id=encoding_id).delete()
|
||||
return Response({"status": "fail"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
@@ -1124,6 +1163,9 @@ class CommentList(APIView):
|
||||
tags=['Comments'],
|
||||
operation_summary='Lists Comments',
|
||||
operation_description='Paginated listing of all comments',
|
||||
responses={
|
||||
200: openapi.Response('response description', CommentSerializer(many=True)),
|
||||
},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
|
||||
@@ -1280,6 +1322,9 @@ class CategoryList(APIView):
|
||||
tags=['Categories'],
|
||||
operation_summary='Lists Categories',
|
||||
operation_description='Lists all categories',
|
||||
responses={
|
||||
200: openapi.Response('response description', CategorySerializer),
|
||||
},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
categories = Category.objects.filter().order_by("title")
|
||||
@@ -1298,6 +1343,9 @@ class TagList(APIView):
|
||||
tags=['Tags'],
|
||||
operation_summary='Lists Tags',
|
||||
operation_description='Paginated listing of all tags',
|
||||
responses={
|
||||
200: openapi.Response('response description', TagSerializer),
|
||||
},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
tags = Tag.objects.filter().order_by("-media_count")
|
||||
@@ -1314,8 +1362,9 @@ class EncodeProfileList(APIView):
|
||||
@swagger_auto_schema(
|
||||
manual_parameters=[],
|
||||
tags=['Encoding Profiles'],
|
||||
operation_summary='to_be_written',
|
||||
operation_description='to_be_written',
|
||||
operation_summary='List Encoding Profiles',
|
||||
operation_description='Lists all encoding profiles for videos',
|
||||
responses={200: EncodeProfileSerializer(many=True)},
|
||||
)
|
||||
def get(self, request, format=None):
|
||||
profiles = EncodeProfile.objects.all()
|
||||
|
||||
12
frontend/.babelrc
Executable file
12
frontend/.babelrc
Executable file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/react", ["@babel/env", {
|
||||
"modules": false,
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": 3,
|
||||
"targets": {
|
||||
"browsers": ["defaults"]
|
||||
}
|
||||
}]
|
||||
]
|
||||
}
|
||||
12
frontend/.env
Normal file
12
frontend/.env
Normal file
@@ -0,0 +1,12 @@
|
||||
MEDIACMS_ID=mediacms-frontend
|
||||
MEDIACMS_TITLE=MediaCMS Frontend
|
||||
|
||||
MEDIACMS_URL=http://localhost
|
||||
MEDIACMS_API=http://localhost/api/v1
|
||||
|
||||
MEDIACMS_USER_IS_ADMIN=true
|
||||
MEDIACMS_USER_IS_ANONYMOUS=false
|
||||
|
||||
MEDIACMS_USER_USERNAME=admin
|
||||
MEDIACMS_USER_NAME=Admin
|
||||
MEDIACMS_USER_THUMB=http://localhost/media/userlogos/user.jpg
|
||||
131
frontend/.gitignore
vendored
Executable file
131
frontend/.gitignore
vendored
Executable file
@@ -0,0 +1,131 @@
|
||||
.DS_Store
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
!modules/**/*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env.development
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
# dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# System files
|
||||
.fuse_hidden*
|
||||
|
||||
# Exports
|
||||
/build
|
||||
/dist
|
||||
|
||||
# Packages dev files
|
||||
# /packages/**/package-lock.json
|
||||
|
||||
# Other
|
||||
*playground*
|
||||
1
frontend/.prettierignore
Normal file
1
frontend/.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
config/templates/*.ejs
|
||||
5
frontend/.prettierrc
Normal file
5
frontend/.prettierrc
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 120
|
||||
}
|
||||
3
frontend/.vscode/settings.json
vendored
Normal file
3
frontend/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
29
frontend/README.md
Normal file
29
frontend/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# MediaCMS Web Client (demo)
|
||||
|
||||
### **Requirements**
|
||||
|
||||
- nodejs: version >= 14.17.0
|
||||
|
||||
---
|
||||
|
||||
### **Installation**
|
||||
|
||||
npm install
|
||||
|
||||
---
|
||||
|
||||
### **Development**
|
||||
|
||||
npm run start
|
||||
|
||||
Open in browser: [http://localhost:8088](http://localhost:8088)
|
||||
|
||||
---
|
||||
|
||||
### **Build**
|
||||
|
||||
npm run dist
|
||||
|
||||
Generates the folder "**_frontend/dist_**".
|
||||
|
||||
Copy folders and files from "**_frontend/dist/static_**" into "**_static_**".
|
||||
44
frontend/config/mediacms.config.html.js
Executable file
44
frontend/config/mediacms.config.html.js
Executable file
@@ -0,0 +1,44 @@
|
||||
module.exports = {
|
||||
head: {
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
{ name: 'theme-color', content: '#fafafa' },
|
||||
{ name: 'msapplication-TileColor', content: '#fafafa' },
|
||||
{ name: 'msapplication-config', content: 'favicons/browserconfig.xml' },
|
||||
],
|
||||
links: [
|
||||
/**
|
||||
* Manifest file link.
|
||||
*/
|
||||
{ rel: 'manifest', href: 'static/favicons/site.webmanifest' },
|
||||
/**
|
||||
* Favicon links.
|
||||
*/
|
||||
{ rel: 'apple-touch-icon', sizes: '180x180', href: 'static/favicons/apple-touch-icon.png' },
|
||||
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: 'static/favicons/favicon-32x32.png' },
|
||||
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: 'static/favicons/favicon-16x16.png' },
|
||||
{ rel: 'mask-icon', href: 'static/favicons/safari-pinned-tab.svg', color: '#fafafa' },
|
||||
{ rel: 'shortcut icon', href: 'static/favicons/favicon.ico' },
|
||||
/**
|
||||
* Stylesheet links
|
||||
*/
|
||||
{ rel: 'preload', href: 'static/css/_extra.css', as: 'style' },
|
||||
{ rel: 'stylesheet', href: 'static/css/_extra.css' },
|
||||
|
||||
// 'https://fonts.googleapis.com/icon?family=Material+Icons',
|
||||
{ rel: 'preload', href: 'static/lib/material-icons/material-icons.css', as: 'style' },
|
||||
{ rel: 'stylesheet', href: 'static/lib/material-icons/material-icons.css' },
|
||||
|
||||
// 'https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,500,500i,700,700i&display=swap
|
||||
{ rel: 'preload', href: 'static/lib/gfonts/gfonts.css', as: 'style' },
|
||||
{ rel: 'stylesheet', href: 'static/lib/gfonts/gfonts.css' },
|
||||
],
|
||||
scripts: [],
|
||||
},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: '',
|
||||
},
|
||||
};
|
||||
31
frontend/config/mediacms.config.js
Executable file
31
frontend/config/mediacms.config.js
Executable file
@@ -0,0 +1,31 @@
|
||||
const path = require('path');
|
||||
|
||||
const rootDir = '..';
|
||||
const srcDir = rootDir + '/src';
|
||||
const configDir = srcDir + '/templates/config';
|
||||
const coreConfigDir = configDir + '/core';
|
||||
const installConfigDir = configDir + '/installation';
|
||||
|
||||
module.exports = {
|
||||
src: path.resolve(__dirname, srcDir),
|
||||
build: path.resolve(__dirname, rootDir),
|
||||
html: require('./mediacms.config.html.js'),
|
||||
pages: require('./mediacms.config.pages.js'),
|
||||
window: {
|
||||
MediaCMS: {
|
||||
api: require(coreConfigDir + '/api.config.js'),
|
||||
url: require(coreConfigDir + '/url.config.js'),
|
||||
user: require(coreConfigDir + '/user.config.js'),
|
||||
site: require(installConfigDir + '/site.config.js'),
|
||||
pages: require(installConfigDir + '/pages.config.js'),
|
||||
contents: require(installConfigDir + '/contents.config.js'),
|
||||
features: require(installConfigDir + '/features.config.js'),
|
||||
/*notifications: [
|
||||
'Message one text',
|
||||
'Message two text',
|
||||
'Message three text'
|
||||
],*/
|
||||
},
|
||||
},
|
||||
postcssConfigFile: path.resolve(__dirname, './postcss.config.js'),
|
||||
};
|
||||
268
frontend/config/mediacms.config.pages.js
Normal file
268
frontend/config/mediacms.config.pages.js
Normal file
@@ -0,0 +1,268 @@
|
||||
const templates = require('./mediacms.config.templates');
|
||||
|
||||
const DEV_SAMPLE_DATA = {
|
||||
profileId: process.env.MEDIACMS_USER_USERNAME,
|
||||
media: {
|
||||
videoId: process.env.MEDIACMS_VIDEO_ID,
|
||||
audioId: process.env.MEDIACMS_AUDIO_ID,
|
||||
imageId: process.env.MEDIACMS_IMAGE_ID,
|
||||
pdfId: process.env.MEDIACMS_PDF_ID,
|
||||
},
|
||||
playlistId: process.env.MEDIACMS_PLAYLIST_ID,
|
||||
};
|
||||
|
||||
const formatPage = (page) => {
|
||||
const pageContentId = 'page-' + page.id;
|
||||
const filename = page.filename ? page.filename : ('home' === page.id ? 'index' : page.id) + '.html';
|
||||
const render = page.renderer
|
||||
? page.renderer
|
||||
: page.component
|
||||
? templates.renderPageContent({ page: { id: pageContentId, component: page.component } })
|
||||
: undefined;
|
||||
const headLinks = [
|
||||
{ rel: 'preload', href: './static/lib/video-js/7.7.5/video.min.js', as: 'script' },
|
||||
...(page.headLinks ? page.headLinks : []),
|
||||
];
|
||||
const bodyScripts = [
|
||||
{ src: './static/lib/video-js/7.7.5/video.min.js' },
|
||||
...(page.bodyScripts ? page.bodyScripts : []),
|
||||
];
|
||||
|
||||
const ret = {
|
||||
buildExclude: !!page.buildExclude,
|
||||
title: page.title,
|
||||
filename,
|
||||
render,
|
||||
html: {
|
||||
head: {
|
||||
links: headLinks,
|
||||
},
|
||||
body: {
|
||||
scripts: bodyScripts,
|
||||
snippet: page.snippet || templates.htmlBodySnippet({ id: pageContentId }),
|
||||
},
|
||||
},
|
||||
window: { MediaCMS: page.global ? { ...page.global } : {} },
|
||||
};
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
const formatPageData = (page) => {
|
||||
return formatPage({
|
||||
...page,
|
||||
});
|
||||
};
|
||||
|
||||
const formatStaticPageData = (page) => {
|
||||
const pageContentId = 'page-' + page.id;
|
||||
return formatPage({
|
||||
...page,
|
||||
renderer: page.renderer
|
||||
? page.renderer
|
||||
: page.component
|
||||
? templates.renderPageStaticContent({ page: { id: pageContentId, component: page.component } })
|
||||
: undefined,
|
||||
});
|
||||
};
|
||||
|
||||
const PAGES = {
|
||||
base: {
|
||||
id: 'base',
|
||||
title: 'Layout base',
|
||||
renderer: templates.renderBase(),
|
||||
},
|
||||
index: { id: 'home', title: 'Home', component: 'HomePage' },
|
||||
search: { id: 'search', title: 'Search results', component: 'SearchPage' },
|
||||
latest: { id: 'latest', title: 'Recent uploads', component: 'LatestMediaPage' },
|
||||
featured: { id: 'featured', title: 'Featured', component: 'FeaturedMediaPage' },
|
||||
recommended: { id: 'recommended', title: 'Recommended', component: 'RecommendedMediaPage' },
|
||||
members: { id: 'members', title: 'Members', component: 'MembersPage' },
|
||||
history: { id: 'history', title: 'History', component: 'HistoryPage' },
|
||||
liked: { id: 'liked', title: 'Liked media', component: 'LikedMediaPage' },
|
||||
tags: { id: 'tags', title: 'Tags', component: 'TagsPage' },
|
||||
categories: { id: 'categories', title: 'Categories', component: 'CategoriesPage' },
|
||||
'manage-media': { id: 'manage-media', title: 'Manage media', component: 'ManageMediaPage' },
|
||||
'manage-users': { id: 'manage-users', title: 'Manage users', component: 'ManageUsersPage' },
|
||||
'manage-comments': { id: 'manage-comments', title: 'Manage comments', component: 'ManageCommentsPage' },
|
||||
'add-media': {
|
||||
id: 'add-media',
|
||||
title: 'Add media',
|
||||
renderer: templates.renderAddMediaPageContent(),
|
||||
snippet: templates.htmlBodySnippetAddMediaPage(),
|
||||
headLinks: [{ rel: 'preload', href: './static/lib/file-uploader/5.13.0/fine-uploader.min.js', as: 'script' }],
|
||||
bodyScripts: [{ src: './static/lib/file-uploader/5.13.0/fine-uploader.min.js' }],
|
||||
},
|
||||
embed: {
|
||||
id: 'embed',
|
||||
title: 'Embedded player',
|
||||
renderer: templates.renderEmbedPageContent({ page: { id: 'page-embed', component: 'EmbedPage' } }),
|
||||
snippet: templates.htmlBodySnippetEmbedPage({ id: 'page-embed' }),
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
|
||||
},
|
||||
media: {
|
||||
id: 'media',
|
||||
title: 'Media',
|
||||
component: 'MediaPage',
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
|
||||
},
|
||||
'media-video': {
|
||||
buildExclude: true,
|
||||
id: 'media-video',
|
||||
title: 'Media - Video',
|
||||
component: 'MediaVideoPage',
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.videoId },
|
||||
},
|
||||
'media-audio': {
|
||||
buildExclude: true,
|
||||
id: 'media-audio',
|
||||
title: 'Media - Audio',
|
||||
component: 'MediaAudioPage',
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.audioId },
|
||||
},
|
||||
'media-image': {
|
||||
buildExclude: true,
|
||||
id: 'media-image',
|
||||
title: 'Media - Image',
|
||||
component: 'MediaImagePage',
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.imageId },
|
||||
},
|
||||
'media-pdf': {
|
||||
buildExclude: true,
|
||||
id: 'media-pdf',
|
||||
title: 'Media - Pdf',
|
||||
component: 'MediaPdfPage',
|
||||
global: { mediaId: DEV_SAMPLE_DATA.media.pdfId },
|
||||
},
|
||||
playlist: {
|
||||
id: 'playlist',
|
||||
title: 'Playlist',
|
||||
component: 'PlaylistPage',
|
||||
global: { playlistId: DEV_SAMPLE_DATA.playlistId },
|
||||
},
|
||||
'profile-media': {
|
||||
id: 'profile-media',
|
||||
title: 'Profile - Media',
|
||||
component: 'ProfileMediaPage',
|
||||
global: { profileId: DEV_SAMPLE_DATA.profileId },
|
||||
},
|
||||
'profile-about': {
|
||||
id: 'profile-about',
|
||||
title: 'Profile - About',
|
||||
component: 'ProfileAboutPage',
|
||||
global: { profileId: DEV_SAMPLE_DATA.profileId },
|
||||
},
|
||||
'profile-playlists': {
|
||||
id: 'profile-playlists',
|
||||
title: 'Profile - Playlist',
|
||||
component: 'ProfilePlaylistsPage',
|
||||
global: { profileId: DEV_SAMPLE_DATA.profileId },
|
||||
},
|
||||
};
|
||||
|
||||
const STATIC_PAGES = {
|
||||
error: {
|
||||
buildExclude: true,
|
||||
id: 'error',
|
||||
title: 'Error',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.errorPage(),
|
||||
},
|
||||
about: {
|
||||
id: 'about',
|
||||
title: 'About',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.aboutPage(),
|
||||
},
|
||||
terms: {
|
||||
buildExclude: true,
|
||||
id: 'terms',
|
||||
title: 'Terms',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.termsPage(),
|
||||
},
|
||||
};
|
||||
|
||||
const DEV_ONLY_STATIC_PAGES = {
|
||||
'add-media-template': {
|
||||
buildExclude: true,
|
||||
id: 'add-media-template',
|
||||
title: 'Add media - Template',
|
||||
renderer: templates.renderAddMediaPageContent(),
|
||||
snippet: templates.static.addMediaPageTemplate(),
|
||||
headLinks: [{ rel: 'preload', href: './static/lib/file-uploader/5.13.0/fine-uploader.min.js', as: 'script' }],
|
||||
bodyScripts: [{ src: './static/lib/file-uploader/5.13.0/fine-uploader.min.js' }],
|
||||
},
|
||||
'edit-media': {
|
||||
buildExclude: true,
|
||||
id: 'edit-media',
|
||||
title: 'Edit media',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.editMediaPage(),
|
||||
},
|
||||
'edit-channel': {
|
||||
buildExclude: true,
|
||||
id: 'edit-channel',
|
||||
title: 'Edit channel',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.editChannelPage(),
|
||||
},
|
||||
'edit-profile': {
|
||||
buildExclude: true,
|
||||
id: 'edit-profile',
|
||||
title: 'Edit profile',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.editProfilePage(),
|
||||
},
|
||||
signin: {
|
||||
buildExclude: true,
|
||||
id: 'signin',
|
||||
title: 'Sign in',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.signinPage(),
|
||||
},
|
||||
signout: {
|
||||
buildExclude: true,
|
||||
id: 'signout',
|
||||
title: 'Sign out',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.signoutPage(),
|
||||
},
|
||||
register: {
|
||||
buildExclude: true,
|
||||
id: 'register',
|
||||
title: 'Register',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.registerPage(),
|
||||
},
|
||||
'reset-password': {
|
||||
buildExclude: true,
|
||||
id: 'reset-password',
|
||||
title: 'Reset password',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.resetPasswordPage(),
|
||||
},
|
||||
contact: {
|
||||
buildExclude: true,
|
||||
id: 'contact',
|
||||
title: 'Contact us',
|
||||
renderer: templates.renderBase(),
|
||||
snippet: templates.static.contactPage(),
|
||||
},
|
||||
};
|
||||
|
||||
const pages = {};
|
||||
|
||||
for (let k in PAGES) {
|
||||
pages[k] = formatPageData(PAGES[k]);
|
||||
}
|
||||
|
||||
for (let k in STATIC_PAGES) {
|
||||
pages[k] = formatStaticPageData(STATIC_PAGES[k]);
|
||||
}
|
||||
|
||||
for (let k in DEV_ONLY_STATIC_PAGES) {
|
||||
pages[k] = formatStaticPageData(DEV_ONLY_STATIC_PAGES[k]);
|
||||
}
|
||||
|
||||
module.exports = pages;
|
||||
45
frontend/config/mediacms.config.templates.js
Normal file
45
frontend/config/mediacms.config.templates.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ejs = require('ejs');
|
||||
|
||||
const templatesPath = path.join(__dirname, './templates');
|
||||
const staticTemplatesPath = path.join(__dirname, './templates/static');
|
||||
|
||||
const compileTmpl = (filename) =>
|
||||
ejs.compile(fs.readFileSync(path.join(templatesPath, filename), 'utf8'), {
|
||||
root: [templatesPath],
|
||||
filename: path.join(templatesPath, filename),
|
||||
outputFunctionName: 'echo',
|
||||
});
|
||||
|
||||
const compileStaticTmpl = (filename) =>
|
||||
ejs.compile(fs.readFileSync(path.join(staticTemplatesPath, filename), 'utf8'), {
|
||||
root: [staticTemplatesPath],
|
||||
filename: path.join(staticTemplatesPath, filename),
|
||||
outputFunctionName: 'echo',
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
htmlBodySnippet: compileTmpl('htmlBodySnippet.ejs'),
|
||||
htmlBodySnippetEmbedPage: compileTmpl('htmlBodySnippetEmbedPage.ejs'),
|
||||
htmlBodySnippetAddMediaPage: compileTmpl('htmlBodySnippetAddMediaPage.ejs'),
|
||||
renderBase: compileTmpl('renderBase.ejs'),
|
||||
renderPageContent: compileTmpl('renderPageContent.ejs'),
|
||||
renderPageStaticContent: compileTmpl('renderPageStaticContent.ejs'),
|
||||
renderEmbedPageContent: compileTmpl('renderEmbedPageContent.ejs'),
|
||||
renderAddMediaPageContent: compileTmpl('renderAddMediaPageContent.ejs'),
|
||||
static: {
|
||||
errorPage: compileStaticTmpl('errorPage.html'),
|
||||
aboutPage: compileStaticTmpl('aboutPage.html'),
|
||||
termsPage: compileStaticTmpl('termsPage.html'),
|
||||
contactPage: compileStaticTmpl('contactPage.html'),
|
||||
signinPage: compileStaticTmpl('signinPage.html'),
|
||||
signoutPage: compileStaticTmpl('signoutPage.html'),
|
||||
registerPage: compileStaticTmpl('registerPage.html'),
|
||||
resetPasswordPage: compileStaticTmpl('resetPasswordPage.html'),
|
||||
editMediaPage: compileStaticTmpl('editMediaPage.html'),
|
||||
editChannelPage: compileStaticTmpl('editChannelPage.html'),
|
||||
editProfilePage: compileStaticTmpl('editProfilePage.html'),
|
||||
addMediaPageTemplate: compileStaticTmpl('addMediaPageTemplate.html'),
|
||||
},
|
||||
};
|
||||
10
frontend/config/postcss.config.js
Executable file
10
frontend/config/postcss.config.js
Executable file
@@ -0,0 +1,10 @@
|
||||
module.exports = (ctx) => {
|
||||
const ret = {
|
||||
map: ctx.env === 'development' ? ctx.map : false,
|
||||
plugins: {
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
return ret;
|
||||
};
|
||||
6
frontend/config/templates/htmlBodySnippet.ejs
Executable file
6
frontend/config/templates/htmlBodySnippet.ejs
Executable file
@@ -0,0 +1,6 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<% if (id) { %><div id="<%= id %>"></div><% } %>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
65
frontend/config/templates/htmlBodySnippetAddMediaPage.ejs
Executable file
65
frontend/config/templates/htmlBodySnippetAddMediaPage.ejs
Executable file
@@ -0,0 +1,65 @@
|
||||
<div id="add-media-page">
|
||||
|
||||
<div class="page-container">
|
||||
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
|
||||
<div class="page-main-wrap">
|
||||
|
||||
<main class="page-main">
|
||||
<div class="page-main-inner">
|
||||
|
||||
<div class="media-uploader-wrap">
|
||||
|
||||
<div class="media-uploader-top-wrap">
|
||||
|
||||
<div class="media-uploader-top-left-wrap">
|
||||
<h1>Upload media files</h1>
|
||||
</div>
|
||||
|
||||
<div class="media-uploader-top-right-wrap"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="qq-template"> <div class="media-uploader-bottom-wrap qq-uploader-selector"> <div class="media-uploader-bottom-left-wrap"> <div class="media-drag-drop-wrap"> <div class="media-drag-drop-inner" qq-drop-area-text="Drop files here"> <div class="media-drag-drop-content"> <div class="media-drag-drop-content-inner"> <span><i class="material-icons">cloud_upload</i></span> <span>Drag and drop files</span> <span>or</span> <span class="browse-files-btn-wrap"><span class="qq-upload-button-selector">Browse your files</span></span><div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone><span class="qq-upload-drop-area-text-selector"></span></div> </div></div></div></div></div><div class="media-uploader-bottom-right-wrap"> <ul class="media-upload-items-list qq-upload-list-selector"> <li> <div class="media-upload-item-main"> <div class="media-upload-item-thumb"> <img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale alt=""/> <span class="media-upload-item-spinner qq-upload-spinner-selector"><i class="material-icons">autorenew</i></span> <button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry"><i class="material-icons">refresh</i> Retry</button> </div><div class="media-upload-item-details"> <div class="media-upload-item-name"> <span class="media-upload-item-filename qq-upload-file-selector"></span> <input class="media-upload-item-filename-input qq-edit-filename-selector" tab-index="0" type="text"/> </div><div class="media-upload-item-details-bottom"> <div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector"> <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="media-upload-item-progress-bar qq-progress-bar-selector"></div></div><span class="media-upload-item-upload-size qq-upload-size-selector"></span> <span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"></span> </div><div class="media-upload-item-top-actions"> <span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename">Edit filename <i class="material-icons">create</i></span> <button type="button" class="delete-media-upload-item qq-upload-delete-selector" aria-label="Delete">Delete <i class="material-icons">delete</i></button> <button type="button" class="cancel-media-upload-item qq-upload-cancel-selector" aria-label="Cancel">Cancel <i class="material-icons">cancel</i></button> </div><div class="media-upload-item-bottom-actions"> <button type="button" class="continue-media-upload-item qq-upload-continue-selector" aria-label="Continue"><i class="material-icons">play_circle_outline</i> Continue</button> <button type="button" class="pause-media-upload-item qq-upload-pause-selector" aria-label="Pause"><i class="material-icons">pause_circle_outline</i> Pause</button> </div></div></div></li></ul> <dialog class="qq-alert-dialog-selector"> <div class="qq-dialog-message-selector"></div><div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">Close</button> </div></dialog> <dialog class="qq-confirm-dialog-selector"> <div class="qq-dialog-message-selector"></div><div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">No</button> <button type="button" class="qq-ok-button-selector">Yes</button> </div></dialog> <dialog class="qq-prompt-dialog-selector"> <div class="qq-dialog-message-selector"></div><input type="text"> <div class="qq-dialog-buttons"> <button type="button" class="qq-cancel-button-selector">Cancel</button> <button type="button" class="qq-ok-button-selector">Ok</button> </div></dialog> </div></div></script>
|
||||
|
||||
<div class="media-uploader"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<script type='text/javascript'>
|
||||
|
||||
function csrfToken(){var a,b=null;if(document.cookie&&""!==document.cookie){var c=document.cookie.split(";");for(a=0;a<c.length;){var d=c[a].trim();if("csrftoken="===d.substring(0,10)){b=decodeURIComponent(d.substring(10));break}a+=1}}return b};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
var default_concurrent_chunked_uploader = new qq.FineUploader({
|
||||
debug: true,
|
||||
element: document.querySelector('.media-uploader'),
|
||||
request: {
|
||||
endpoint: '/fu/upload/',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': csrfToken('csrftoken'),
|
||||
},
|
||||
},
|
||||
retry: {
|
||||
enableAuto: true,
|
||||
},
|
||||
chunking: {
|
||||
enabled: true,
|
||||
concurrent: {
|
||||
enabled: true,
|
||||
},
|
||||
success: {
|
||||
endpoint: '/fu/upload/?done',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
3
frontend/config/templates/htmlBodySnippetEmbedPage.ejs
Executable file
3
frontend/config/templates/htmlBodySnippetEmbedPage.ejs
Executable file
@@ -0,0 +1,3 @@
|
||||
<div class="page-main-wrap">
|
||||
<% if (id) { %><div id="<%= id %>"></div><% } %>
|
||||
</div>
|
||||
5
frontend/config/templates/renderAddMediaPageContent.ejs
Executable file
5
frontend/config/templates/renderAddMediaPageContent.ejs
Executable file
@@ -0,0 +1,5 @@
|
||||
import { renderPage } from './static/js/utils/renderer';
|
||||
|
||||
import './static/css/AddMediaPage.scss';
|
||||
|
||||
renderPage();
|
||||
3
frontend/config/templates/renderBase.ejs
Executable file
3
frontend/config/templates/renderBase.ejs
Executable file
@@ -0,0 +1,3 @@
|
||||
import { renderPage } from './static/js/utils/renderer';
|
||||
|
||||
renderPage();
|
||||
15
frontend/config/templates/renderEmbedPageContent.ejs
Executable file
15
frontend/config/templates/renderEmbedPageContent.ejs
Executable file
@@ -0,0 +1,15 @@
|
||||
<% if (page) { %>
|
||||
|
||||
import { renderEmbedPage } from './static/js/utils/renderer';
|
||||
|
||||
<% if (page.component) { %>
|
||||
|
||||
import { <%= page.component %> } from './static/js/pages/<%= page.component %>';
|
||||
|
||||
<% if (page.id) { %>
|
||||
renderEmbedPage( '<%= page.id %>', <%= page.component %> );
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
15
frontend/config/templates/renderPageContent.ejs
Executable file
15
frontend/config/templates/renderPageContent.ejs
Executable file
@@ -0,0 +1,15 @@
|
||||
<% if (page) { %>
|
||||
|
||||
import { renderPage } from './static/js/utils/renderer';
|
||||
|
||||
<% if (page.component) { %>
|
||||
|
||||
import { <%= page.component %> } from './static/js/pages/<%= page.component %>';
|
||||
|
||||
<% if (page.id) { %>
|
||||
renderPage( '<%= page.id %>', <%= page.component %> );
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
15
frontend/config/templates/renderPageStaticContent.ejs
Executable file
15
frontend/config/templates/renderPageStaticContent.ejs
Executable file
@@ -0,0 +1,15 @@
|
||||
<% if (page) { %>
|
||||
|
||||
import { renderPage } from './static/js/utils/renderer';
|
||||
|
||||
<% if (page.component) { %>
|
||||
|
||||
import { <%= page.component %> } from './static/js/static-pages/<%= page.component %>';
|
||||
|
||||
<% if (page.id) { %>
|
||||
renderPage( '<%= page.id %>', <%= page.component %> );
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
|
||||
<% } %>
|
||||
18
frontend/config/templates/static/aboutPage.html
Executable file
18
frontend/config/templates/static/aboutPage.html
Executable file
@@ -0,0 +1,18 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="custom-page-wrapper">
|
||||
<h2>About</h2>
|
||||
<hr />
|
||||
<p>
|
||||
<a href="https://mediacms.io">MediaCMS</a> is a modern, fully featured open source video and media CMS. It is
|
||||
developed to meet the needs of modern web platforms for viewing and sharing media.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
144
frontend/config/templates/static/addMediaPageTemplate.html
Executable file
144
frontend/config/templates/static/addMediaPageTemplate.html
Executable file
@@ -0,0 +1,144 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="media-uploader-wrap">
|
||||
<div class="media-uploader-top-wrap">
|
||||
<div class="media-uploader-top-left-wrap">
|
||||
<h1>Upload media files</h1>
|
||||
</div>
|
||||
<div class="media-uploader-top-right-wrap"></div>
|
||||
</div>
|
||||
<div class="media-uploader">
|
||||
<div class="media-uploader-bottom-wrap">
|
||||
<div class="media-uploader-bottom-left-wrap">
|
||||
<div class="media-drag-drop-wrap">
|
||||
<div class="media-drag-drop-inner">
|
||||
<div class="media-drag-drop-content">
|
||||
<div class="media-drag-drop-content-inner">
|
||||
<span><i class="material-icons">cloud_upload</i></span>
|
||||
<span>Drag and drop files</span>
|
||||
<span>or</span>
|
||||
<span class="browse-files-btn-wrap">
|
||||
<span
|
||||
class="qq-upload-button-selector"
|
||||
style="position: relative; overflow: hidden; direction: ltr"
|
||||
>Browse your files<input
|
||||
qq-button-id="9523a4a3-6688-40a5-b01d-7c72a79c7bcb"
|
||||
title="file input"
|
||||
multiple=""
|
||||
type="file"
|
||||
name="qqfile"
|
||||
style="
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
top: 0px;
|
||||
font-family: Arial;
|
||||
font-size: 118px;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
height: 100%;
|
||||
"
|
||||
/></span>
|
||||
</span>
|
||||
<div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-uploader-bottom-right-wrap">
|
||||
<ul class="media-upload-items-list qq-upload-list-selector">
|
||||
<li>
|
||||
<div class="media-upload-item-main">
|
||||
<div class="media-upload-item-thumb">
|
||||
<img class="qq-thumbnail-selector" alt="" />
|
||||
<span class="media-upload-item-spinner qq-upload-spinner-selector"
|
||||
><i class="material-icons">autorenew</i></span
|
||||
>
|
||||
<button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry">
|
||||
<i class="material-icons">refresh</i> Retry
|
||||
</button>
|
||||
</div>
|
||||
<div class="media-upload-item-details">
|
||||
<div class="media-upload-item-name">
|
||||
<span class="media-upload-item-filename qq-upload-file-selector" title="">File #1</span>
|
||||
<input
|
||||
class="
|
||||
media-upload-item-filename-input
|
||||
qq-edit-filename-selector qq-edit-filename-icon-selector
|
||||
"
|
||||
tabindex="0"
|
||||
type="text"
|
||||
value="File #1"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-upload-item-details-bottom">
|
||||
<div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector">
|
||||
<div
|
||||
role="progressbar"
|
||||
aria-valuenow="80"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100"
|
||||
class="media-upload-item-progress-bar qq-progress-bar-selector"
|
||||
style="width: 80%"
|
||||
></div>
|
||||
</div>
|
||||
<span class="media-upload-item-upload-size qq-upload-size-selector">80% of 7.5MB</span>
|
||||
<span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"
|
||||
>Retrying 3/3...</span
|
||||
>
|
||||
</div>
|
||||
<div class="media-upload-item-top-actions">
|
||||
<span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename"
|
||||
>Edit filename <i class="material-icons">create</i></span
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="delete-media-upload-item qq-upload-delete-selector"
|
||||
aria-label="Delete"
|
||||
>
|
||||
Delete <i class="material-icons">delete</i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="cancel-media-upload-item qq-upload-cancel-selector"
|
||||
aria-label="Cancel"
|
||||
>
|
||||
Cancel <i class="material-icons">cancel</i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="media-upload-item-bottom-actions">
|
||||
<button
|
||||
type="button"
|
||||
class="continue-media-upload-item qq-upload-continue-selector"
|
||||
aria-label="Continue"
|
||||
>
|
||||
<i class="material-icons">play_circle_outline</i> Continue
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="pause-media-upload-item qq-upload-pause-selector"
|
||||
aria-label="Pause"
|
||||
>
|
||||
<i class="material-icons">pause_circle_outline</i> Pause
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
27
frontend/config/templates/static/contactPage.html
Executable file
27
frontend/config/templates/static/contactPage.html
Executable file
@@ -0,0 +1,27 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Contact us</h1>
|
||||
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
||||
<p>
|
||||
<label for="id_from_email">Your email:</label>
|
||||
<input type="email" name="from_email" required="" id="id_from_email" />
|
||||
</p>
|
||||
<p><label for="id_name">Your name:</label> <input type="text" name="name" id="id_name" /></p>
|
||||
<p>
|
||||
<label for="id_message">Please add your message here and submit:</label>
|
||||
<textarea name="message" cols="40" rows="10" required="" id="id_message"></textarea>
|
||||
</p>
|
||||
<button class="primaryAction" type="submit">Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
30
frontend/config/templates/static/editChannelPage.html
Executable file
30
frontend/config/templates/static/editChannelPage.html
Executable file
@@ -0,0 +1,30 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Edit Channel</h1>
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
||||
<div id="div_id_banner_logo" class="control-group">
|
||||
<label for="id_banner_logo" class="control-label"> Banner logo </label>
|
||||
<div class="controls">
|
||||
Currently: <a href="#">*****</a>
|
||||
<input type="checkbox" name="banner_logo-clear" id="banner_logo-clear_id" />
|
||||
<label for="banner_logo-clear_id">Clear</label><br />
|
||||
Change:
|
||||
<input type="file" name="banner_logo" accept="image/*" class="clearablefileinput" id="id_banner_logo" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="primaryAction" type="submit">Update Channel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
137
frontend/config/templates/static/editMediaPage.html
Normal file
137
frontend/config/templates/static/editMediaPage.html
Normal file
@@ -0,0 +1,137 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Edit Media</h1>
|
||||
|
||||
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
||||
<div id="div_id_title" class="control-group">
|
||||
<label for="id_title" class="control-label"> Title </label>
|
||||
<div class="controls">
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
value="Media title..."
|
||||
maxlength="100"
|
||||
class="textinput textInput"
|
||||
id="id_title"
|
||||
/>
|
||||
<p id="hint_id_title" class="help-block">media title</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_category" class="control-group">
|
||||
<label for="id_category" class="control-label"> Category </label>
|
||||
<div class="controls">
|
||||
<select name="category" class="selectmultiple" id="id_category" multiple="">
|
||||
<option value="1">Art</option>
|
||||
<option value="2">Documentary</option>
|
||||
<option value="3">Experimental</option>
|
||||
<option value="4">Film</option>
|
||||
<option value="5">Music</option>
|
||||
<option value="6">TV</option>
|
||||
</select>
|
||||
<p id="hint_id_category" class="help-block">Media can be part of one or more categories</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_new_tags" class="control-group">
|
||||
<label for="id_new_tags" class="control-label"> Tags </label>
|
||||
<div class="controls">
|
||||
<input type="text" name="new_tags" class="textinput textInput" id="id_new_tags" />
|
||||
<p id="hint_id_new_tags" class="help-block">a comma separated list of new tags.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_add_date" class="control-group">
|
||||
<label for="id_add_date" class="control-label"> Date produced </label>
|
||||
<div class="controls">
|
||||
<input type="text" name="add_date" value="2021-01-01 00:00:01" class="datetimeinput" id="id_add_date" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_uploaded_poster" class="control-group">
|
||||
<label for="id_uploaded_poster" class="control-label"> Upload image </label>
|
||||
<div class="controls">
|
||||
<input
|
||||
type="file"
|
||||
name="uploaded_poster"
|
||||
accept="image/*"
|
||||
class="clearablefileinput"
|
||||
id="id_uploaded_poster"
|
||||
/>
|
||||
<p id="hint_id_uploaded_poster" class="help-block">This image will characterize the media</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_description" class="control-group">
|
||||
<label for="id_description" class="control-label"> Description </label>
|
||||
<div class="controls">
|
||||
<textarea name="description" cols="40" rows="10" class="textarea" id="id_description"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_state" class="control-group">
|
||||
<label for="id_state" class="control-label requiredField">
|
||||
State<span class="asteriskField">*</span>
|
||||
</label>
|
||||
<div class="controls">
|
||||
<select name="state" class="select" id="id_state">
|
||||
<option value="private">Private</option>
|
||||
<option value="public" selected="">Public</option>
|
||||
<option value="unlisted">Unlisted</option>
|
||||
</select>
|
||||
<p id="hint_id_state" class="help-block">state of Media</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_enable_comments" class="control-group">
|
||||
<div class="controls">
|
||||
<label for="id_enable_comments" class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="enable_comments"
|
||||
class="checkboxinput"
|
||||
id="id_enable_comments"
|
||||
checked=""
|
||||
/>
|
||||
Enable comments
|
||||
</label>
|
||||
<p id="hint_id_enable_comments" class="help-block">Whether comments will be allowed for this media</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_thumbnail_time" class="control-group">
|
||||
<label for="id_thumbnail_time" class="control-label"> Thumbnail time </label>
|
||||
<div class="controls">
|
||||
<input
|
||||
type="number"
|
||||
name="thumbnail_time"
|
||||
value="132.5"
|
||||
step="any"
|
||||
class="numberinput"
|
||||
id="id_thumbnail_time"
|
||||
/>
|
||||
<p id="hint_id_thumbnail_time" class="help-block">Time on video that a thumbnail will be taken</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="div_id_allow_download" class="control-group">
|
||||
<div class="controls">
|
||||
<label for="id_allow_download" class="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="allow_download"
|
||||
class="checkboxinput"
|
||||
id="id_allow_download"
|
||||
checked=""
|
||||
/>
|
||||
Allow download
|
||||
</label>
|
||||
<p id="hint_id_allow_download" class="help-block">Whether option to download media is shown</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="primaryAction" type="submit">Update Media</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
43
frontend/config/templates/static/editProfilePage.html
Executable file
43
frontend/config/templates/static/editProfilePage.html
Executable file
@@ -0,0 +1,43 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Edit Profile</h1>
|
||||
<form enctype="multipart/form-data" action="" method="post" class="post-form">
|
||||
<p>
|
||||
<label for="id_name">Full name:</label>
|
||||
<input type="text" name="name" value="" maxlength="250" required="" id="id_name" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_description">About me:</label>
|
||||
<textarea name="description" cols="40" rows="10" id="id_description"></textarea>
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_email">Email address:</label>
|
||||
<input type="email" name="email" value="" maxlength="254" id="id_email" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_logo">Logo:</label> Currently: <a href="#">*****</a>
|
||||
<input type="checkbox" name="logo-clear" id="logo-clear_id" />
|
||||
<label for="logo-clear_id">Clear</label><br />
|
||||
Change:
|
||||
<input type="file" name="logo" accept="image/*" id="id_logo" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_notification_on_comments"
|
||||
>Whether you will receive email notifications for comments added to your content:</label
|
||||
>
|
||||
<input type="checkbox" name="notification_on_comments" id="id_notification_on_comments" checked="" />
|
||||
</p>
|
||||
<button class="primaryAction" type="submit">Update Profile</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
11
frontend/config/templates/static/errorPage.html
Executable file
11
frontend/config/templates/static/errorPage.html
Executable file
@@ -0,0 +1,11 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="error-page-wrap">Error</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
45
frontend/config/templates/static/registerPage.html
Executable file
45
frontend/config/templates/static/registerPage.html
Executable file
@@ -0,0 +1,45 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Sign Up</h1>
|
||||
<p>Already have an account? Then please <a href="./signin.html">sign in</a>.</p>
|
||||
<form class="signup" id="signup_form" method="post" action="#">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="" />
|
||||
<p>
|
||||
<label for="id_email">E-mail:</label>
|
||||
<input type="email" name="email" id="id_email" required="" placeholder="E-mail address" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_username">Username:</label>
|
||||
<input
|
||||
type="text"
|
||||
name="username"
|
||||
id="id_username"
|
||||
autofocus="autofocus"
|
||||
placeholder="Username"
|
||||
minlength="4"
|
||||
maxlength="150"
|
||||
required=""
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_name">Name:</label>
|
||||
<input type="text" name="name" id="id_name" maxlength="100" required="" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_password1">Password:</label>
|
||||
<input type="password" name="password1" id="id_password1" required="" placeholder="Password" />
|
||||
</p>
|
||||
<button type="submit">Sign Up »</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
28
frontend/config/templates/static/resetPasswordPage.html
Executable file
28
frontend/config/templates/static/resetPasswordPage.html
Executable file
@@ -0,0 +1,28 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Password Reset</h1>
|
||||
<p>
|
||||
Forgotten your password? Enter your e-mail address below, and we'll send you an e-mail allowing you to reset
|
||||
it.
|
||||
</p>
|
||||
<form method="POST" action="#" class="password_reset">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="" />
|
||||
<p>
|
||||
<label for="id_email">E-mail:</label>
|
||||
<input type="email" name="email" size="30" id="id_email" required="" placeholder="E-mail address" />
|
||||
</p>
|
||||
<input type="submit" value="Reset My Password" />
|
||||
</form>
|
||||
<p>Please contact us if you have any trouble resetting your password.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
36
frontend/config/templates/static/signinPage.html
Executable file
36
frontend/config/templates/static/signinPage.html
Executable file
@@ -0,0 +1,36 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Sign In</h1>
|
||||
<p>If you have not created an account yet, then please <a href="./register.html">sign up</a> first.</p>
|
||||
<form class="login" method="POST" action="#">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="" />
|
||||
<p>
|
||||
<label for="id_login">Login:</label>
|
||||
<input
|
||||
type="text"
|
||||
name="login"
|
||||
id="id_login"
|
||||
required=""
|
||||
autofocus="autofocus"
|
||||
placeholder="Username or e-mail"
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<label for="id_password">Password:</label>
|
||||
<input type="password" name="password" id="id_password" required="" placeholder="Password" />
|
||||
</p>
|
||||
<a class="button secondaryAction" href="./reset-password.html">Forgot Password?</a>
|
||||
<button class="primaryAction" type="submit">Sign In</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
20
frontend/config/templates/static/signoutPage.html
Executable file
20
frontend/config/templates/static/signoutPage.html
Executable file
@@ -0,0 +1,20 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="user-action-form-wrap">
|
||||
<div class="user-action-form-inner">
|
||||
<h1>Sign Out</h1>
|
||||
<p>Are you sure you want to sign out?</p>
|
||||
<form method="post" action="#">
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="" />
|
||||
<button type="submit">Sign Out</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
15
frontend/config/templates/static/termsPage.html
Executable file
15
frontend/config/templates/static/termsPage.html
Executable file
@@ -0,0 +1,15 @@
|
||||
<div id="app-header"></div>
|
||||
<div id="app-sidebar"></div>
|
||||
<div class="page-main-wrap">
|
||||
<div class="page-main">
|
||||
<div class="page-main-inner">
|
||||
<div class="custom-page-wrapper">
|
||||
<h2>Terms</h2>
|
||||
<hr />
|
||||
<p>Terms of service</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-sidebar-content-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="app-footer"></div>
|
||||
39075
frontend/package-lock.json
generated
Normal file
39075
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
53
frontend/package.json
Executable file
53
frontend/package.json
Executable file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "mediacms-frontend",
|
||||
"version": "0.9.1",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"keywords": [],
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "mediacms-scripts development --config=./config/mediacms.config.js --host=0.0.0.0 --port=8088",
|
||||
"dist": "mediacms-scripts rimraf ./dist && mediacms-scripts build --config=./config/mediacms.config.js --env=dist"
|
||||
},
|
||||
"browserslist": [
|
||||
"cover 99.5%"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@types/react": "^17.0.11",
|
||||
"@types/react-dom": "^17.0.7",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"compass-mixins": "^0.12.10",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"core-js": "^3.14.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"dotenv": "^10.0.0",
|
||||
"ejs": "^3.1.6",
|
||||
"ejs-compiled-loader": "^3.1.0",
|
||||
"mediacms-scripts": "file:packages/scripts",
|
||||
"postcss-loader": "^6.1.0",
|
||||
"prettier": "^2.3.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"sass": "^1.34.1",
|
||||
"sass-loader": "^12.1.0",
|
||||
"ts-loader": "^9.2.3",
|
||||
"typescript": "^4.3.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.38.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"flux": "^4.0.1",
|
||||
"mediacms-player": "file:packages/player",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"sortablejs": "^1.13.0",
|
||||
"timeago.js": "^4.0.2",
|
||||
"url-parse": "^1.5.1"
|
||||
}
|
||||
}
|
||||
3
frontend/packages/player/.babelrc
Executable file
3
frontend/packages/player/.babelrc
Executable file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"presets": ["@babel/preset-env"]
|
||||
}
|
||||
18
frontend/packages/player/.editorconfig
Executable file
18
frontend/packages/player/.editorconfig
Executable file
@@ -0,0 +1,18 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 1
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[package.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = false
|
||||
8
frontend/packages/player/.modernizrrc
Executable file
8
frontend/packages/player/.modernizrrc
Executable file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"minify": true,
|
||||
"options": [],
|
||||
"feature-detects": [
|
||||
"css/transforms",
|
||||
"test/storage/localstorage"
|
||||
]
|
||||
}
|
||||
1
frontend/packages/player/README.md
Executable file
1
frontend/packages/player/README.md
Executable file
@@ -0,0 +1 @@
|
||||
# mediacms-player
|
||||
113
frontend/packages/player/config/includes/rollup_builds.js
Executable file
113
frontend/packages/player/config/includes/rollup_builds.js
Executable file
@@ -0,0 +1,113 @@
|
||||
import gzip from 'rollup-plugin-gzip';
|
||||
import postcss from 'rollup-plugin-postcss';
|
||||
import babel from 'rollup-plugin-babel';
|
||||
import cleanup from 'rollup-plugin-cleanup';
|
||||
// import { uglify } from "rollup-plugin-uglify";
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import json from '@rollup/plugin-json';
|
||||
|
||||
export default function rollup_builds(input_file, output_folder, pkg) {
|
||||
const package_name = pkg.name;
|
||||
|
||||
const dependencies = pkg.dependencies;
|
||||
const dependencies_names = !!dependencies ? Object.keys(pkg.dependencies) : [];
|
||||
|
||||
const esm_format = 'es';
|
||||
const browser_format = 'umd';
|
||||
const commonjs_format = 'cjs';
|
||||
|
||||
const postcss_config = {
|
||||
extract: true,
|
||||
modules: false, // Avoid adding prefixes to classnames (etc).
|
||||
extensions: ['.css', '.sss', '.pcss', '.scss'],
|
||||
};
|
||||
|
||||
const postcss_plugin = postcss(postcss_config);
|
||||
const postcss_plugin_minimized = postcss({ ...postcss_config, minimize: true });
|
||||
|
||||
const commonjs_resolve_config = {
|
||||
// pass custom options to the resolve plugin
|
||||
customResolveOptions: { moduleDirectory: 'node_modules' },
|
||||
};
|
||||
|
||||
function beautify_plugin() {
|
||||
return cleanup(/*{
|
||||
maxEmptyLines: 1,
|
||||
sourcemap: false,
|
||||
}*/);
|
||||
}
|
||||
|
||||
function visualizer_plugin(name) {
|
||||
return visualizer({
|
||||
title: name,
|
||||
filename: output_folder + '/visualizer/' + name + '.html',
|
||||
});
|
||||
}
|
||||
|
||||
function es_build(filename, visualize, bundle) {
|
||||
const plugins = [postcss_plugin, json(), beautify_plugin()];
|
||||
|
||||
if (!!visualize) {
|
||||
plugins.push(visualizer_plugin(filename));
|
||||
}
|
||||
|
||||
return {
|
||||
input: input_file,
|
||||
external: !!bundle ? {} : dependencies_names,
|
||||
output: [{ format: esm_format, file: filename }],
|
||||
plugins: plugins,
|
||||
};
|
||||
}
|
||||
|
||||
function commonjs_build(filename, visualize, bundle) {
|
||||
const plugins = [postcss_plugin, json(), resolve(commonjs_resolve_config), beautify_plugin()];
|
||||
|
||||
if (!!visualize) {
|
||||
plugins.push(visualizer_plugin(filename));
|
||||
}
|
||||
|
||||
return {
|
||||
input: input_file,
|
||||
external: !!bundle ? {} : dependencies_names,
|
||||
output: [{ format: commonjs_format, file: filename }],
|
||||
plugins: plugins,
|
||||
};
|
||||
}
|
||||
|
||||
function browser_build(filename, visualize, minimize, compact) {
|
||||
const plugins = [
|
||||
!!minimize ? postcss_plugin_minimized : postcss_plugin,
|
||||
json(),
|
||||
babel(),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
beautify_plugin(),
|
||||
];
|
||||
|
||||
if (!!minimize) {
|
||||
// plugins.push( uglify() );
|
||||
|
||||
if (!!compact) {
|
||||
plugins.push(gzip());
|
||||
}
|
||||
}
|
||||
|
||||
if (!!visualize) {
|
||||
plugins.push(visualizer_plugin(filename));
|
||||
}
|
||||
|
||||
return {
|
||||
input: input_file,
|
||||
output: { name: package_name, format: browser_format, file: filename },
|
||||
plugins: plugins,
|
||||
};
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
es: es_build,
|
||||
browser: browser_build,
|
||||
commonjs: commonjs_build,
|
||||
});
|
||||
}
|
||||
11
frontend/packages/player/config/rollup.config.build.js
Executable file
11
frontend/packages/player/config/rollup.config.build.js
Executable file
@@ -0,0 +1,11 @@
|
||||
import rollup_builds from './includes/rollup_builds';
|
||||
import pckg from '../package.json';
|
||||
|
||||
const dists = rollup_builds('./src/index.js', './out', pckg);
|
||||
|
||||
export default [
|
||||
dists.browser('./dist/mediacms-player.js'),
|
||||
// dists.browser("./dist/mediacms-player.js", true),
|
||||
// dists.browser("./dist/mediacms-player.min.js", true, true),
|
||||
// dists.browser("./dist/mediacms-player.min.js", true, true, true)
|
||||
];
|
||||
6
frontend/packages/player/config/rollup.config.js
Executable file
6
frontend/packages/player/config/rollup.config.js
Executable file
@@ -0,0 +1,6 @@
|
||||
import rollup_builds from './includes/rollup_builds';
|
||||
import pckg from '../package.json';
|
||||
|
||||
const dists = rollup_builds('./src/index.js', './out', pckg);
|
||||
|
||||
export default [dists.browser('./dist/mediacms-player.js')];
|
||||
1239
frontend/packages/player/dist/mediacms-player.css
vendored
Normal file
1239
frontend/packages/player/dist/mediacms-player.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2584
frontend/packages/player/dist/mediacms-player.js
vendored
Normal file
2584
frontend/packages/player/dist/mediacms-player.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16686
frontend/packages/player/package-lock.json
generated
Normal file
16686
frontend/packages/player/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
62
frontend/packages/player/package.json
Executable file
62
frontend/packages/player/package.json
Executable file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"name": "mediacms-player",
|
||||
"version": "0.9.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"keywords": [
|
||||
"mediacms",
|
||||
"media player",
|
||||
"videojs"
|
||||
],
|
||||
"main": "./dist/mediacms-player.js",
|
||||
"module": "./src/",
|
||||
"browser": "./dist/mediacms-player.js",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"browserslist": [
|
||||
"defaults"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "npx rollup -w -c config/rollup.config.js",
|
||||
"build": "npx rollup -c config/rollup.config.build.js",
|
||||
"clean:build": "node ./scripts/rmrf.js ./dist"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"video.js": "^7.12.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mediacms-vjs-plugin": "file:../vjs-plugin"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"@rollup/plugin-commonjs": "^19.0.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"ajv": "^8.6.0",
|
||||
"babel-core": "^6.26.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-polyfill": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"core-js": "^3.14.0",
|
||||
"css-loader": "^5.2.6",
|
||||
"global": "^4.4.0",
|
||||
"json-loader": "^0.5.7",
|
||||
"node-sass": "^6.0.0",
|
||||
"postcss": "^8.3.2",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-babel": "^4.3.3",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-gzip": "^2.5.0",
|
||||
"rollup-plugin-postcss": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.5.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"trim-newlines": "^4.0.2"
|
||||
}
|
||||
}
|
||||
21
frontend/packages/player/scripts/rmrf.js
Executable file
21
frontend/packages/player/scripts/rmrf.js
Executable file
@@ -0,0 +1,21 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var rimraf = require('rimraf');
|
||||
|
||||
var cliArgs = process.argv.slice(2);
|
||||
|
||||
function rmdir_callback(err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
var i, dir;
|
||||
|
||||
for (i = 0; i < cliArgs.length; i++) {
|
||||
dir = path.resolve(cliArgs[i]);
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
rimraf.sync(dir, {}, rmdir_callback);
|
||||
}
|
||||
}
|
||||
453
frontend/packages/player/src/MediaPlayer.js
Executable file
453
frontend/packages/player/src/MediaPlayer.js
Executable file
@@ -0,0 +1,453 @@
|
||||
import 'mediacms-vjs-plugin/dist/mediacms-vjs-plugin.js';
|
||||
import 'mediacms-vjs-plugin/dist/mediacms-vjs-plugin.css';
|
||||
|
||||
function isString(v) {
|
||||
return 'string' === typeof v || v instanceof String;
|
||||
}
|
||||
|
||||
function isArray(v) {
|
||||
return !Array.isArray ? '[object Array]' === Object.prototype.toString.call(v) : Array.isArray(v);
|
||||
}
|
||||
|
||||
function isBoolean(v) {
|
||||
return 'boolean' === typeof v || v instanceof Boolean;
|
||||
}
|
||||
|
||||
function ifBooleanElse(bol, els) {
|
||||
return isBoolean(bol) ? bol : els;
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
options: {
|
||||
sources: [],
|
||||
keyboardControls: !0,
|
||||
enabledTouchControls: !0,
|
||||
nativeDimensions: !1,
|
||||
suppressNotSupportedError: !0,
|
||||
poster: '',
|
||||
loop: !1,
|
||||
controls: !0,
|
||||
preload: 'auto',
|
||||
autoplay: !1,
|
||||
bigPlayButton: !0,
|
||||
liveui: !1,
|
||||
controlBar: {
|
||||
bottomBackground: !0,
|
||||
progress: !0,
|
||||
play: !0,
|
||||
next: !1,
|
||||
previous: !1,
|
||||
volume: !0,
|
||||
pictureInPicture: !0, // @link: https://docs.videojs.com/control-bar_picture-in-picture-toggle.js.html
|
||||
fullscreen: !0,
|
||||
theaterMode: !0,
|
||||
time: !0,
|
||||
},
|
||||
cornerLayers: {
|
||||
topLeft: null,
|
||||
topRight: null,
|
||||
bottomLeft: null,
|
||||
bottomRight: null,
|
||||
},
|
||||
videoPreviewThumb: {},
|
||||
subtitles: {
|
||||
on: false,
|
||||
default: null,
|
||||
languages: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Filter plugin options values.
|
||||
* @param {Object} opt Options object.
|
||||
* @return {Object} Filtered/Validated options object.
|
||||
*/
|
||||
function filterPlayerOptions(domPlayer, opt) {
|
||||
let k, x, j, i;
|
||||
|
||||
opt.sources = isArray(opt.sources) && opt.sources.length ? opt.sources : [];
|
||||
opt.loop = ifBooleanElse(opt.loop, defaults.options.loop);
|
||||
opt.controls = ifBooleanElse(opt.controls, defaults.options.controls);
|
||||
|
||||
if (opt.subtitles && opt.subtitles instanceof Object) {
|
||||
opt.subtitles.default = void 0 !== opt.subtitles.default ? opt.subtitles.default : defaults.options.subtitles.default;
|
||||
opt.subtitles.languages = isArray(opt.subtitles.languages)
|
||||
? opt.subtitles.languages
|
||||
: defaults.options.subtitles.languages;
|
||||
opt.subtitles.on = ifBooleanElse(opt.subtitles.on, defaults.options.subtitles.on);
|
||||
} else {
|
||||
opt.subtitles.default = defaults.options.subtitles;
|
||||
}
|
||||
|
||||
opt.autoplay =
|
||||
'any' === opt.autoplay || 'play' === opt.autoplay || 'muted' === opt.autoplay
|
||||
? opt.autoplay
|
||||
: ifBooleanElse(opt.autoplay, defaults.options.autoplay);
|
||||
|
||||
// console.log(opt.autoplay);
|
||||
|
||||
opt.bigPlayButton = ifBooleanElse(opt.bigPlayButton, defaults.options.bigPlayButton);
|
||||
opt.poster = isString(opt.poster) && '' !== opt.poster.trim() ? opt.poster : defaults.options.poster;
|
||||
opt.preload =
|
||||
isString(opt.preload) && -1 < ['auto', 'metadata', 'none'].indexOf(opt.preload.trim())
|
||||
? opt.preload
|
||||
: defaults.options.preload;
|
||||
|
||||
// Control bar options.
|
||||
if (opt.controlBar && opt.controlBar instanceof Object && Object.keys(opt.controlBar).length) {
|
||||
for (k in opt.controlBar) {
|
||||
if (opt.controlBar.hasOwnProperty(k)) {
|
||||
opt.controlBar[k] = ifBooleanElse(opt.controlBar[k], defaults.options.controlBar[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Corner layers.
|
||||
if (opt.cornerLayers && opt.cornerLayers instanceof Object && Object.keys(opt.cornerLayers).length) {
|
||||
for (k in opt.cornerLayers) {
|
||||
if (opt.cornerLayers.hasOwnProperty(k)) {
|
||||
if ('string' === typeof opt.cornerLayers[k]) {
|
||||
opt.cornerLayers[k] = '' !== opt.cornerLayers[k] ? opt.cornerLayers[k] : defaults.options.cornerLayers[k];
|
||||
} else if (Node.prototype.isPrototypeOf(opt.cornerLayers[k]) || !isNaN(opt.cornerLayers[k])) {
|
||||
opt.cornerLayers[k] = opt.cornerLayers[k];
|
||||
} else {
|
||||
opt.cornerLayers[k] = opt.cornerLayers[k] || defaults.options.cornerLayers[k];
|
||||
}
|
||||
} else {
|
||||
opt.cornerLayers[k] = defaults.options.cornerLayers[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opt.previewSprite = 'object' === typeof opt.previewSprite ? opt.previewSprite : {};
|
||||
|
||||
// Include HTML sources.
|
||||
|
||||
let obj;
|
||||
let sources_el = domPlayer.querySelectorAll('source');
|
||||
|
||||
i = 0;
|
||||
while (i < sources_el.length) {
|
||||
if (void 0 !== sources_el[i].attributes.src) {
|
||||
obj = {
|
||||
src: sources_el[i].src,
|
||||
};
|
||||
|
||||
if (void 0 !== sources_el[i].attributes.type) {
|
||||
obj.type = sources_el[i].type;
|
||||
}
|
||||
|
||||
x = 0;
|
||||
while (x < opt.sources.length && obj.src !== opt.sources[x].src) {
|
||||
x += 1;
|
||||
}
|
||||
|
||||
if (x >= opt.sources.length) {
|
||||
opt.sources.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// Include HTML subtitle tracks.
|
||||
|
||||
let subs_el = domPlayer.querySelectorAll('track[kind="subtitles"]');
|
||||
|
||||
const subtitles_options = {
|
||||
on: opt.subtitles.on,
|
||||
default: null,
|
||||
languages: [],
|
||||
};
|
||||
|
||||
const languages = {};
|
||||
|
||||
function addSubtitle(track) {
|
||||
track.src = void 0 !== track.src && null !== track.src ? track.src.toString().trim() : '';
|
||||
track.srclang = void 0 !== track.srclang && null !== track.srclang ? track.srclang.toString().trim() : '';
|
||||
|
||||
if (track.src.length && track.srclang.length) {
|
||||
track.label = void 0 !== track.label && null !== track.label ? track.label.toString().trim() : track.srclang;
|
||||
|
||||
if (void 0 !== languages[track.srclang]) {
|
||||
languages[track.srclang].src = track.src;
|
||||
languages[track.srclang].label = track.label;
|
||||
} else {
|
||||
subtitles_options.languages.push({
|
||||
label: track.label,
|
||||
src: track.src,
|
||||
srclang: track.srclang,
|
||||
});
|
||||
|
||||
languages[track.srclang] = subtitles_options.languages[subtitles_options.languages.length - 1];
|
||||
}
|
||||
|
||||
if (void 0 !== track.default && null !== track.default) {
|
||||
track.default = track.default.toString().trim();
|
||||
|
||||
if (!track.default.length || '1' === track.default || 'true' === track.default) {
|
||||
subtitles_options.default = track.srclang;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (i < subs_el.length) {
|
||||
addSubtitle({
|
||||
src: subs_el[i].getAttribute('src'),
|
||||
srclang: subs_el[i].getAttribute('srclang'),
|
||||
default: subs_el[i].getAttribute('default'),
|
||||
label: subs_el[i].getAttribute('label'),
|
||||
});
|
||||
|
||||
i += 1;
|
||||
}
|
||||
|
||||
if (opt.subtitles.languages.length) {
|
||||
i = 0;
|
||||
|
||||
while (i < opt.subtitles.languages.length) {
|
||||
addSubtitle({
|
||||
src: opt.subtitles.languages[i].src,
|
||||
srclang: opt.subtitles.languages[i].srclang,
|
||||
default: opt.subtitles.languages[i].default,
|
||||
label: opt.subtitles.languages[i].label,
|
||||
});
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (null !== opt.subtitles.default && void 0 !== languages[opt.subtitles.default]) {
|
||||
subtitles_options.default = opt.subtitles.default;
|
||||
}
|
||||
|
||||
if (null === subtitles_options.default && opt.subtitles.languages.length) {
|
||||
subtitles_options.default = opt.subtitles.languages[0].srclang;
|
||||
}
|
||||
|
||||
opt.subtitles = subtitles_options;
|
||||
|
||||
return opt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct VideoJs options by player options.
|
||||
* @param {Object} opt Plugin options.
|
||||
* @param {Object} vjopt Initial VideoJs object.
|
||||
* @return {Object} Final VideoJs object.
|
||||
*/
|
||||
function constructVideojsOptions(opt, vjopt) {
|
||||
// {
|
||||
// /*autoplay: false,
|
||||
// controls: true,
|
||||
// preload: "auto", // preload: "metadata",
|
||||
// loop: false,
|
||||
// bigPlayButton: true,*/
|
||||
// // poster: "",
|
||||
// // width: "",
|
||||
// // height: "",
|
||||
// // children: {}
|
||||
// controlBar: {
|
||||
// children: [],
|
||||
// // children: {
|
||||
// // bottomGradientComponent: true,
|
||||
// // progressControl: true, // (hidden during live playback)
|
||||
// // leftControls: true,
|
||||
// // // playbackRateMenuButton: true, // (hidden, unless playback tech supports rate changes)
|
||||
// // // chaptersButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // // descriptionsButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // // subtitlesButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // // captionsButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // // audioTrackButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // }
|
||||
// // seekBar: false,
|
||||
// // loadProgressBar: false,
|
||||
// // mouseTimeDisplay: false,
|
||||
// // playProgressBar: false,
|
||||
// // liveDisplay: false, // (hidden during VOD playback)
|
||||
// // remainingTimeDisplay: false,
|
||||
// // customControlSpacer: false, // (has no UI)
|
||||
// // playbackRateMenuButton: true, // (hidden, unless playback tech supports rate changes)
|
||||
// // chaptersButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // descriptionsButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // subtitlesButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // captionsButton: true, // (hidden, unless there are relevant tracks)
|
||||
// // audioTrackButton: true, // (hidden, unless there are relevant tracks)
|
||||
// }
|
||||
// }
|
||||
|
||||
vjopt.sources = opt.sources;
|
||||
vjopt.loop = opt.loop;
|
||||
vjopt.controls = opt.controls;
|
||||
vjopt.autoplay = opt.autoplay;
|
||||
vjopt.bigPlayButton = opt.bigPlayButton;
|
||||
vjopt.poster = opt.poster;
|
||||
vjopt.preload = opt.preload;
|
||||
vjopt.suppressNotSupportedError = opt.suppressNotSupportedError;
|
||||
|
||||
// console.log( vjopt );
|
||||
// console.log( opt );
|
||||
|
||||
return vjopt;
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper/container class of MediaCMS VideoJs player.
|
||||
* @param {DOM Node} domPlayer The video element in html.
|
||||
* @param {Object} pluginOptions Plugin (genral player's) options.
|
||||
* @param {Object} pluginState Plugin initial state values.
|
||||
* @param {Function} pluginStateUpdateCallback The function will be called on plugin's state values update.
|
||||
*/
|
||||
export function MediaPlayer(
|
||||
domPlayer,
|
||||
pluginOptions,
|
||||
pluginState,
|
||||
videoResolutions,
|
||||
videoPlaybackSpeeds,
|
||||
pluginStateUpdateCallback,
|
||||
onNextButtonClick,
|
||||
onPrevButtonClick
|
||||
) {
|
||||
if (!Node.prototype.isPrototypeOf(domPlayer)) {
|
||||
console.error('Invalid player DOM element', domPlayer); // TODO: Validate that element is <video> or <audio>.
|
||||
return null;
|
||||
}
|
||||
|
||||
function sourcesSrcs(urls) {
|
||||
const ret = [];
|
||||
let i = 0;
|
||||
while (i < urls.length) {
|
||||
if (!!urls[i]) {
|
||||
ret.push(urls[i]); // @todo: Validate url file extension.
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function sourcesFormats(formats) {
|
||||
const ret = [];
|
||||
let i = 0;
|
||||
while (i < formats.length) {
|
||||
if (!!formats[i]) {
|
||||
ret.push(formats[i]); // @todo: Validate format.
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
let k,
|
||||
i,
|
||||
pluginVideoResolutions = {},
|
||||
pluginVideoPlaybackSpeeds = {};
|
||||
|
||||
if (!!videoResolutions) {
|
||||
for (k in videoResolutions) {
|
||||
if (videoResolutions.hasOwnProperty(k)) {
|
||||
if (
|
||||
isArray(videoResolutions[k].url) &&
|
||||
videoResolutions[k].url.length &&
|
||||
isArray(videoResolutions[k].format) &&
|
||||
videoResolutions[k].format.length
|
||||
) {
|
||||
pluginVideoResolutions[k] = {
|
||||
title: k,
|
||||
src: sourcesSrcs(videoResolutions[k].url),
|
||||
format: sourcesFormats(videoResolutions[k].format),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!!videoPlaybackSpeeds) {
|
||||
k = 0;
|
||||
while (k < videoPlaybackSpeeds.length) {
|
||||
pluginVideoPlaybackSpeeds[k] = {
|
||||
title: 1 === videoPlaybackSpeeds[k] ? 'Normal' : videoPlaybackSpeeds[k],
|
||||
speed: videoPlaybackSpeeds[k].toString(),
|
||||
};
|
||||
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Filter options value.
|
||||
*/
|
||||
|
||||
// console.log( '####################' );
|
||||
// console.log( domPlayer );
|
||||
// console.log( defaults.options );
|
||||
// console.log( Object.keys(pluginOptions) );
|
||||
|
||||
pluginOptions = filterPlayerOptions(
|
||||
domPlayer,
|
||||
videojs.mergeOptions(
|
||||
defaults.options,
|
||||
pluginOptions && pluginOptions instanceof Object && Object.keys(pluginOptions).length ? pluginOptions : {}
|
||||
)
|
||||
);
|
||||
|
||||
// console.log( pluginOptions );
|
||||
// console.log( '####################' );
|
||||
|
||||
/*
|
||||
* Filter state value.
|
||||
*/
|
||||
|
||||
// console.log( '####################' );
|
||||
// console.log( pluginState );
|
||||
|
||||
// console.warn( pluginOptions.subtitles );
|
||||
// console.log( pluginState );
|
||||
if (null !== pluginOptions.subtitles.default && pluginOptions.subtitles.on) {
|
||||
pluginState.theSelectedSubtitleOption = pluginOptions.subtitles.default;
|
||||
}
|
||||
// console.log( pluginState );
|
||||
|
||||
// console.log( pluginState );
|
||||
// console.log( '####################' );
|
||||
|
||||
/*
|
||||
* Initialize videojs player.
|
||||
*/
|
||||
|
||||
const passOptions = constructVideojsOptions(pluginOptions, {
|
||||
controlBar: {
|
||||
children: [],
|
||||
},
|
||||
});
|
||||
|
||||
this.player = videojs(domPlayer, passOptions);
|
||||
|
||||
/*
|
||||
* Call plugin.
|
||||
*/
|
||||
|
||||
this.player.mediaCmsVjsPlugin(
|
||||
domPlayer,
|
||||
pluginOptions,
|
||||
pluginState,
|
||||
pluginVideoResolutions,
|
||||
pluginVideoPlaybackSpeeds,
|
||||
pluginStateUpdateCallback,
|
||||
onNextButtonClick,
|
||||
onPrevButtonClick
|
||||
);
|
||||
|
||||
/*
|
||||
* Public methods.
|
||||
*/
|
||||
|
||||
this.isEnded = this.player.mediaCmsVjsPlugin().isEnded;
|
||||
this.isFullscreen = this.player.mediaCmsVjsPlugin().isFullscreen;
|
||||
this.isTheaterMode = this.player.mediaCmsVjsPlugin().isTheaterMode;
|
||||
|
||||
if (void 0 !== typeof window) {
|
||||
window.HELP_IMPROVE_VIDEOJS = false;
|
||||
}
|
||||
}
|
||||
3
frontend/packages/player/src/index.js
Executable file
3
frontend/packages/player/src/index.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { MediaPlayer } from './MediaPlayer';
|
||||
|
||||
export default MediaPlayer;
|
||||
36
frontend/packages/scripts/.eslintrc.js
Executable file
36
frontend/packages/scripts/.eslintrc.js
Executable file
@@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
"extends": ["eslint:recommended"],
|
||||
"env": { "es6": true, "browser": true, "node": true },
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2018
|
||||
},
|
||||
"rules": {
|
||||
"indent": ["error", "tab"],
|
||||
"linebreak-style": ["error", "unix"],
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
'no-empty': ["error", { "allowEmptyCatch": true }],
|
||||
'no-constant-condition': ["error", { "checkLoops": false }],
|
||||
},
|
||||
"overrides": [{
|
||||
"plugins": [ "@typescript-eslint" ],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"files": ["**/*.ts", "**/*.tsx"],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": { "jsx": true },
|
||||
"ecmaVersion": 2018,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"rules": {
|
||||
"no-mixed-spaces-and-tabs": ["error", "smart-tabs"],
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
},
|
||||
}]
|
||||
}
|
||||
1
frontend/packages/scripts/README.md
Normal file
1
frontend/packages/scripts/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# mediacms-scripts
|
||||
47
frontend/packages/scripts/cli.js
Executable file
47
frontend/packages/scripts/cli.js
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const spawn = require('cross-spawn');
|
||||
|
||||
('use strict');
|
||||
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on('unhandledRejection', (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const scriptIndex = args.findIndex(
|
||||
(x) => x === 'build' || x === 'development' || x === 'analyzer' || x === 'rimraf' // TODO
|
||||
);
|
||||
const script = scriptIndex === -1 ? args[0] : args[scriptIndex];
|
||||
const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : [];
|
||||
|
||||
if (['build', 'development', 'analyzer', 'rimraf'].includes(script)) {
|
||||
const result = spawn.sync(
|
||||
process.execPath,
|
||||
nodeArgs.concat(require.resolve('./scripts/' + script)).concat(args.slice(scriptIndex + 1)),
|
||||
{ stdio: 'inherit' }
|
||||
);
|
||||
if (result.signal) {
|
||||
if (result.signal === 'SIGKILL') {
|
||||
console.log(
|
||||
'The build failed because the process exited too early. ' +
|
||||
'This probably means the system ran out of memory or someone called ' +
|
||||
'`kill -9` on the process.'
|
||||
);
|
||||
} else if (result.signal === 'SIGTERM') {
|
||||
console.log(
|
||||
'The build failed because the process exited too early. ' +
|
||||
'Someone might have called `kill` or `killall`, or the system could ' +
|
||||
'be shutting down.'
|
||||
);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(result.status);
|
||||
} else {
|
||||
console.log('Unknown script "' + script + '".');
|
||||
}
|
||||
3
frontend/packages/scripts/config/build.config.js
Executable file
3
frontend/packages/scripts/config/build.config.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { buildCommonjs } from './helpers/buildCommonjs.js';
|
||||
|
||||
export default buildCommonjs('./src/index.ts', '.')('./dist/webpack-dev-env.js');
|
||||
24
frontend/packages/scripts/config/helpers/buildCommonjs.js
Executable file
24
frontend/packages/scripts/config/helpers/buildCommonjs.js
Executable file
@@ -0,0 +1,24 @@
|
||||
import typescript from 'rollup-plugin-typescript2';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import visualizer from 'rollup-plugin-visualizer';
|
||||
import cleanup from 'rollup-plugin-cleanup';
|
||||
|
||||
export function buildCommonjs(input_file, output_folder) {
|
||||
return function (filename, visualize) {
|
||||
const plugins = [
|
||||
resolve({ customResolveOptions: { moduleDirectories: 'node_modules' } }),
|
||||
typescript(),
|
||||
cleanup({ comments: 'none' }),
|
||||
];
|
||||
|
||||
if (visualize) {
|
||||
plugins.push(visualizer({ title: filename, filename: output_folder + filename + '.html' }));
|
||||
}
|
||||
|
||||
return {
|
||||
input: input_file,
|
||||
output: [{ format: 'cjs', file: filename }],
|
||||
plugins: plugins,
|
||||
};
|
||||
};
|
||||
}
|
||||
3
frontend/packages/scripts/config/visual.config.js
Executable file
3
frontend/packages/scripts/config/visual.config.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { buildCommonjs } from './helpers/buildCommonjs.js';
|
||||
|
||||
export default buildCommonjs('./src/index.ts', './visualizer/')('./dist/webpack-dev-env.js', true);
|
||||
3
frontend/packages/scripts/config/watch.config.js
Executable file
3
frontend/packages/scripts/config/watch.config.js
Executable file
@@ -0,0 +1,3 @@
|
||||
import { buildCommonjs } from './helpers/buildCommonjs.js';
|
||||
|
||||
export default buildCommonjs('./src/index.ts', '.')('./dist/webpack-dev-env.js');
|
||||
782
frontend/packages/scripts/dist/webpack-dev-env.js
vendored
Normal file
782
frontend/packages/scripts/dist/webpack-dev-env.js
vendored
Normal file
@@ -0,0 +1,782 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var __assign = function() {
|
||||
__assign = Object.assign || function __assign(t) {
|
||||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||||
s = arguments[i];
|
||||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
||||
}
|
||||
return t;
|
||||
};
|
||||
return __assign.apply(this, arguments);
|
||||
};
|
||||
function __spreadArray(to, from, pack) {
|
||||
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
||||
if (ar || !(i in from)) {
|
||||
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
||||
ar[i] = from[i];
|
||||
}
|
||||
}
|
||||
return to.concat(ar || from);
|
||||
}
|
||||
|
||||
function bodySnippet(id) {
|
||||
return '<div id="' + id + '"></div>';
|
||||
}
|
||||
var homePage = {
|
||||
staticPage: true,
|
||||
buildExclude: false,
|
||||
title: 'Home',
|
||||
filename: 'index.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: bodySnippet('page-home'),
|
||||
}
|
||||
},
|
||||
window: {},
|
||||
render: 'import { renderPage } from \'./js/helpers\'; import { HomePage } from \'./js/pages/HomePage\'; renderPage( \'page-home\', HomePage );',
|
||||
};
|
||||
var errorPage = {
|
||||
staticPage: true,
|
||||
buildExclude: false,
|
||||
title: 'Error',
|
||||
filename: 'error.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: bodySnippet('page-error'),
|
||||
}
|
||||
},
|
||||
window: {},
|
||||
render: 'import { renderPage } from \'./js/helpers\'; import { ErrorPage } from \'./js/pages/ErrorPage\'; renderPage( \'page-error\', ErrorPage );',
|
||||
};
|
||||
var pages = {
|
||||
home: homePage,
|
||||
error: errorPage,
|
||||
};
|
||||
var htmlHead = {
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
],
|
||||
links: [],
|
||||
scripts: [],
|
||||
};
|
||||
var htmlBody = {
|
||||
scripts: [],
|
||||
snippet: '',
|
||||
};
|
||||
var html = {
|
||||
head: htmlHead,
|
||||
body: htmlBody,
|
||||
};
|
||||
var config$3 = {
|
||||
src: '',
|
||||
build: '',
|
||||
pages: pages,
|
||||
html: html,
|
||||
window: {},
|
||||
postcssConfigFile: '',
|
||||
};
|
||||
|
||||
/*const chunksCacheGroups_0 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
/*const chunksCacheGroups_1 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
// priority: -10,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
|
||||
name: "_vendors",
|
||||
// priority: -20,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
// reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
/*const chunksCacheGroups_2 = {
|
||||
commons: {
|
||||
minChunks: 2,
|
||||
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
|
||||
// minSize: 0,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
/*const chunksCacheGroups_3 = {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
};*/
|
||||
var config$2 = {
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
/*splitChunks: {
|
||||
// minSize: 1000000,
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
// minSize: 1000000,
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_0,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_1,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_2,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_3,
|
||||
},*/
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/*const chunksCacheGroups_0 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_1 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
// priority: -10,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
|
||||
name: "_vendors",
|
||||
// priority: -20,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
// reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_2 = {
|
||||
commons: {
|
||||
minChunks: 2,
|
||||
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
|
||||
// minSize: 0,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_3 = {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
};*/
|
||||
var config$1 = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
runtimeChunk: false,
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @see {link: https://github.com/seeyoulater/html-beautify-webpack-plugin/blob/master/index.js}
|
||||
*/
|
||||
var prettify = require('html-prettify');
|
||||
var HtmlWebpackPlugin$1 = require('html-webpack-plugin');
|
||||
require('webpack/lib/WebpackError.js');
|
||||
function htmlPluginDataFunction(pluginData, options, callback) {
|
||||
pluginData.html = prettify(options.replace.reduce(function (res, item) { return res.replace(item instanceof RegExp ? new RegExp(item, 'gi') : item, ''); }, pluginData.html) /*,
|
||||
options.config*/);
|
||||
callback(null, pluginData);
|
||||
}
|
||||
var MyHtmlBeautifyWebpackPlugin = /** @class */ (function () {
|
||||
function MyHtmlBeautifyWebpackPlugin() {
|
||||
}
|
||||
MyHtmlBeautifyWebpackPlugin.prototype.apply = function (compiler) {
|
||||
var options = {
|
||||
config: {
|
||||
indent_size: 4,
|
||||
indent_with_tabs: false,
|
||||
html: {
|
||||
end_with_newline: true,
|
||||
indent_inner_html: true,
|
||||
preserve_newlines: true,
|
||||
max_preserve_newlines: 0,
|
||||
}
|
||||
},
|
||||
replace: []
|
||||
};
|
||||
function tapAsyncCallback(pluginData, callback) {
|
||||
return htmlPluginDataFunction(pluginData, options, callback);
|
||||
}
|
||||
function tapHookCallback(compilation) {
|
||||
return HtmlWebpackPlugin$1.getHooks(compilation).beforeEmit.tapAsync('MyHtmlBeautifyWebpackPlugin', tapAsyncCallback);
|
||||
}
|
||||
compiler.hooks.compilation.tap('MyHtmlBeautifyWebpackPlugin', tapHookCallback);
|
||||
};
|
||||
return MyHtmlBeautifyWebpackPlugin;
|
||||
}());
|
||||
|
||||
var fs = require('fs');
|
||||
var path$1 = require('path');
|
||||
var ejs = require('ejs');
|
||||
var templatePath = path$1.join(__dirname, '../templates');
|
||||
var sitemapTemplatePath = path$1.join(templatePath, 'sitemap.ejs');
|
||||
var sitemapTemplate = ejs.compile(fs.readFileSync(sitemapTemplatePath, 'utf8'), { root: [templatePath], filename: sitemapTemplatePath, outputFunctionName: 'echo' });
|
||||
function pagesConfig(pagesKeys) {
|
||||
var pages = {};
|
||||
if (-1 === pagesKeys.indexOf('sitemap')) {
|
||||
pages.sitemap = {
|
||||
staticPage: true,
|
||||
buildExclude: true,
|
||||
title: 'Sitemap',
|
||||
filename: 'sitemap.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: sitemapTemplate({ pages: __spreadArray(__spreadArray([], pagesKeys), Object.keys(pages)) }),
|
||||
},
|
||||
},
|
||||
window: {},
|
||||
render: ''
|
||||
};
|
||||
}
|
||||
return pages;
|
||||
}
|
||||
|
||||
var merge = require('lodash.merge');
|
||||
function validateBoolean(value, defaultValue) {
|
||||
if (defaultValue === void 0) { defaultValue = false; }
|
||||
if (true === value || false === value) {
|
||||
return value;
|
||||
}
|
||||
if (0 === value || 1 === value) {
|
||||
return !!value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
function validateString(value, defaultValue) {
|
||||
if (defaultValue === void 0) { defaultValue = ''; }
|
||||
return value ? value : defaultValue;
|
||||
}
|
||||
function getArrayType(sourcesArr, pageArr) {
|
||||
if (pageArr === void 0) { pageArr = []; }
|
||||
if ((!sourcesArr || !sourcesArr.length) && (!pageArr || !pageArr.length)) {
|
||||
return [];
|
||||
}
|
||||
if (sourcesArr && sourcesArr.length && pageArr && pageArr.length) {
|
||||
return sourcesArr.concat(pageArr);
|
||||
}
|
||||
if (sourcesArr && sourcesArr.length) {
|
||||
return sourcesArr;
|
||||
}
|
||||
return pageArr;
|
||||
}
|
||||
function formatPagesConfig(sources, pages) {
|
||||
var ret = {};
|
||||
for (var pk in pages) {
|
||||
ret[pk] = {
|
||||
staticPage: validateBoolean(pages[pk].staticPage, false),
|
||||
buildExclude: validateBoolean(pages[pk].buildExclude, false),
|
||||
title: validateString(pages[pk].title, sources.title),
|
||||
filename: validateString(pages[pk].filename, sources.filename),
|
||||
html: {
|
||||
head: {
|
||||
meta: getArrayType(sources.html.head.meta, pages[pk].html.head.meta),
|
||||
links: getArrayType(sources.html.head.links, pages[pk].html.head.links),
|
||||
scripts: getArrayType(sources.html.head.scripts, pages[pk].html.head.scripts),
|
||||
},
|
||||
body: {
|
||||
scripts: getArrayType(sources.html.body.scripts, pages[pk].html.body.scripts),
|
||||
snippet: validateString(pages[pk].html.body.snippet, sources.html.body.snippet),
|
||||
},
|
||||
},
|
||||
window: merge({}, sources.window, pages[pk].window),
|
||||
render: validateString(sources.render, pages[pk].render),
|
||||
};
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
var path = require('path');
|
||||
var NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
|
||||
// Webpack plugins.
|
||||
var DefinePlugin = require('webpack').DefinePlugin;
|
||||
var LimitChunkCountPlugin = require('webpack').optimize.LimitChunkCountPlugin;
|
||||
var HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
var VirtualModulesPlugin = require('webpack-virtual-modules');
|
||||
var MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
var ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||
var CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
var CopyPlugin = require("copy-webpack-plugin");
|
||||
var dotenv = require('dotenv').config({ path: path.resolve(__dirname + '../../../../.env') });
|
||||
function webpackEntry(env, srcDir, pages) {
|
||||
var ret = {};
|
||||
for (var p in pages) {
|
||||
if ('development' === env || !pages[p].buildExclude) {
|
||||
ret[p] = path.resolve(srcDir + '/' + p + '.js');
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function webpackOutput(env, destinationDir, buildDir, chunkhash, hash) {
|
||||
var ret = {
|
||||
path: destinationDir,
|
||||
filename: '',
|
||||
};
|
||||
var prefix = 'development' === env ? '' : buildDir;
|
||||
var tmp;
|
||||
if (undefined !== chunkhash) {
|
||||
tmp = chunkhash.trim();
|
||||
if ('' === tmp) {
|
||||
throw Error('Invalid chunkhash argument value: ' + chunkhash);
|
||||
}
|
||||
ret.filename = (prefix || '') + '[name]-[chunkhash].js';
|
||||
}
|
||||
else if (undefined !== hash) {
|
||||
tmp = hash.trim();
|
||||
if ('' === tmp) {
|
||||
throw Error('Invalid hash argument value: ' + hash);
|
||||
}
|
||||
ret.filename = (prefix || '') + '[name]-[hash].js';
|
||||
}
|
||||
else {
|
||||
ret.filename = (prefix || '') + '[name].js';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function webpackAlias() {
|
||||
return {
|
||||
// modernizr$: path.resolve(__dirname, "../../.modernizrrc"), // TODO: Enable this?
|
||||
};
|
||||
}
|
||||
function webpackRules(env, srcDir, postcssConfigFile) {
|
||||
return [{
|
||||
test: /\.(jsx|js)?$/,
|
||||
use: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(tsx|ts)?$/,
|
||||
use: 'ts-loader',
|
||||
// exclude: /node_modules/,
|
||||
// options: {
|
||||
// compilerOptions: {
|
||||
// "sourceMap": !isProduction,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
test: /\.ejs$/,
|
||||
use: {
|
||||
loader: 'ejs-compiled-loader',
|
||||
options: {
|
||||
// beautify: true,
|
||||
htmlmin: true,
|
||||
// htmlminOptions: {
|
||||
// removeComments: true,
|
||||
// collapseWhitespace: true,
|
||||
// preserveLineBreaks: true
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(sa|sc|c)ss$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
|
||||
{ loader: 'css-loader', options: { importLoaders: 1 } },
|
||||
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
|
||||
{ loader: 'sass-loader' },
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.module\.(sa|sc|c)ss$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
|
||||
{ loader: 'css-loader', options: { importLoaders: 1, modules: true, onlyLocals: false } },
|
||||
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
|
||||
{ loader: 'sass-loader' },
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)(\?\S*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 1024,
|
||||
fallback: 'file-loader',
|
||||
name: function (file) {
|
||||
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
/*issuer: {
|
||||
test: /\.jsx?$/
|
||||
},*/
|
||||
use: ['babel-loader', '@svgr/webpack', 'url-loader']
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'url-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: function (file) {
|
||||
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
|
||||
},
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.modernizrrc.js$/,
|
||||
use: 'modernizr-loader',
|
||||
},
|
||||
{
|
||||
test: /\.modernizrrc(\.json)?$/,
|
||||
use: ['modernizr-loader', 'json-loader'],
|
||||
}];
|
||||
}
|
||||
function webpackPlugins(env, srcDir, pages, cssSrc) {
|
||||
var ret = [
|
||||
new DefinePlugin({ "process.env": JSON.stringify(dotenv.parsed) }),
|
||||
new NodePolyfillPlugin(),
|
||||
new MyHtmlBeautifyWebpackPlugin(),
|
||||
];
|
||||
if ('development' !== env) {
|
||||
ret.push(new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/lib'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/lib'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/images'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/images'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/favicons'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/favicons'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/css/_extra.css'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/css/_extra.css'),
|
||||
},
|
||||
],
|
||||
}));
|
||||
}
|
||||
var virtualPages = {};
|
||||
var file;
|
||||
for (var k in pages) {
|
||||
if ('production' !== env || !pages[k].buildExclude) {
|
||||
file = path.resolve(srcDir + '/' + k + '.js');
|
||||
if ((void 0 !== pages[k].staticPage && pages[k].staticPage) || void 0 === pages[k].render) {
|
||||
virtualPages[file] = '';
|
||||
}
|
||||
else {
|
||||
virtualPages[file] = pages[k].render;
|
||||
}
|
||||
}
|
||||
if ('development' === env) {
|
||||
// Export pages HTML files.
|
||||
ret.push(new HtmlWebpackPlugin(__assign({ template: path.resolve(__dirname, '../templates/index.ejs'), hash: false, chunks: [k] }, pages[k])));
|
||||
}
|
||||
}
|
||||
ret.push(new VirtualModulesPlugin(virtualPages));
|
||||
ret.push(new MiniCssExtractPlugin({
|
||||
ignoreOrder: true,
|
||||
// filename: ! is_build ? '[name].css' : '[name].[hash].css',
|
||||
// chunkFilename: ! is_build ? '[id].css' : '[id].[hash].css',
|
||||
filename: cssSrc + '[name].css',
|
||||
// chunkFilename: "../css/[id].css",
|
||||
}));
|
||||
if ('development' !== env) {
|
||||
ret.push(new LimitChunkCountPlugin({ maxChunks: 1 }));
|
||||
ret.push(new ProgressBarPlugin({
|
||||
clear: false,
|
||||
}));
|
||||
}
|
||||
if ('production' === env) {
|
||||
ret.push(new CssMinimizerPlugin({
|
||||
cache: true,
|
||||
minimizerOptions: {
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
discardComments: { removeAll: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
function generateConfig(env, config) {
|
||||
var srcDir = config.src;
|
||||
var buildDir = config.build + '/' + env + ('development' === env ? '' : '/static');
|
||||
var cssbuild = './css/';
|
||||
var jsbuild = './js/';
|
||||
var configPages = config.pages;
|
||||
var configPagesKeys = config.pages ? Object.keys(configPages) : [];
|
||||
var defPages = pagesConfig(configPagesKeys);
|
||||
var pages = formatPagesConfig({ title: '', filename: '', render: '', html: config.html, window: config.window }, __assign(__assign({}, configPages), defPages));
|
||||
var ret = {
|
||||
entry: webpackEntry(env, srcDir, pages),
|
||||
output: 'development' === env ? webpackOutput(env, srcDir, void 0, void 0, void 0) : webpackOutput(env, buildDir, jsbuild, void 0, void 0),
|
||||
plugins: webpackPlugins(env, srcDir, pages, cssbuild),
|
||||
module: {
|
||||
rules: webpackRules(env, srcDir, config.postcssConfigFile),
|
||||
},
|
||||
resolve: {
|
||||
alias: webpackAlias(),
|
||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||
},
|
||||
};
|
||||
return ret;
|
||||
}
|
||||
|
||||
var isAbsolutePath$2 = require('path').isAbsolute;
|
||||
var webpack$2 = require('webpack');
|
||||
var webpackFormatMessages$1 = require('webpack-format-messages');
|
||||
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
var defaultOptions$2 = {
|
||||
env: 'production',
|
||||
host: '127.0.0.1',
|
||||
port: 8888,
|
||||
mode: 'static',
|
||||
config: config$3,
|
||||
};
|
||||
function analyzer(analyzerOptions) {
|
||||
if (analyzerOptions === void 0) { analyzerOptions = defaultOptions$2; }
|
||||
var options = __assign(__assign({}, defaultOptions$2), analyzerOptions);
|
||||
options.config = __assign(__assign({}, defaultOptions$2.config), analyzerOptions.config);
|
||||
var config = generateConfig(options.env, options.config);
|
||||
if (!isAbsolutePath$2(options.config.src)) {
|
||||
throw Error('"src" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath$2(options.config.build)) {
|
||||
throw Error('"build" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath$2(options.config.postcssConfigFile)) {
|
||||
throw Error('"postcssConfigFile" is not an absolute path');
|
||||
}
|
||||
var analyzerConfig = {
|
||||
analyzerMode: options.mode,
|
||||
analyzerHost: options.host,
|
||||
analyzerPort: options.port,
|
||||
generateStatsFile: 'server' !== options.mode,
|
||||
startAnalyzer: 'server' === options.mode,
|
||||
statsFilename: 'analyzer-stats.json',
|
||||
reportFilename: 'analyzer-report.html',
|
||||
};
|
||||
var compiler = 'dist' === options.env ? webpack$2(__assign(__assign({}, config$1), config)) : webpack$2(__assign(__assign({}, config$2), config));
|
||||
var analyzer = new BundleAnalyzerPlugin(analyzerConfig);
|
||||
analyzer.apply(compiler);
|
||||
compiler.run(function (err, stats) {
|
||||
if (err)
|
||||
throw err;
|
||||
var messages = webpackFormatMessages$1(stats);
|
||||
if (!messages.errors.length && !messages.warnings.length) {
|
||||
console.log('Compiled successfully!', '\n');
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
console.log('Failed to compile.', '\n');
|
||||
for (var _i = 0, _a = messages.errors; _i < _a.length; _i++) {
|
||||
var m = _a[_i];
|
||||
console.log(m);
|
||||
}
|
||||
}
|
||||
else if (messages.warnings.length) {
|
||||
console.log('Compiled with warnings.', '\n');
|
||||
for (var _b = 0, _c = messages.warnings; _b < _c.length; _b++) {
|
||||
var m = _c[_b];
|
||||
console.log(m);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var isAbsolutePath$1 = require('path').isAbsolute;
|
||||
var webpack$1 = require('webpack');
|
||||
var webpackFormatMessages = require('webpack-format-messages');
|
||||
var defaultOptions$1 = {
|
||||
env: 'production',
|
||||
config: config$3,
|
||||
};
|
||||
function build(buildOptions) {
|
||||
if (buildOptions === void 0) { buildOptions = defaultOptions$1; }
|
||||
var options = __assign(__assign({}, defaultOptions$1), buildOptions);
|
||||
options.config = __assign(__assign({}, defaultOptions$1.config), buildOptions.config);
|
||||
if (!isAbsolutePath$1(options.config.src)) {
|
||||
throw Error('"src" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath$1(options.config.build)) {
|
||||
throw Error('"build" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath$1(options.config.postcssConfigFile)) {
|
||||
throw Error('"postcssConfigFile" is not an absolute path');
|
||||
}
|
||||
var config = generateConfig(options.env, options.config);
|
||||
var compiler = 'dist' === options.env ? webpack$1(__assign(__assign({}, config$1), config)) : webpack$1(__assign(__assign({}, config$2), config));
|
||||
compiler.run(function (err, stats) {
|
||||
if (err)
|
||||
throw err;
|
||||
var messages = webpackFormatMessages(stats);
|
||||
if (!messages.errors.length && !messages.warnings.length) {
|
||||
console.log('Compiled successfully!', '\n');
|
||||
}
|
||||
if (messages.errors.length) {
|
||||
console.log('Failed to compile.', '\n');
|
||||
for (var _i = 0, _a = messages.errors; _i < _a.length; _i++) {
|
||||
var m = _a[_i];
|
||||
console.log(m);
|
||||
}
|
||||
}
|
||||
else if (messages.warnings.length) {
|
||||
console.log('Compiled with warnings.', '\n');
|
||||
for (var _b = 0, _c = messages.warnings; _b < _c.length; _b++) {
|
||||
var m = _c[_b];
|
||||
console.log(m);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var config = {
|
||||
mode: 'development',
|
||||
// devtool: 'eval',
|
||||
// devtool: 'source-map',
|
||||
// devtool: 'eval-cheap-source-map',
|
||||
optimization: {
|
||||
minimize: false
|
||||
}
|
||||
};
|
||||
|
||||
function configFunc(contentBase) {
|
||||
return {
|
||||
watchOptions: {
|
||||
poll: true,
|
||||
},
|
||||
contentBase: contentBase,
|
||||
compress: true,
|
||||
hot: true
|
||||
};
|
||||
}
|
||||
|
||||
var isAbsolutePath = require('path').isAbsolute;
|
||||
var webpack = require('webpack');
|
||||
var WebpackDevServer = require('webpack-dev-server');
|
||||
var defaultOptions = {
|
||||
env: 'development',
|
||||
host: '0.0.0.0',
|
||||
port: 8080,
|
||||
config: config$3,
|
||||
};
|
||||
function dev(devOptions) {
|
||||
if (devOptions === void 0) { devOptions = defaultOptions; }
|
||||
var options = __assign(__assign({}, defaultOptions), devOptions);
|
||||
options.config = __assign(__assign({}, defaultOptions.config), devOptions.config);
|
||||
var config$1 = generateConfig(options.env, options.config);
|
||||
if (!isAbsolutePath(options.config.src)) {
|
||||
throw Error('"src" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath(options.config.build)) {
|
||||
throw Error('"build" is not an absolute path');
|
||||
}
|
||||
if (!isAbsolutePath(options.config.postcssConfigFile)) {
|
||||
throw Error('"postcssConfigFile" is not an absolute path');
|
||||
}
|
||||
var compilerConfig = __assign(__assign({}, config), config$1);
|
||||
var serverOptions = configFunc(options.config.src);
|
||||
WebpackDevServer.addDevServerEntrypoints(compilerConfig, serverOptions);
|
||||
var compiler = webpack(compilerConfig);
|
||||
var server = new WebpackDevServer(compiler, serverOptions);
|
||||
server.listen(options.port, options.host, function (err) {
|
||||
if (err)
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
exports.analyzer = analyzer;
|
||||
exports.build = build;
|
||||
exports.dev = dev;
|
||||
99
frontend/packages/scripts/lib/.webpack/build.config.ts
Executable file
99
frontend/packages/scripts/lib/.webpack/build.config.ts
Executable file
@@ -0,0 +1,99 @@
|
||||
import { Configuration } from 'webpack';
|
||||
|
||||
/*const chunksCacheGroups_0 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
|
||||
/*const chunksCacheGroups_1 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
// priority: -10,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
|
||||
name: "_vendors",
|
||||
// priority: -20,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
// reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
|
||||
/*const chunksCacheGroups_2 = {
|
||||
commons: {
|
||||
minChunks: 2,
|
||||
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
|
||||
// minSize: 0,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};*/
|
||||
|
||||
/*const chunksCacheGroups_3 = {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
};*/
|
||||
|
||||
export const config: Configuration = {
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
optimization: {
|
||||
runtimeChunk: false,
|
||||
/*splitChunks: {
|
||||
// minSize: 1000000,
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
// minSize: 1000000,
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_0,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_1,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_2,
|
||||
},*/
|
||||
/*splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: chunksCacheGroups_3,
|
||||
},*/
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: '_commons',
|
||||
priority: 1,
|
||||
chunks: 'initial',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
12
frontend/packages/scripts/lib/.webpack/dev-server.config.ts
Executable file
12
frontend/packages/scripts/lib/.webpack/dev-server.config.ts
Executable file
@@ -0,0 +1,12 @@
|
||||
import { Configuration } from 'webpack-dev-server';
|
||||
|
||||
export function configFunc(contentBase: string): Configuration {
|
||||
return {
|
||||
watchOptions: {
|
||||
poll: true,
|
||||
},
|
||||
contentBase: contentBase,
|
||||
compress: true,
|
||||
hot: true,
|
||||
};
|
||||
}
|
||||
11
frontend/packages/scripts/lib/.webpack/dev.config.ts
Executable file
11
frontend/packages/scripts/lib/.webpack/dev.config.ts
Executable file
@@ -0,0 +1,11 @@
|
||||
import { Configuration } from 'webpack';
|
||||
|
||||
export const config: Configuration = {
|
||||
mode: 'development',
|
||||
// devtool: 'eval',
|
||||
// devtool: 'source-map',
|
||||
// devtool: 'eval-cheap-source-map',
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
};
|
||||
73
frontend/packages/scripts/lib/.webpack/dist.config.ts
Executable file
73
frontend/packages/scripts/lib/.webpack/dist.config.ts
Executable file
@@ -0,0 +1,73 @@
|
||||
import { Configuration } from 'webpack';
|
||||
|
||||
/*const chunksCacheGroups_0 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_1 = {
|
||||
commons: {
|
||||
test: /[\\/]src[\\/]/,
|
||||
name: "_commons",
|
||||
// priority: -10,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
||||
// test: /[\\/]node_modules[\\/](!MediaCmsPlayer)[\\/]/,
|
||||
name: "_vendors",
|
||||
// priority: -20,
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
// reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_2 = {
|
||||
commons: {
|
||||
minChunks: 2,
|
||||
// maxInitialRequests: 8, // @note: Tested values from 0 to 10, and changes applied with values 0, 4, 5, 6, 7, 8.
|
||||
// minSize: 0,
|
||||
name: "_commons",
|
||||
chunks: "all",
|
||||
enforce: true,
|
||||
reuseExistingChunk: true,
|
||||
},
|
||||
};
|
||||
|
||||
const chunksCacheGroups_3 = {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: "_commons",
|
||||
priority: 1,
|
||||
chunks: "initial",
|
||||
},
|
||||
};*/
|
||||
|
||||
export const config: Configuration = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
runtimeChunk: false,
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
automaticNameDelimiter: '-',
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/]/,
|
||||
name: '_commons',
|
||||
priority: 1,
|
||||
chunks: 'initial',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
114
frontend/packages/scripts/lib/config.ts
Executable file
114
frontend/packages/scripts/lib/config.ts
Executable file
@@ -0,0 +1,114 @@
|
||||
function bodySnippet(id:string) {
|
||||
return '<div id="' + id + '"></div>';
|
||||
}
|
||||
|
||||
interface ConfigHtmlHead{
|
||||
meta?: { [key: string]: string }[],
|
||||
links?: { [key: string]: string }[],
|
||||
scripts?: { [key: string]: string }[],
|
||||
}
|
||||
|
||||
interface ConfigHtmlBody{
|
||||
scripts: { [key: string]: string }[],
|
||||
snippet: string,
|
||||
}
|
||||
|
||||
export interface ConfigHtml{
|
||||
head: ConfigHtmlHead,
|
||||
body: ConfigHtmlBody,
|
||||
}
|
||||
|
||||
export interface ConfigPages{
|
||||
[key: string]: ConfigPage
|
||||
}
|
||||
|
||||
export interface ConfigWindow{
|
||||
[key: string ]: unknown
|
||||
}
|
||||
|
||||
export interface ConfigType {
|
||||
src: string,
|
||||
build: string,
|
||||
html: ConfigHtml,
|
||||
pages: ConfigPages,
|
||||
window: ConfigWindow,
|
||||
postcssConfigFile: string,
|
||||
}
|
||||
|
||||
export interface ConfigPage{
|
||||
staticPage: boolean,
|
||||
buildExclude: boolean,
|
||||
title: string,
|
||||
filename: string,
|
||||
html: ConfigHtml,
|
||||
window: ConfigWindow,
|
||||
render: string,
|
||||
}
|
||||
|
||||
const homePage: ConfigPage = {
|
||||
staticPage: true,
|
||||
buildExclude: false,
|
||||
title: 'Home',
|
||||
filename: 'index.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: bodySnippet('page-home'),
|
||||
}
|
||||
},
|
||||
window: {},
|
||||
render: 'import { renderPage } from \'./js/helpers\'; import { HomePage } from \'./js/pages/HomePage\'; renderPage( \'page-home\', HomePage );',
|
||||
};
|
||||
|
||||
const errorPage: ConfigPage = {
|
||||
staticPage: true,
|
||||
buildExclude: false,
|
||||
title: 'Error',
|
||||
filename: 'error.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: bodySnippet('page-error'),
|
||||
}
|
||||
},
|
||||
window: {},
|
||||
render: 'import { renderPage } from \'./js/helpers\'; import { ErrorPage } from \'./js/pages/ErrorPage\'; renderPage( \'page-error\', ErrorPage );',
|
||||
};
|
||||
|
||||
const pages: { [key: string]: ConfigPage } = {
|
||||
home: homePage,
|
||||
error: errorPage,
|
||||
};
|
||||
|
||||
const htmlHead: ConfigHtmlHead = {
|
||||
meta: [
|
||||
{ charset: 'utf-8' },
|
||||
{ content: 'ie=edge', 'http-equiv': 'x-ua-compatible' },
|
||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
||||
],
|
||||
links: [],
|
||||
scripts: [],
|
||||
};
|
||||
|
||||
const htmlBody: ConfigHtmlBody = {
|
||||
scripts: [],
|
||||
snippet: '',
|
||||
};
|
||||
|
||||
const html: ConfigHtml = {
|
||||
head: htmlHead,
|
||||
body: htmlBody,
|
||||
};
|
||||
|
||||
export const config : ConfigType = {
|
||||
src: '',
|
||||
build: '',
|
||||
pages,
|
||||
html,
|
||||
window: {},
|
||||
postcssConfigFile: '',
|
||||
};
|
||||
|
||||
export default config;
|
||||
21
frontend/packages/scripts/lib/interfaces/OptionsTypes.ts
Executable file
21
frontend/packages/scripts/lib/interfaces/OptionsTypes.ts
Executable file
@@ -0,0 +1,21 @@
|
||||
import { ConfigType } from '../config';
|
||||
|
||||
export interface DevOptionsType {
|
||||
env: string,
|
||||
host: string,
|
||||
port: number,
|
||||
config: ConfigType,
|
||||
}
|
||||
|
||||
export interface BuildOptionsType {
|
||||
env: string,
|
||||
config: ConfigType,
|
||||
}
|
||||
|
||||
export interface AnalyzerOptionsType {
|
||||
env: string,
|
||||
host: string,
|
||||
port: number,
|
||||
mode: string,
|
||||
config: ConfigType,
|
||||
}
|
||||
69
frontend/packages/scripts/lib/webpack-helpers/formatPagesConfig.ts
Executable file
69
frontend/packages/scripts/lib/webpack-helpers/formatPagesConfig.ts
Executable file
@@ -0,0 +1,69 @@
|
||||
const merge = require('lodash.merge');
|
||||
|
||||
import { ConfigHtml, ConfigPages, ConfigWindow } from '../config';
|
||||
|
||||
function validateBoolean(value?: boolean | 0 | 1, defaultValue = false): boolean {
|
||||
|
||||
if (true === value || false === value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (0 === value || 1 === value) {
|
||||
return !!value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function validateString(value?: string, defaultValue = ''): string {
|
||||
return value ? value : defaultValue;
|
||||
}
|
||||
|
||||
function getArrayType(sourcesArr?: Array<{ [key: string]: string }>, pageArr: Array<{ [key: string]: string }> = []): Array<{ [key: string]: string }> {
|
||||
|
||||
if ((!sourcesArr || !sourcesArr.length) && (!pageArr || !pageArr.length)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (sourcesArr && sourcesArr.length && pageArr && pageArr.length) {
|
||||
return sourcesArr.concat(pageArr);
|
||||
}
|
||||
|
||||
if (sourcesArr && sourcesArr.length) {
|
||||
return sourcesArr;
|
||||
}
|
||||
|
||||
return pageArr;
|
||||
}
|
||||
|
||||
function formatPagesConfig(sources: { title: string, filename: string, render: string, html: ConfigHtml, window: ConfigWindow }, pages: ConfigPages): ConfigPages {
|
||||
|
||||
const ret: ConfigPages = {};
|
||||
|
||||
for (const pk in pages) {
|
||||
|
||||
ret[pk] = {
|
||||
staticPage: validateBoolean(pages[pk].staticPage, false),
|
||||
buildExclude: validateBoolean(pages[pk].buildExclude, false),
|
||||
title: validateString(pages[pk].title, sources.title),
|
||||
filename: validateString(pages[pk].filename, sources.filename),
|
||||
html: {
|
||||
head: {
|
||||
meta: getArrayType(sources.html.head.meta, pages[pk].html.head.meta),
|
||||
links: getArrayType(sources.html.head.links, pages[pk].html.head.links),
|
||||
scripts: getArrayType(sources.html.head.scripts, pages[pk].html.head.scripts),
|
||||
},
|
||||
body: {
|
||||
scripts: getArrayType(sources.html.body.scripts, pages[pk].html.body.scripts),
|
||||
snippet: validateString(pages[pk].html.body.snippet, sources.html.body.snippet),
|
||||
},
|
||||
},
|
||||
window: merge({}, sources.window, pages[pk].window),
|
||||
render: validateString(sources.render, pages[pk].render),
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export default formatPagesConfig;
|
||||
308
frontend/packages/scripts/lib/webpack-helpers/generateConfig.ts
Executable file
308
frontend/packages/scripts/lib/webpack-helpers/generateConfig.ts
Executable file
@@ -0,0 +1,308 @@
|
||||
const path = require('path');
|
||||
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
|
||||
|
||||
// Webpack plugins.
|
||||
const { DefinePlugin } = require('webpack');
|
||||
const { LimitChunkCountPlugin } = require('webpack').optimize;
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const VirtualModulesPlugin = require('webpack-virtual-modules');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const ProgressBarPlugin = require('progress-bar-webpack-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const CopyPlugin = require("copy-webpack-plugin");
|
||||
|
||||
var dotenv = require('dotenv').config({ path: path.resolve(__dirname + '../../../../.env') });
|
||||
|
||||
import MyHtmlBeautifyWebpackPlugin from '../webpack-plugins/MyHtmlBeautifyWebpackPlugin';
|
||||
|
||||
import { ConfigType, ConfigPages } from '../config';
|
||||
|
||||
import defaultPages from './pagesConfig';
|
||||
|
||||
import formatPagesConfig from './formatPagesConfig';
|
||||
|
||||
function webpackEntry(env: string, srcDir: string, pages: ConfigPages) {
|
||||
|
||||
const ret: { [key: string]: string } = {};
|
||||
|
||||
for (const p in pages) {
|
||||
|
||||
if ('development' === env || !pages[p].buildExclude) {
|
||||
ret[p] = path.resolve(srcDir + '/' + p + '.js');
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function webpackOutput(env: string, destinationDir: string, buildDir?: string, chunkhash?: string, hash?: string) {
|
||||
|
||||
const ret = {
|
||||
path: destinationDir,
|
||||
filename: '',
|
||||
};
|
||||
|
||||
const prefix = 'development' === env ? '' : buildDir;
|
||||
|
||||
let tmp;
|
||||
|
||||
if (undefined !== chunkhash) {
|
||||
|
||||
tmp = chunkhash.trim();
|
||||
|
||||
if ('' === tmp) {
|
||||
throw Error('Invalid chunkhash argument value: ' + chunkhash);
|
||||
}
|
||||
|
||||
ret.filename = (prefix || '') + '[name]-[chunkhash].js';
|
||||
}
|
||||
else if (undefined !== hash) {
|
||||
|
||||
tmp = hash.trim();
|
||||
|
||||
if ('' === tmp) {
|
||||
throw Error('Invalid hash argument value: ' + hash);
|
||||
}
|
||||
|
||||
ret.filename = (prefix || '') + '[name]-[hash].js';
|
||||
}
|
||||
else {
|
||||
ret.filename = (prefix || '') + '[name].js';
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
function webpackAlias() {
|
||||
|
||||
return {
|
||||
// modernizr$: path.resolve(__dirname, "../../.modernizrrc"), // TODO: Enable this?
|
||||
};
|
||||
}
|
||||
|
||||
function webpackRules(env: string, srcDir: string, postcssConfigFile: string): any[] {
|
||||
|
||||
return [{
|
||||
test: /\.(jsx|js)?$/,
|
||||
use: 'babel-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(tsx|ts)?$/,
|
||||
use: 'ts-loader',
|
||||
// exclude: /node_modules/,
|
||||
// options: {
|
||||
// compilerOptions: {
|
||||
// "sourceMap": !isProduction,
|
||||
// },
|
||||
// },
|
||||
},
|
||||
{
|
||||
test: /\.ejs$/,
|
||||
use: {
|
||||
loader: 'ejs-compiled-loader',
|
||||
options: {
|
||||
// beautify: true,
|
||||
htmlmin: true,
|
||||
// htmlminOptions: {
|
||||
// removeComments: true,
|
||||
// collapseWhitespace: true,
|
||||
// preserveLineBreaks: true
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.(sa|sc|c)ss$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
|
||||
{ loader: 'css-loader', options: { importLoaders: 1 } },
|
||||
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
|
||||
{ loader: 'sass-loader' },
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.module\.(sa|sc|c)ss$/,
|
||||
use: [
|
||||
{ loader: MiniCssExtractPlugin.loader },
|
||||
// { loader: 'development' === env ? MiniCssExtractPlugin.loader : 'style-loader' }, // Use inline <style> tag.
|
||||
{ loader: 'css-loader', options: { importLoaders: 1, modules: true, onlyLocals: false } },
|
||||
{ loader: 'postcss-loader', options: { postcssOptions: { config: postcssConfigFile } } },
|
||||
{ loader: 'sass-loader' },
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpe?g|gif)(\?\S*)?$/,
|
||||
use: {
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 1024,
|
||||
fallback: 'file-loader',
|
||||
name: (file: string) => {
|
||||
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
/*issuer: {
|
||||
test: /\.jsx?$/
|
||||
},*/
|
||||
use: ['babel-loader', '@svgr/webpack', 'url-loader']
|
||||
},
|
||||
{
|
||||
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
|
||||
loader: 'url-loader'
|
||||
},
|
||||
{
|
||||
test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: (file: string) => {
|
||||
return '.' + path.join(file.replace(srcDir, ''), '..').replace(/\\/g, '/') + '/[name].[ext]';
|
||||
},
|
||||
}
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.modernizrrc.js$/,
|
||||
use: 'modernizr-loader',
|
||||
},
|
||||
{
|
||||
test: /\.modernizrrc(\.json)?$/,
|
||||
use: ['modernizr-loader', 'json-loader'],
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function webpackPlugins(env: string, srcDir: string, pages: ConfigPages, cssSrc: string) {
|
||||
|
||||
const ret = [
|
||||
new DefinePlugin({ "process.env": JSON.stringify(dotenv.parsed) }),
|
||||
new NodePolyfillPlugin(),
|
||||
new MyHtmlBeautifyWebpackPlugin(),
|
||||
];
|
||||
|
||||
if ('development' !== env) {
|
||||
ret.push(
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/lib'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/lib'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/images'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/images'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/favicons'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/favicons'),
|
||||
},
|
||||
{
|
||||
from: path.resolve(__dirname, '../../../src/static/css/_extra.css'),
|
||||
to: path.resolve(__dirname, '../../../' + env + '/static/css/_extra.css'),
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const virtualPages: { [key: string]: string } = {};
|
||||
|
||||
let file: string;
|
||||
|
||||
for (const k in pages) {
|
||||
|
||||
if ('production' !== env || !pages[k].buildExclude) {
|
||||
|
||||
file = path.resolve(srcDir + '/' + k + '.js');
|
||||
|
||||
if ((void 0 !== pages[k].staticPage && pages[k].staticPage) || void 0 === pages[k].render) {
|
||||
virtualPages[file] = '';
|
||||
} else {
|
||||
virtualPages[file] = pages[k].render;
|
||||
}
|
||||
}
|
||||
|
||||
if ('development' === env) {
|
||||
// Export pages HTML files.
|
||||
ret.push(new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, '../templates/index.ejs'),
|
||||
hash: false,
|
||||
chunks: [k],
|
||||
...pages[k],
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
ret.push(new VirtualModulesPlugin(virtualPages));
|
||||
|
||||
ret.push(new MiniCssExtractPlugin({
|
||||
ignoreOrder: true, // TODO: Remove it...
|
||||
// filename: ! is_build ? '[name].css' : '[name].[hash].css',
|
||||
// chunkFilename: ! is_build ? '[id].css' : '[id].[hash].css',
|
||||
filename: cssSrc + '[name].css',
|
||||
// chunkFilename: "../css/[id].css",
|
||||
}));
|
||||
|
||||
if ('development' !== env) {
|
||||
ret.push(new LimitChunkCountPlugin({ maxChunks: 1 }));
|
||||
ret.push(
|
||||
new ProgressBarPlugin({
|
||||
clear: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if ('production' === env) {
|
||||
ret.push(new CssMinimizerPlugin({
|
||||
cache: true, // TODO: Ignore in Webpack 5. Use https://webpack.js.org/configuration/other-options/#cache.
|
||||
minimizerOptions: {
|
||||
preset: [
|
||||
'default',
|
||||
{
|
||||
discardComments: { removeAll: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
export default function generateConfig(env: string, config: ConfigType) {
|
||||
|
||||
const srcDir = config.src;
|
||||
const buildDir = config.build + '/' + env + ('development' === env ? '' : '/static');
|
||||
|
||||
const cssbuild = './css/';
|
||||
const jsbuild = './js/';
|
||||
|
||||
const configPages = config.pages;
|
||||
const configPagesKeys = config.pages ? Object.keys(configPages) : [];
|
||||
const defPages = defaultPages(configPagesKeys);
|
||||
|
||||
const pages = formatPagesConfig(
|
||||
{ title: '', filename: '', render: '', html: config.html, window: config.window },
|
||||
{ ...configPages, ...defPages }
|
||||
);
|
||||
|
||||
const ret = {
|
||||
entry: webpackEntry(env, srcDir, pages),
|
||||
output: 'development' === env ? webpackOutput(env, srcDir, void 0, void 0, void 0) : webpackOutput(env, buildDir, jsbuild, void 0, void 0),
|
||||
plugins: webpackPlugins(env, srcDir, pages, cssbuild),
|
||||
module: {
|
||||
rules: webpackRules(env, srcDir, config.postcssConfigFile),
|
||||
},
|
||||
resolve: {
|
||||
alias: webpackAlias(),
|
||||
extensions: ['.tsx', '.ts', '.jsx', '.js'],
|
||||
},
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
37
frontend/packages/scripts/lib/webpack-helpers/pagesConfig.ts
Executable file
37
frontend/packages/scripts/lib/webpack-helpers/pagesConfig.ts
Executable file
@@ -0,0 +1,37 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const ejs = require('ejs');
|
||||
|
||||
const templatePath = path.join(__dirname, '../templates');
|
||||
const sitemapTemplatePath = path.join(templatePath, 'sitemap.ejs');
|
||||
|
||||
const sitemapTemplate = ejs.compile(fs.readFileSync(sitemapTemplatePath, 'utf8'), { root: [templatePath], filename: sitemapTemplatePath, outputFunctionName: 'echo' });
|
||||
|
||||
import { ConfigPages } from '../config';
|
||||
|
||||
export default function pagesConfig(pagesKeys: string[]): ConfigPages {
|
||||
|
||||
const pages: ConfigPages = {};
|
||||
|
||||
if (-1 === pagesKeys.indexOf('sitemap')) {
|
||||
|
||||
pages.sitemap = {
|
||||
staticPage: true,
|
||||
buildExclude: true,
|
||||
title: 'Sitemap',
|
||||
filename: 'sitemap.html',
|
||||
html: {
|
||||
head: {},
|
||||
body: {
|
||||
scripts: [],
|
||||
snippet: sitemapTemplate({ pages: [...pagesKeys, ...Object.keys(pages)] }),
|
||||
},
|
||||
},
|
||||
window: {},
|
||||
render: ''
|
||||
};
|
||||
}
|
||||
|
||||
return pages;
|
||||
}
|
||||
74
frontend/packages/scripts/lib/webpack-plugins/MyHtmlBeautifyWebpackPlugin.ts
Executable file
74
frontend/packages/scripts/lib/webpack-plugins/MyHtmlBeautifyWebpackPlugin.ts
Executable file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* @see {link: https://github.com/seeyoulater/html-beautify-webpack-plugin/blob/master/index.js}
|
||||
*/
|
||||
|
||||
const prettify = require('html-prettify');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
const WebpackError = require( 'webpack/lib/WebpackError.js' );
|
||||
|
||||
import { Compiler, Compilation } from 'webpack';
|
||||
|
||||
interface OptionsConfigHtml {
|
||||
end_with_newline: boolean,
|
||||
indent_inner_html: boolean,
|
||||
preserve_newlines: boolean,
|
||||
max_preserve_newlines: number,
|
||||
}
|
||||
|
||||
interface OptionsConfig {
|
||||
indent_size: number,
|
||||
indent_with_tabs: boolean,
|
||||
html: OptionsConfigHtml
|
||||
}
|
||||
|
||||
interface Options{
|
||||
config: OptionsConfig,
|
||||
replace: Array<string|RegExp>
|
||||
}
|
||||
|
||||
interface HtmlWebpackPluginArgs{
|
||||
html: string,
|
||||
plugin: typeof HtmlWebpackPlugin,
|
||||
outputName: string
|
||||
}
|
||||
|
||||
function htmlPluginDataFunction (pluginData: HtmlWebpackPluginArgs, options: Options, callback: (err:typeof WebpackError, arg1: HtmlWebpackPluginArgs) => void) {
|
||||
|
||||
pluginData.html = prettify(
|
||||
options.replace.reduce( (res:string, item: string | RegExp) => res.replace( item instanceof RegExp ? new RegExp(item, 'gi') : item, '' ), pluginData.html )/*,
|
||||
options.config*/
|
||||
);
|
||||
|
||||
callback(null, pluginData);
|
||||
}
|
||||
|
||||
export default class MyHtmlBeautifyWebpackPlugin {
|
||||
|
||||
apply(compiler: Compiler): void {
|
||||
|
||||
const options: Options = {
|
||||
config: { // TODO: Remove it.
|
||||
indent_size: 4,
|
||||
indent_with_tabs: false,
|
||||
html: {
|
||||
end_with_newline: true,
|
||||
indent_inner_html: true,
|
||||
preserve_newlines: true,
|
||||
max_preserve_newlines: 0,
|
||||
}
|
||||
},
|
||||
replace: []
|
||||
};
|
||||
|
||||
function tapAsyncCallback(pluginData: HtmlWebpackPluginArgs, callback: (err:typeof WebpackError, arg1: HtmlWebpackPluginArgs) => void ){
|
||||
return htmlPluginDataFunction (pluginData, options, callback);
|
||||
}
|
||||
|
||||
function tapHookCallback(compilation: Compilation){
|
||||
return HtmlWebpackPlugin.getHooks(compilation).beforeEmit.tapAsync( 'MyHtmlBeautifyWebpackPlugin', tapAsyncCallback );
|
||||
}
|
||||
|
||||
compiler.hooks.compilation.tap( 'MyHtmlBeautifyWebpackPlugin', tapHookCallback );
|
||||
}
|
||||
}
|
||||
10148
frontend/packages/scripts/package-lock.json
generated
Normal file
10148
frontend/packages/scripts/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
86
frontend/packages/scripts/package.json
Normal file
86
frontend/packages/scripts/package.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"name": "mediacms-scripts",
|
||||
"version": "0.9.0",
|
||||
"description": "",
|
||||
"author": "",
|
||||
"license": "",
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
},
|
||||
"files": [
|
||||
"scripts",
|
||||
"cli.js"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "npx rollup -w -c ./config/watch.config.js",
|
||||
"build": "npx rollup -c ./config/build.config.js",
|
||||
"visual": "npx rollup -c ./config/visual.config.js"
|
||||
},
|
||||
"bin": {
|
||||
"mediacms-scripts": "./cli.js"
|
||||
},
|
||||
"types": "",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.14.5",
|
||||
"@babel/preset-env": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.14.5",
|
||||
"@rollup/plugin-node-resolve": "^13.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.1",
|
||||
"@types/react": "^17.0.11",
|
||||
"@types/react-dom": "^17.0.7",
|
||||
"axios": "^0.21.1",
|
||||
"babel-core": "^6.26.3",
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"cross-spawn": "^7.0.3",
|
||||
"dotenv": "^10.0.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.51.2",
|
||||
"rollup-plugin-cleanup": "^3.2.1",
|
||||
"rollup-plugin-typescript2": "^0.30.0",
|
||||
"rollup-plugin-visualizer": "^5.5.0",
|
||||
"serialize-javascript": "^5.0.1",
|
||||
"source-map-loader": "^3.0.0",
|
||||
"ts-loader": "^9.2.3",
|
||||
"typescript": "^4.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@types/webpack-bundle-analyzer": "^4.4.0",
|
||||
"@types/webpack-dev-server": "^3.11.4",
|
||||
"autoprefixer": "^10.2.6",
|
||||
"babel-loader": "^8.2.2",
|
||||
"css-loader": "^5.2.6",
|
||||
"css-minimizer-webpack-plugin": "^3.0.1",
|
||||
"ejs": "^3.1.6",
|
||||
"ejs-compiled-loader": "^3.1.0",
|
||||
"ejs-loader": "^0.5.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-prettify": "^1.0.3",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"json-loader": "^0.5.7",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"mini-css-extract-plugin": "^1.6.0",
|
||||
"node-polyfill-webpack-plugin": "^1.1.2",
|
||||
"node-sass": "^6.0.0",
|
||||
"postcss": "^8.3.2",
|
||||
"postcss-import": "^14.0.2",
|
||||
"postcss-loader": "^6.1.0",
|
||||
"postcss-modules": "^4.1.3",
|
||||
"postcss-nested": "^5.0.5",
|
||||
"postcss-scss": "^3.0.5",
|
||||
"progress-bar-webpack-plugin": "^2.1.0",
|
||||
"sass-loader": "^12.1.0",
|
||||
"style-loader": "^2.0.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-format-messages": "^2.0.6",
|
||||
"webpack-virtual-modules": "^0.4.3"
|
||||
},
|
||||
"peerDependenciesMeta": {},
|
||||
"browserslist": {}
|
||||
}
|
||||
5
frontend/packages/scripts/scripts/analyzer.js
Normal file
5
frontend/packages/scripts/scripts/analyzer.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { analyzer } = require('../dist/webpack-dev-env.js');
|
||||
const parseCliArgs = require('./utils/parseCliArgs.js');
|
||||
const { validateAnalyzerOptions } = require('./utils/validateOptions.js');
|
||||
const options = validateAnalyzerOptions(parseCliArgs(process.argv.slice(2)));
|
||||
analyzer(options);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user