Compare commits
165 Commits
main
...
feat-lti-i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c0805748d | ||
|
|
8e7f1ef15d | ||
|
|
d00b4ed3c5 | ||
|
|
7126cf1cbd | ||
|
|
33135f81e9 | ||
|
|
cfd305d563 | ||
|
|
d4bad33592 | ||
|
|
7c0ea60d0e | ||
|
|
c6726d1b13 | ||
|
|
7c04cc30fb | ||
|
|
f28ce5990b | ||
|
|
27828d798e | ||
|
|
ba53467033 | ||
|
|
a77761ec35 | ||
|
|
4d07ae64c6 | ||
|
|
1a96ac0703 | ||
|
|
9e0290ae49 | ||
|
|
610a22935f | ||
|
|
81e3325687 | ||
|
|
16f7b010ab | ||
|
|
68cb21ff8a | ||
|
|
2ddc3b24d7 | ||
|
|
63e96b327e | ||
|
|
48537515cb | ||
|
|
e6db138d11 | ||
|
|
2f2d32f0db | ||
|
|
f4d3439246 | ||
|
|
7fe9891942 | ||
|
|
9eb8a1ad62 | ||
|
|
23ee0dc7cc | ||
|
|
e5be39f392 | ||
|
|
f0c084fa53 | ||
|
|
571bfcc4ce | ||
|
|
c04380af47 | ||
|
|
97741f780e | ||
|
|
78cce0eb10 | ||
|
|
472b3029c4 | ||
|
|
343f1e7009 | ||
|
|
8c78b67b0c | ||
|
|
29fc7fb861 | ||
|
|
b03a33d93e | ||
|
|
64472be406 | ||
|
|
cc0f4d4645 | ||
|
|
095e4d2cb4 | ||
|
|
5c8978453e | ||
|
|
83189076e4 | ||
|
|
ca6dbf3740 | ||
|
|
8646bd70dc | ||
|
|
1f493c8e15 | ||
|
|
e11cb7ea6e | ||
|
|
3131e76ef7 | ||
|
|
809cdccc42 | ||
|
|
ed36240f45 | ||
|
|
77bafff6f6 | ||
|
|
f6252f4f77 | ||
|
|
764580287f | ||
|
|
ce6c9a0a3c | ||
|
|
1ced023a07 | ||
|
|
981fec296c | ||
|
|
40cd7916e7 | ||
|
|
bcef59c3a9 | ||
|
|
e93c8225c4 | ||
|
|
5c3c33ca84 | ||
|
|
7a954e7a3d | ||
|
|
8610df0c2b | ||
|
|
8ab9030d14 | ||
|
|
15c8dec041 | ||
|
|
9af4686bd4 | ||
|
|
bcc8a0858c | ||
|
|
549b672d48 | ||
|
|
abe950f1da | ||
|
|
5fecda02d6 | ||
|
|
3c6f8c102c | ||
|
|
2d28520cd4 | ||
|
|
4bd56da2d8 | ||
|
|
fdfa857794 | ||
|
|
2c1f27c0be | ||
|
|
2f0bbd2533 | ||
|
|
54336f6c31 | ||
|
|
37e21f7ebf | ||
|
|
3deee80dd0 | ||
|
|
2e57164831 | ||
|
|
de0c16729b | ||
|
|
2c0bba1427 | ||
|
|
54a8e41f6d | ||
|
|
78fb19b464 | ||
|
|
8e5e7991b7 | ||
|
|
5cf435eca0 | ||
|
|
5026ce73da | ||
|
|
8b2ebe2415 | ||
|
|
8df320e134 | ||
|
|
8c8f737460 | ||
|
|
995faedb08 | ||
|
|
bde300b4bd | ||
|
|
fd5c0a2908 | ||
|
|
9c145da2e2 | ||
|
|
e9e5d44c3e | ||
|
|
a624c2e5b8 | ||
|
|
748d3b39ba | ||
|
|
ddc6bf9e67 | ||
|
|
aa7dbfe534 | ||
|
|
5cc72357c6 | ||
|
|
01b061a47b | ||
|
|
fbc78e7944 | ||
|
|
9e7a8afdda | ||
|
|
5572a67019 | ||
|
|
610590972f | ||
|
|
bdf7d3c2d0 | ||
|
|
a47bf5a3f8 | ||
|
|
38caea3c7c | ||
|
|
30491bf420 | ||
|
|
d0ebe19c2a | ||
|
|
59be9f16c0 | ||
|
|
a2d898c54e | ||
|
|
9733d53c0b | ||
|
|
70e2c67f3d | ||
|
|
77721d9c0e | ||
|
|
06bc64b2c4 | ||
|
|
b9899476b9 | ||
|
|
107750406e | ||
|
|
ae4ae5a07e | ||
|
|
f346a5604c | ||
|
|
56026a1a96 | ||
|
|
a88413ce14 | ||
|
|
9dab3ad858 | ||
|
|
dfe7e8fab0 | ||
|
|
1181d16ab9 | ||
|
|
d032ee3baa | ||
|
|
93f66d206b | ||
|
|
0585513439 | ||
|
|
9667e6b0ad | ||
|
|
f56948a4a2 | ||
|
|
8b3e76b554 | ||
|
|
dc417de628 | ||
|
|
35cd56c85c | ||
|
|
f0b2451815 | ||
|
|
7696251394 | ||
|
|
b95725660b | ||
|
|
d6bf98b30e | ||
|
|
3baa8ef7d7 | ||
|
|
45246eac4f | ||
|
|
9685c1b5d4 | ||
|
|
20a1da22bb | ||
|
|
f9a94321ad | ||
|
|
f85299a600 | ||
|
|
29ab2a715b | ||
|
|
43ce685f08 | ||
|
|
8c682a76af | ||
|
|
ec6b6daa81 | ||
|
|
cf90169240 | ||
|
|
fb3f377e27 | ||
|
|
f5f9a7beac | ||
|
|
726a5b74a1 | ||
|
|
40c31f295a | ||
|
|
1d77293afc | ||
|
|
5c702387ca | ||
|
|
0001f370a9 | ||
|
|
af71d4c906 | ||
|
|
eb7503125d | ||
|
|
f897d0ba2b | ||
|
|
545cca154e | ||
|
|
ef4ff9cb1d | ||
|
|
3a40fc6d88 | ||
|
|
f67d2a4d78 | ||
|
|
295578dae2 |
23
HISTORY.md
23
HISTORY.md
@ -1,23 +0,0 @@
|
|||||||
# History
|
|
||||||
|
|
||||||
## 3.0.0
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Updates Python/Django requirements and Dockerfile to use latest 3.11 Python - https://github.com/mediacms-io/mediacms/pull/826/files. This update requires some manual steps, for existing (not new) installations. Check the update section under the [Admin docs](https://github.com/mediacms-io/mediacms/blob/main/docs/admins_docs.md#2-server-installation), either for single server or for Docker Compose installations
|
|
||||||
- Upgrade postgres on Docker Compose - https://github.com/mediacms-io/mediacms/pull/749
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- video player options for HLS - https://github.com/mediacms-io/mediacms/pull/832
|
|
||||||
- AVI videos not correctly recognised as videos - https://github.com/mediacms-io/mediacms/pull/833
|
|
||||||
|
|
||||||
## 2.1.0
|
|
||||||
|
|
||||||
### Fixes
|
|
||||||
- Increase uwsgi buffer-size parameter. This prevents an error by uwsgi with large headers - [#5b60](https://github.com/mediacms-io/mediacms/commit/5b601698a41ad97f08c1830e14b1c18f73ab8315)
|
|
||||||
- Fix issues with comments. These were not reported on the tracker but it is certain that they would not show comments on media files (non videos but also videos). Unfortunately this reverts work done with Timestamps on comments + Mentions on comments, more on PR [#802](https://github.com/mediacms-io/mediacms/pull/802)
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- Allow tags to contains other characters too, not only English alphabet ones [#801](https://github.com/mediacms-io/mediacms/pull/801)
|
|
||||||
- Add simple cookie consent code [#799](https://github.com/mediacms-io/mediacms/pull/799)
|
|
||||||
- Allow password reset & email verify pages on global login required [#790](https://github.com/mediacms-io/mediacms/pull/790)
|
|
||||||
- Add api_url field to search api [#692](https://github.com/mediacms-io/mediacms/pull/692)
|
|
||||||
@ -108,7 +108,7 @@ There are two ways to run MediaCMS, through Docker Compose and through installin
|
|||||||
|
|
||||||
## Technology
|
## 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, Gunicorn, React, Fine Uploader, video.js, FFMPEG, Bento4
|
||||||
|
|
||||||
|
|
||||||
## Who is using it
|
## Who is using it
|
||||||
|
|||||||
@ -24,6 +24,7 @@ INSTALLED_APPS = [
|
|||||||
"actions.apps.ActionsConfig",
|
"actions.apps.ActionsConfig",
|
||||||
"rbac.apps.RbacConfig",
|
"rbac.apps.RbacConfig",
|
||||||
"identity_providers.apps.IdentityProvidersConfig",
|
"identity_providers.apps.IdentityProvidersConfig",
|
||||||
|
"lti.apps.LtiConfig",
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
"mptt",
|
"mptt",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
|
|||||||
@ -300,6 +300,7 @@ INSTALLED_APPS = [
|
|||||||
"actions.apps.ActionsConfig",
|
"actions.apps.ActionsConfig",
|
||||||
"rbac.apps.RbacConfig",
|
"rbac.apps.RbacConfig",
|
||||||
"identity_providers.apps.IdentityProvidersConfig",
|
"identity_providers.apps.IdentityProvidersConfig",
|
||||||
|
"lti.apps.LtiConfig",
|
||||||
"debug_toolbar",
|
"debug_toolbar",
|
||||||
"mptt",
|
"mptt",
|
||||||
"crispy_forms",
|
"crispy_forms",
|
||||||
@ -555,6 +556,7 @@ DJANGO_ADMIN_URL = "admin/"
|
|||||||
USE_SAML = False
|
USE_SAML = False
|
||||||
USE_RBAC = False
|
USE_RBAC = False
|
||||||
USE_IDENTITY_PROVIDERS = False
|
USE_IDENTITY_PROVIDERS = False
|
||||||
|
USE_LTI = False # Enable LTI 1.3 integration
|
||||||
JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
|
JAZZMIN_UI_TWEAKS = {"theme": "flatly"}
|
||||||
|
|
||||||
USE_ROUNDED_CORNERS = True
|
USE_ROUNDED_CORNERS = True
|
||||||
@ -650,3 +652,18 @@ if USERS_NEEDS_TO_BE_APPROVED:
|
|||||||
)
|
)
|
||||||
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
|
auth_index = MIDDLEWARE.index("django.contrib.auth.middleware.AuthenticationMiddleware")
|
||||||
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")
|
MIDDLEWARE.insert(auth_index + 1, "cms.middleware.ApprovalMiddleware")
|
||||||
|
|
||||||
|
|
||||||
|
# LTI 1.3 Integration Settings
|
||||||
|
if USE_LTI:
|
||||||
|
# Session timeout for LTI launches (seconds)
|
||||||
|
LTI_SESSION_TIMEOUT = 3600 # 1 hour
|
||||||
|
|
||||||
|
# Cookie settings required for iframe embedding from LMS
|
||||||
|
# IMPORTANT: Requires HTTPS to be enabled
|
||||||
|
SESSION_COOKIE_SAMESITE = 'None'
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
CSRF_COOKIE_SAMESITE = 'None'
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
# SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
|
# Consider using cached_db for reliability if sessions are lost between many LTI launches
|
||||||
|
|||||||
@ -25,6 +25,7 @@ urlpatterns = [
|
|||||||
re_path(r"^", include("files.urls")),
|
re_path(r"^", include("files.urls")),
|
||||||
re_path(r"^", include("users.urls")),
|
re_path(r"^", include("users.urls")),
|
||||||
re_path(r"^accounts/", include("allauth.urls")),
|
re_path(r"^accounts/", include("allauth.urls")),
|
||||||
|
re_path(r"^lti/", include("lti.urls")),
|
||||||
re_path(r"^api-auth/", include("rest_framework.urls")),
|
re_path(r"^api-auth/", include("rest_framework.urls")),
|
||||||
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
|
path(settings.DJANGO_ADMIN_URL, admin.site.urls),
|
||||||
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
VERSION = "7.7"
|
VERSION = "8.10"
|
||||||
|
|||||||
@ -1,3 +1,9 @@
|
|||||||
|
# Use existing X-Forwarded-Proto from reverse proxy if present, otherwise use $scheme
|
||||||
|
map $http_x_forwarded_proto $forwarded_proto {
|
||||||
|
default $http_x_forwarded_proto;
|
||||||
|
'' $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
listen 80 ;
|
listen 80 ;
|
||||||
|
|
||||||
@ -28,7 +34,10 @@ server {
|
|||||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
||||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
proxy_pass http://127.0.0.1:9000;
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $forwarded_proto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,6 @@ fi
|
|||||||
|
|
||||||
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
|
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-available/default
|
||||||
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
|
cp deploy/docker/nginx_http_only.conf /etc/nginx/sites-enabled/default
|
||||||
cp deploy/docker/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
|
|
||||||
cp deploy/docker/nginx.conf /etc/nginx/
|
cp deploy/docker/nginx.conf /etc/nginx/
|
||||||
|
|
||||||
#### Supervisord Configurations #####
|
#### Supervisord Configurations #####
|
||||||
@ -45,12 +44,12 @@ cp deploy/docker/nginx.conf /etc/nginx/
|
|||||||
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
|
cp deploy/docker/supervisord/supervisord-debian.conf /etc/supervisor/conf.d/supervisord-debian.conf
|
||||||
|
|
||||||
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
|
if [ X"$ENABLE_UWSGI" = X"yes" ] ; then
|
||||||
echo "Enabling uwsgi app server"
|
echo "Enabling gunicorn app server"
|
||||||
cp deploy/docker/supervisord/supervisord-uwsgi.conf /etc/supervisor/conf.d/supervisord-uwsgi.conf
|
cp deploy/docker/supervisord/supervisord-gunicorn.conf /etc/supervisor/conf.d/supervisord-gunicorn.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
|
if [ X"$ENABLE_NGINX" = X"yes" ] ; then
|
||||||
echo "Enabling nginx as uwsgi app proxy and media server"
|
echo "Enabling nginx as gunicorn app proxy and media server"
|
||||||
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
|
cp deploy/docker/supervisord/supervisord-nginx.conf /etc/supervisor/conf.d/supervisord-nginx.conf
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ else
|
|||||||
echo "There is no script $PRE_START_PATH"
|
echo "There is no script $PRE_START_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Start Supervisor, with Nginx and uWSGI
|
# Start Supervisor, with Nginx and Gunicorn
|
||||||
echo "Starting server using supervisord..."
|
echo "Starting server using supervisord..."
|
||||||
|
|
||||||
exec /usr/bin/supervisord
|
exec /usr/bin/supervisord
|
||||||
|
|||||||
9
deploy/docker/supervisord/supervisord-gunicorn.conf
Normal file
9
deploy/docker/supervisord/supervisord-gunicorn.conf
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[program:gunicorn]
|
||||||
|
command=/home/mediacms.io/bin/gunicorn cms.wsgi:application --workers=2 --threads=2 --worker-class=gthread --bind=127.0.0.1:9000 --user=www-data --group=www-data --timeout=120 --keep-alive=5 --max-requests=1000 --max-requests-jitter=50 --access-logfile=- --error-logfile=- --log-level=info --chdir=/home/mediacms.io/mediacms
|
||||||
|
stdout_logfile=/dev/stdout
|
||||||
|
stdout_logfile_maxbytes=0
|
||||||
|
stderr_logfile=/dev/stderr
|
||||||
|
stderr_logfile_maxbytes=0
|
||||||
|
priority=100
|
||||||
|
startinorder=true
|
||||||
|
startsecs=0
|
||||||
@ -1,9 +0,0 @@
|
|||||||
[program:uwsgi]
|
|
||||||
command=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/docker/uwsgi.ini
|
|
||||||
stdout_logfile=/dev/stdout
|
|
||||||
stdout_logfile_maxbytes=0
|
|
||||||
stderr_logfile=/dev/stderr
|
|
||||||
stderr_logfile_maxbytes=0
|
|
||||||
priority=100
|
|
||||||
startinorder=true
|
|
||||||
startsecs=0
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
|
|
||||||
chdir = /home/mediacms.io/mediacms/
|
|
||||||
virtualenv = /home/mediacms.io
|
|
||||||
module = cms.wsgi
|
|
||||||
|
|
||||||
uid=www-data
|
|
||||||
gid=www-data
|
|
||||||
|
|
||||||
processes = 2
|
|
||||||
threads = 2
|
|
||||||
|
|
||||||
master = true
|
|
||||||
|
|
||||||
socket = 127.0.0.1:9000
|
|
||||||
|
|
||||||
workers = 2
|
|
||||||
|
|
||||||
vacuum = true
|
|
||||||
|
|
||||||
hook-master-start = unix_signal:15 gracefully_kill_them_all
|
|
||||||
need-app = true
|
|
||||||
die-on-term = true
|
|
||||||
buffer-size=32768
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
uwsgi_param QUERY_STRING $query_string;
|
|
||||||
uwsgi_param REQUEST_METHOD $request_method;
|
|
||||||
uwsgi_param CONTENT_TYPE $content_type;
|
|
||||||
uwsgi_param CONTENT_LENGTH $content_length;
|
|
||||||
|
|
||||||
uwsgi_param REQUEST_URI $request_uri;
|
|
||||||
uwsgi_param PATH_INFO $document_uri;
|
|
||||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
|
||||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
|
||||||
uwsgi_param REQUEST_SCHEME $scheme;
|
|
||||||
uwsgi_param HTTPS $https if_not_empty;
|
|
||||||
|
|
||||||
uwsgi_param REMOTE_ADDR $remote_addr;
|
|
||||||
uwsgi_param REMOTE_PORT $remote_port;
|
|
||||||
uwsgi_param SERVER_PORT $server_port;
|
|
||||||
uwsgi_param SERVER_NAME $server_name;
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery beat
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=simple
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
WorkingDirectory=/home/mediacms.io/mediacms
|
|
||||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
|
||||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/beat%n.pid"
|
|
||||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/beat%N.log"
|
|
||||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
|
||||||
|
|
||||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms beat --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL}'
|
|
||||||
ExecStop=/bin/kill -s TERM $MAINPID
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery long queue
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
WorkingDirectory=/home/mediacms.io/mediacms
|
|
||||||
Environment=CELERYD_NODES="long1"
|
|
||||||
Environment=CELERY_QUEUE="long_tasks"
|
|
||||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
|
||||||
Environment=CELERYD_MULTI="multi"
|
|
||||||
Environment=CELERYD_OPTS="-Ofair --prefetch-multiplier=1"
|
|
||||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
|
||||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
|
||||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
|
||||||
|
|
||||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
|
||||||
|
|
||||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS celery short queue
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Type=forking
|
|
||||||
User=www-data
|
|
||||||
Group=www-data
|
|
||||||
Restart=always
|
|
||||||
RestartSec=10
|
|
||||||
WorkingDirectory=/home/mediacms.io/mediacms
|
|
||||||
Environment=CELERYD_NODES="short1 short2"
|
|
||||||
Environment=CELERY_QUEUE="short_tasks"
|
|
||||||
# Absolute or relative path to the 'celery' command:
|
|
||||||
Environment=CELERY_BIN="/home/mediacms.io/bin/celery"
|
|
||||||
# App instance to use
|
|
||||||
# comment out this line if you don't use an app
|
|
||||||
# or fully qualified:
|
|
||||||
#CELERY_APP="proj.tasks:app"
|
|
||||||
# How to call manage.py
|
|
||||||
Environment=CELERYD_MULTI="multi"
|
|
||||||
# Extra command-line arguments to the worker
|
|
||||||
Environment=CELERYD_OPTS="--soft-time-limit=300 -c10"
|
|
||||||
# - %n will be replaced with the first part of the nodename.
|
|
||||||
# - %I will be replaced with the current child process index
|
|
||||||
# and is important when using the prefork pool to avoid race conditions.
|
|
||||||
Environment=CELERYD_PID_FILE="/home/mediacms.io/mediacms/pids/%n.pid"
|
|
||||||
Environment=CELERYD_LOG_FILE="/home/mediacms.io/mediacms/logs/%N.log"
|
|
||||||
Environment=CELERYD_LOG_LEVEL="INFO"
|
|
||||||
|
|
||||||
ExecStart=/bin/sh -c '${CELERY_BIN} -A cms multi start ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
|
||||||
|
|
||||||
ExecStop=/bin/sh -c '${CELERY_BIN} -A cms multi stopwait ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE}'
|
|
||||||
|
|
||||||
ExecReload=/bin/sh -c '${CELERY_BIN} -A cms multi restart ${CELERYD_NODES} --pidfile=${CELERYD_PID_FILE} --logfile=${CELERYD_LOG_FILE} --loglevel=${CELERYD_LOG_LEVEL} ${CELERYD_OPTS} -Q ${CELERY_QUEUE}'
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
-----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-----
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80 ;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
access_log /var/log/nginx/mediacms.io.access.log;
|
|
||||||
|
|
||||||
error_log /var/log/nginx/mediacms.io.error.log warn;
|
|
||||||
|
|
||||||
# # redirect to https if logged in
|
|
||||||
# if ($http_cookie ~* "sessionid") {
|
|
||||||
# rewrite ^/(.*)$ https://localhost/$1 permanent;
|
|
||||||
# }
|
|
||||||
|
|
||||||
# # redirect basic forms to https
|
|
||||||
# location ~ (login|login_form|register|mail_password_form)$ {
|
|
||||||
# rewrite ^/(.*)$ https://localhost/$1 permanent;
|
|
||||||
# }
|
|
||||||
|
|
||||||
location /static {
|
|
||||||
alias /home/mediacms.io/mediacms/static ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media/original {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files/original;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
add_header 'Access-Control-Allow-Origin' '*';
|
|
||||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
|
||||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
|
||||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name localhost;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
error_log /var/log/nginx/mediacms.io.error.log warn;
|
|
||||||
|
|
||||||
location /static {
|
|
||||||
alias /home/mediacms.io/mediacms/static ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media/original {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files/original;
|
|
||||||
#auth_basic "auth protected area";
|
|
||||||
#auth_basic_user_file /home/mediacms.io/mediacms/deploy/local_install/.htpasswd;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /media {
|
|
||||||
alias /home/mediacms.io/mediacms/media_files ;
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
|
||||||
add_header 'Access-Control-Allow-Origin' '*';
|
|
||||||
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
|
|
||||||
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
|
|
||||||
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
|
|
||||||
|
|
||||||
include /etc/nginx/sites-enabled/uwsgi_params;
|
|
||||||
uwsgi_pass 127.0.0.1:9000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIFTjCCBDagAwIBAgISBNOUeDlerH9MkKmHLvZJeMYgMA0GCSqGSIb3DQEBCwUA
|
|
||||||
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
|
|
||||||
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0yMDAzMTAxNzUxNDFaFw0y
|
|
||||||
MDA2MDgxNzUxNDFaMBYxFDASBgNVBAMTC21lZGlhY21zLmlvMIIBIjANBgkqhkiG
|
|
||||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAps5Jn18nW2tq/LYFDgQ1YZGLlpF/B2AAPvvH
|
|
||||||
3yuD+AcT4skKdZouVL/a5pXrptuYL5lthO9dlcja2tuO2ltYrb7Dp01dAIFaJE8O
|
|
||||||
DKd+Sv5wr8VWQZykqzMiMBgviml7TBvUHQjvCJg8UwmnN0XSUILCttd6u4qOzS7d
|
|
||||||
lKMMsKpYzLhElBT0rzhhsWulDiy6aAZbMV95bfR74nIWsBJacy6jx3jvxAuvCtkB
|
|
||||||
OVdOoVL6BPjDE3SNEk53bAZGIb5A9ri0O5jh/zBFT6tQSjUhAUTkmv9oZP547RnV
|
|
||||||
fDj+rdvCVk/fE+Jno36mcT183Qd/Ty3fWuqFoM5g/luhnfvWEwIDAQABo4ICYDCC
|
|
||||||
AlwwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcD
|
|
||||||
AjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTd5EZBt74zu5XxT1uXQs6oM8qOuDAf
|
|
||||||
BgNVHSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEw
|
|
||||||
LgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcw
|
|
||||||
LwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv
|
|
||||||
MBYGA1UdEQQPMA2CC21lZGlhY21zLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcG
|
|
||||||
CysGAQQBgt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5
|
|
||||||
cHQub3JnMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAXqdz+d9WwOe1Nkh90Eng
|
|
||||||
MnqRmgyEoRIShBh1loFxRVgAAAFwxcnL+AAABAMARzBFAiAb3yeBuW3j9MxcRc0T
|
|
||||||
icUBvEa/rH7Fv2eB0oQlnZ1exQIhAPf+CtTXmzxoeT/BBiivj4AmGDsq4xWhe/U6
|
|
||||||
BytYrKLeAHYAB7dcG+V9aP/xsMYdIxXHuuZXfFeUt2ruvGE6GmnTohwAAAFwxcnM
|
|
||||||
HAAABAMARzBFAiAuP5gKyyaT0LVXxwjYD9zhezvxf4Icx0P9pk75c5ao+AIhAK0+
|
|
||||||
fSJv+WTXciMT6gA1sk/tuCHuDFAuexSA/6TcRXcVMA0GCSqGSIb3DQEBCwUAA4IB
|
|
||||||
AQCPCYBU4Q/ro2MUkjDPKGmeqdxQycS4R9WvKTG/nmoahKNg30bnLaDPUcpyMU2k
|
|
||||||
sPDemdZ7uTGLZ3ZrlIva8DbrnJmrTPf9BMwaM6j+ZV/QhxvKZVIWkLkZrwiVI57X
|
|
||||||
Ba+rs5IEB4oWJ0EBaeIrzeKG5zLMkRcIdE4Hlhuwu3zGG56c+wmAPuvpIDlYoO6o
|
|
||||||
W22xRdxoTIHBvkzwonpVYUaRcaIw+48xnllxh1dHO+X69DT45wlF4tKveOUi+L50
|
|
||||||
4GWJ8Vjv7Fot/WNHEM4Mnmw0jHj9TPkIZKnPNRMdHmJ5CF/FJFDiptOeuzbfohG+
|
|
||||||
mdvuInb8JDc0XBE99Gf/S4/y
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
|
|
||||||
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
|
|
||||||
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
|
|
||||||
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
|
|
||||||
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
|
|
||||||
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
|
|
||||||
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
|
|
||||||
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
|
|
||||||
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
|
|
||||||
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
|
|
||||||
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
|
|
||||||
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
|
|
||||||
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
|
|
||||||
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
|
|
||||||
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
|
|
||||||
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
|
|
||||||
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
|
|
||||||
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
|
|
||||||
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
|
|
||||||
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
|
|
||||||
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
|
|
||||||
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
|
|
||||||
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
|
|
||||||
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
|
|
||||||
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
@ -1,28 +0,0 @@
|
|||||||
-----BEGIN PRIVATE KEY-----
|
|
||||||
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmzkmfXydba2r8
|
|
||||||
tgUOBDVhkYuWkX8HYAA++8ffK4P4BxPiyQp1mi5Uv9rmleum25gvmW2E712VyNra
|
|
||||||
247aW1itvsOnTV0AgVokTw4Mp35K/nCvxVZBnKSrMyIwGC+KaXtMG9QdCO8ImDxT
|
|
||||||
Cac3RdJQgsK213q7io7NLt2UowywqljMuESUFPSvOGGxa6UOLLpoBlsxX3lt9Hvi
|
|
||||||
chawElpzLqPHeO/EC68K2QE5V06hUvoE+MMTdI0STndsBkYhvkD2uLQ7mOH/MEVP
|
|
||||||
q1BKNSEBROSa/2hk/njtGdV8OP6t28JWT98T4mejfqZxPXzdB39PLd9a6oWgzmD+
|
|
||||||
W6Gd+9YTAgMBAAECggEADnEJuryYQbf5GUwBAAepP3tEZJLQNqk/HDTcRxwTXuPt
|
|
||||||
+tKBD1F79WZu40vTjSyx7l0QOFQo/BDZsd0Ubx89fD1p3xA5nxOT5FTb2IifzIpe
|
|
||||||
4zjokOGo+BGDQjq10vvy6tH1+VWOrGXRwzawvX5UCRhpFz9sptQGLQmDsZy0Oo9B
|
|
||||||
LtavYVUqsbyqRWlzaclHgbythegIACWkqcalOzOtx+l6TGBRjej+c7URcwYBfr7t
|
|
||||||
XTAzbP+vnpaJovZyZT1eekr0OLzMpnjx4HvRvzL+NxauRpn6KfabsTfZlk8nrs4I
|
|
||||||
UdSjeukj1Iz8rGQilHdN/4dVJ3KzrlHVkVTBSjmMUQKBgQDaVXZnhAScfdiKeZbO
|
|
||||||
rdUAWcnwfkDghtRuAmzHaRM/FhFBEoVhdSbBuu+OUyBnIw/Ra4o2ePuEBcKIUiQO
|
|
||||||
w2tnE1CY5PPAcjw+OCSpvzy5xxjaqaRbm9BJp3FTeEYGLXERnchPpHg/NpexuF22
|
|
||||||
QOJ+FrysPyNMxuQp47ZwO9WT3QKBgQDDlSGjq/eeWxemwf7ZqMVlRyqsdJsgnCew
|
|
||||||
DkC62IGiYCBDfeEmndN+vcA/uzJHYV4iXiqS3aYJCWGaZFMhdIhIn5MgULvO1j5G
|
|
||||||
u/MxuzaaNPz22FlNCWTLBw4T1HOOvyTL+nLtZDKJ/BHxgHCmur1kiGvvZWrcCthD
|
|
||||||
afLEmseqrwKBgBuLZKCymxJTHhp6NHhmndSpfzyD8RNibzJhw+90ZiUzV4HqIEGn
|
|
||||||
Ufhm6Qn/mrroRXqaIpm0saZ6Q4yHMF1cchRS73wahlXlE4yV8KopojOd1pjfhgi4
|
|
||||||
o5JnOXjaV5s36GfcjATgLvtqm8CkDc6MaQaXP75LSNzKysYuIDoQkmVRAoGAAghF
|
|
||||||
rja2Pv4BU+lGJarcSj4gEmSvy/nza5/qSka/qhlHnIvtUAJp1TJRkhf24MkBOmgy
|
|
||||||
Fw6YkBV53ynVt05HsEGAPOC54t9VDFUdpNGmMpoEWuhKnUNQuc9b9RbLEJup3TjA
|
|
||||||
Avl8kPR+lzzXbtQX7biBLp6mKp0uPB0YubRGCN8CgYA0JMxK0x38Q2x3AQVhOmZh
|
|
||||||
YubtIa0JqVJhvpweOCFnkq3ebBpLsWYwiLTn86vuD0jupe5M3sxtefjkJmAKd8xY
|
|
||||||
aBU7QWhjh1fX4mzmggnbjcrIFbkIHsxwMeg567U/4AGxOOUsv9QUn37mqycqRKEn
|
|
||||||
YfUyYNLM6F3MmQAOs2kaHw==
|
|
||||||
-----END PRIVATE KEY-----
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=MediaCMS uwsgi
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
ExecStart=/home/mediacms.io/bin/uwsgi --ini /home/mediacms.io/mediacms/deploy/local_install/uwsgi.ini
|
|
||||||
ExecStop=/usr/bin/killall -9 uwsgi
|
|
||||||
RestartSec=3
|
|
||||||
#ExecRestart=killall -9 uwsgi; sleep 5; /home/sss/bin/uwsgi --ini /home/sss/wordgames/uwsgi.ini
|
|
||||||
Restart=always
|
|
||||||
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
/home/mediacms.io/mediacms/logs/*.log {
|
|
||||||
weekly
|
|
||||||
missingok
|
|
||||||
rotate 7
|
|
||||||
compress
|
|
||||||
notifempty
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
user www-data;
|
|
||||||
worker_processes auto;
|
|
||||||
pid /run/nginx.pid;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 10240;
|
|
||||||
}
|
|
||||||
|
|
||||||
worker_rlimit_nofile 20000; #each connection needs a filehandle (or 2 if you are proxying)
|
|
||||||
http {
|
|
||||||
proxy_connect_timeout 75;
|
|
||||||
proxy_read_timeout 12000;
|
|
||||||
client_max_body_size 5800M;
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 10;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
|
|
||||||
include /etc/nginx/mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
access_log /var/log/nginx/access.log;
|
|
||||||
error_log /var/log/nginx/error.log;
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_disable "msie6";
|
|
||||||
|
|
||||||
log_format compression '$remote_addr - $remote_user [$time_local] '
|
|
||||||
'"$request" $status $body_bytes_sent '
|
|
||||||
'"$http_referer" "$http_user_agent" "$gzip_ratio"';
|
|
||||||
|
|
||||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
|
||||||
|
|
||||||
include /etc/nginx/conf.d/*.conf;
|
|
||||||
include /etc/nginx/sites-enabled/*;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
module selinux-mediacms 1.0;
|
|
||||||
|
|
||||||
require {
|
|
||||||
type init_t;
|
|
||||||
type var_t;
|
|
||||||
type redis_port_t;
|
|
||||||
type postgresql_port_t;
|
|
||||||
type httpd_t;
|
|
||||||
type httpd_sys_content_t;
|
|
||||||
type httpd_sys_rw_content_t;
|
|
||||||
class file { append create execute execute_no_trans getattr ioctl lock open read rename setattr unlink write };
|
|
||||||
class dir { add_name remove_name rmdir };
|
|
||||||
class tcp_socket name_connect;
|
|
||||||
class lnk_file read;
|
|
||||||
}
|
|
||||||
|
|
||||||
#============= httpd_t ==============
|
|
||||||
|
|
||||||
allow httpd_t var_t:file { getattr open read };
|
|
||||||
|
|
||||||
#============= init_t ==============
|
|
||||||
allow init_t postgresql_port_t:tcp_socket name_connect;
|
|
||||||
|
|
||||||
allow init_t redis_port_t:tcp_socket name_connect;
|
|
||||||
|
|
||||||
allow init_t httpd_sys_content_t:dir rmdir;
|
|
||||||
|
|
||||||
allow init_t httpd_sys_content_t:file { append create execute execute_no_trans ioctl lock open read rename setattr unlink write };
|
|
||||||
|
|
||||||
allow init_t httpd_sys_content_t:lnk_file read;
|
|
||||||
|
|
||||||
allow init_t httpd_sys_rw_content_t:dir { add_name remove_name rmdir };
|
|
||||||
|
|
||||||
allow init_t httpd_sys_rw_content_t:file { create ioctl lock open read setattr unlink write };
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
[uwsgi]
|
|
||||||
|
|
||||||
chdir = /home/mediacms.io/mediacms/
|
|
||||||
virtualenv = /home/mediacms.io
|
|
||||||
module = cms.wsgi
|
|
||||||
|
|
||||||
uid=www-data
|
|
||||||
gid=www-data
|
|
||||||
|
|
||||||
processes = 2
|
|
||||||
threads = 2
|
|
||||||
|
|
||||||
master = true
|
|
||||||
|
|
||||||
socket = 127.0.0.1:9000
|
|
||||||
#socket = /home/mediacms.io/mediacms/deploy/uwsgi.sock
|
|
||||||
|
|
||||||
|
|
||||||
workers = 2
|
|
||||||
|
|
||||||
|
|
||||||
vacuum = true
|
|
||||||
|
|
||||||
logto = /home/mediacms.io/mediacms/logs/errorlog.txt
|
|
||||||
|
|
||||||
disable-logging = true
|
|
||||||
buffer-size=32768
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
uwsgi_param QUERY_STRING $query_string;
|
|
||||||
uwsgi_param REQUEST_METHOD $request_method;
|
|
||||||
uwsgi_param CONTENT_TYPE $content_type;
|
|
||||||
uwsgi_param CONTENT_LENGTH $content_length;
|
|
||||||
|
|
||||||
uwsgi_param REQUEST_URI $request_uri;
|
|
||||||
uwsgi_param PATH_INFO $document_uri;
|
|
||||||
uwsgi_param DOCUMENT_ROOT $document_root;
|
|
||||||
uwsgi_param SERVER_PROTOCOL $server_protocol;
|
|
||||||
uwsgi_param REQUEST_SCHEME $scheme;
|
|
||||||
uwsgi_param HTTPS $https if_not_empty;
|
|
||||||
|
|
||||||
uwsgi_param REMOTE_ADDR $remote_addr;
|
|
||||||
uwsgi_param REMOTE_PORT $remote_port;
|
|
||||||
uwsgi_param SERVER_PORT $server_port;
|
|
||||||
uwsgi_param SERVER_NAME $server_name;
|
|
||||||
@ -23,7 +23,7 @@ and will start all services required for MediaCMS, as Celery/Redis for asynchron
|
|||||||
For Django, the changes from the image produced by docker-compose.yaml are these:
|
For Django, the changes from the image produced by docker-compose.yaml are these:
|
||||||
|
|
||||||
* Django runs in debug mode, with `python manage.py runserver`
|
* Django runs in debug mode, with `python manage.py runserver`
|
||||||
* uwsgi and nginx are not run
|
* gunicorn and nginx are not run
|
||||||
* Django runs in Debug mode, with Debug Toolbar
|
* Django runs in Debug mode, with Debug Toolbar
|
||||||
* Static files (js/css) are loaded from static/ folder
|
* Static files (js/css) are loaded from static/ folder
|
||||||
* corsheaders is installed and configured to allow all origins
|
* corsheaders is installed and configured to allow all origins
|
||||||
|
|||||||
@ -65,6 +65,7 @@ class CategoryAdminForm(forms.ModelForm):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Category
|
model = Category
|
||||||
|
# LTI fields will be shown as read-only when USE_LTI is enabled
|
||||||
fields = '__all__'
|
fields = '__all__'
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
@ -135,7 +136,7 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
list_display = ["title", "user", "add_date", "media_count"]
|
list_display = ["title", "user", "add_date", "media_count"]
|
||||||
list_filter = []
|
list_filter = []
|
||||||
ordering = ("-add_date",)
|
ordering = ("-add_date",)
|
||||||
readonly_fields = ("user", "media_count")
|
readonly_fields = ("user", "media_count", "lti_platform", "lti_context_id")
|
||||||
change_form_template = 'admin/files/category/change_form.html'
|
change_form_template = 'admin/files/category/change_form.html'
|
||||||
|
|
||||||
def get_list_filter(self, request):
|
def get_list_filter(self, request):
|
||||||
@ -167,6 +168,14 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
additional_fieldsets = []
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
lti_fieldset = [
|
||||||
|
('LTI Integration', {'fields': ['lti_platform', 'lti_context_id'], 'classes': ['tab'], 'description': 'LTI/LMS integration settings (automatically managed by LTI provisioning)'}),
|
||||||
|
]
|
||||||
|
additional_fieldsets.extend(lti_fieldset)
|
||||||
|
|
||||||
if getattr(settings, 'USE_RBAC', False):
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
rbac_fieldset = [
|
rbac_fieldset = [
|
||||||
('RBAC Settings', {'fields': ['is_rbac_category'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
('RBAC Settings', {'fields': ['is_rbac_category'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
||||||
@ -177,9 +186,9 @@ class CategoryAdmin(admin.ModelAdmin):
|
|||||||
('RBAC Settings', {'fields': ['is_rbac_category', 'identity_provider'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
('RBAC Settings', {'fields': ['is_rbac_category', 'identity_provider'], 'classes': ['tab'], 'description': 'Role-Based Access Control settings'}),
|
||||||
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
|
('Group Access', {'fields': ['rbac_groups'], 'description': 'Select the Groups that have access to category'}),
|
||||||
]
|
]
|
||||||
return basic_fieldset + rbac_fieldset
|
additional_fieldsets.extend(rbac_fieldset)
|
||||||
else:
|
|
||||||
return basic_fieldset
|
return basic_fieldset + additional_fieldsets
|
||||||
|
|
||||||
|
|
||||||
class TagAdmin(admin.ModelAdmin):
|
class TagAdmin(admin.ModelAdmin):
|
||||||
|
|||||||
@ -64,4 +64,10 @@ def stuff(request):
|
|||||||
if request.user.is_superuser:
|
if request.user.is_superuser:
|
||||||
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
ret["DJANGO_ADMIN_URL"] = settings.DJANGO_ADMIN_URL
|
||||||
|
|
||||||
|
if getattr(settings, 'USE_LTI', False):
|
||||||
|
lti_session = request.session.get('lti_session')
|
||||||
|
|
||||||
|
if lti_session and request.user.is_authenticated:
|
||||||
|
ret['lti_session'] = lti_session
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|||||||
@ -965,3 +965,13 @@ def get_alphanumeric_only(string):
|
|||||||
"""
|
"""
|
||||||
string = "".join([char for char in string if char.isalnum()])
|
string = "".join([char for char in string if char.isalnum()])
|
||||||
return string.lower()
|
return string.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def get_alphanumeric_and_spaces(string):
|
||||||
|
"""Returns a query that contains only alphanumeric characters and spaces
|
||||||
|
This include characters other than the English alphabet too
|
||||||
|
"""
|
||||||
|
string = "".join([char for char in string if char.isalnum() or char.isspace()])
|
||||||
|
# Replace multiple spaces with single space and strip
|
||||||
|
string = " ".join(string.split())
|
||||||
|
return string
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-12-29 16:15
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('files', '0014_alter_subtitle_options_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='is_lms_course',
|
||||||
|
field=models.BooleanField(db_index=True, default=False, help_text='Whether this category represents an LMS course'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='lti_context_id',
|
||||||
|
field=models.CharField(blank=True, db_index=True, help_text='LTI context ID from platform', max_length=255),
|
||||||
|
),
|
||||||
|
]
|
||||||
21
files/migrations/0016_category_lti_platform.py
Normal file
21
files/migrations/0016_category_lti_platform.py
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.2.6 on 2025-12-29 16:15
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
('files', '0015_category_is_lms_course_category_lti_context_id'),
|
||||||
|
('lti', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='category',
|
||||||
|
name='lti_platform',
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True, help_text='LTI Platform if this is an LTI course', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='categories', to='lti.ltiplatform'
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -47,6 +47,13 @@ class Category(models.Model):
|
|||||||
verbose_name='IDP Config Name',
|
verbose_name='IDP Config Name',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# LTI/LMS integration fields
|
||||||
|
is_lms_course = models.BooleanField(default=False, db_index=True, help_text='Whether this category represents an LMS course')
|
||||||
|
|
||||||
|
lti_platform = models.ForeignKey('lti.LTIPlatform', blank=True, null=True, on_delete=models.SET_NULL, related_name='categories', help_text='LTI Platform if this is an LTI course')
|
||||||
|
|
||||||
|
lti_context_id = models.CharField(max_length=255, blank=True, db_index=True, help_text='LTI context ID from platform')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
||||||
@ -137,7 +144,7 @@ class Tag(models.Model):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.title = helpers.get_alphanumeric_only(self.title)
|
self.title = helpers.get_alphanumeric_and_spaces(self.title)
|
||||||
self.title = self.title[:100]
|
self.title = self.title[:100]
|
||||||
super(Tag, self).save(*args, **kwargs)
|
super(Tag, self).save(*args, **kwargs)
|
||||||
|
|
||||||
|
|||||||
@ -352,20 +352,11 @@ class Media(models.Model):
|
|||||||
# first get anything interesting out of the media
|
# first get anything interesting out of the media
|
||||||
# that needs to be search able
|
# that needs to be search able
|
||||||
|
|
||||||
a_tags = b_tags = ""
|
a_tags = ""
|
||||||
if self.id:
|
if self.id:
|
||||||
a_tags = " ".join([tag.title for tag in self.tags.all()])
|
a_tags = " ".join([tag.title for tag in self.tags.all()])
|
||||||
b_tags = " ".join([tag.title.replace("-", " ") for tag in self.tags.all()])
|
|
||||||
|
|
||||||
items = [
|
items = [self.friendly_token, self.title, self.user.username, self.user.email, self.user.name, self.description, a_tags]
|
||||||
self.title,
|
|
||||||
self.user.username,
|
|
||||||
self.user.email,
|
|
||||||
self.user.name,
|
|
||||||
self.description,
|
|
||||||
a_tags,
|
|
||||||
b_tags,
|
|
||||||
]
|
|
||||||
|
|
||||||
for subtitle in self.subtitles.all():
|
for subtitle in self.subtitles.all():
|
||||||
items.append(subtitle.subtitle_text)
|
items.append(subtitle.subtitle_text)
|
||||||
|
|||||||
@ -80,6 +80,7 @@ urlpatterns = [
|
|||||||
views.trim_video,
|
views.trim_video,
|
||||||
),
|
),
|
||||||
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
|
re_path(r"^api/v1/categories$", views.CategoryList.as_view()),
|
||||||
|
re_path(r"^api/v1/categories/contributor$", views.CategoryListContributor.as_view()),
|
||||||
re_path(r"^api/v1/tags$", views.TagList.as_view()),
|
re_path(r"^api/v1/tags$", views.TagList.as_view()),
|
||||||
re_path(r"^api/v1/comments$", views.CommentList.as_view()),
|
re_path(r"^api/v1/comments$", views.CommentList.as_view()),
|
||||||
re_path(
|
re_path(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
# Import all views for backward compatibility
|
# Import all views for backward compatibility
|
||||||
|
|
||||||
from .auth import custom_login_view, saml_metadata # noqa: F401
|
from .auth import custom_login_view, saml_metadata # noqa: F401
|
||||||
from .categories import CategoryList, TagList # noqa: F401
|
from .categories import CategoryList, CategoryListContributor, TagList # noqa: F401
|
||||||
from .comments import CommentDetail, CommentList # noqa: F401
|
from .comments import CommentDetail, CommentList # noqa: F401
|
||||||
from .encoding import EncodeProfileList, EncodingDetail # noqa: F401
|
from .encoding import EncodeProfileList, EncodingDetail # noqa: F401
|
||||||
from .media import MediaActions # noqa: F401
|
from .media import MediaActions # noqa: F401
|
||||||
|
|||||||
@ -43,6 +43,40 @@ class CategoryList(APIView):
|
|||||||
return Response(ret)
|
return Response(ret)
|
||||||
|
|
||||||
|
|
||||||
|
class CategoryListContributor(APIView):
|
||||||
|
"""List categories where user has contributor access"""
|
||||||
|
|
||||||
|
@swagger_auto_schema(
|
||||||
|
manual_parameters=[],
|
||||||
|
tags=['Categories'],
|
||||||
|
operation_summary='Lists Categories for Contributors',
|
||||||
|
operation_description='Lists all categories where the user has contributor access',
|
||||||
|
responses={
|
||||||
|
200: openapi.Response('response description', CategorySerializer),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
def get(self, request, format=None):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return Response([])
|
||||||
|
|
||||||
|
categories = Category.objects.none()
|
||||||
|
|
||||||
|
# Get global/public categories (non-RBAC)
|
||||||
|
public_categories = Category.objects.filter(is_rbac_category=False).prefetch_related("user")
|
||||||
|
|
||||||
|
# Get RBAC categories where user has contributor access
|
||||||
|
if getattr(settings, 'USE_RBAC', False):
|
||||||
|
rbac_categories = request.user.get_rbac_categories_as_contributor()
|
||||||
|
categories = public_categories.union(rbac_categories)
|
||||||
|
else:
|
||||||
|
categories = public_categories
|
||||||
|
|
||||||
|
categories = categories.order_by("title")
|
||||||
|
|
||||||
|
serializer = CategorySerializer(categories, many=True, context={"request": request})
|
||||||
|
return Response(serializer.data)
|
||||||
|
|
||||||
|
|
||||||
class TagList(APIView):
|
class TagList(APIView):
|
||||||
"""List tags"""
|
"""List tags"""
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ from ..forms import (
|
|||||||
WhisperSubtitlesForm,
|
WhisperSubtitlesForm,
|
||||||
)
|
)
|
||||||
from ..frontend_translations import translate_string
|
from ..frontend_translations import translate_string
|
||||||
from ..helpers import get_alphanumeric_only
|
from ..helpers import get_alphanumeric_and_spaces
|
||||||
from ..methods import (
|
from ..methods import (
|
||||||
can_transcribe_video,
|
can_transcribe_video,
|
||||||
create_video_trim_request,
|
create_video_trim_request,
|
||||||
@ -310,8 +310,8 @@ def edit_media(request):
|
|||||||
media.tags.remove(tag)
|
media.tags.remove(tag)
|
||||||
if form.cleaned_data.get("new_tags"):
|
if form.cleaned_data.get("new_tags"):
|
||||||
for tag in form.cleaned_data.get("new_tags").split(","):
|
for tag in form.cleaned_data.get("new_tags").split(","):
|
||||||
tag = get_alphanumeric_only(tag)
|
tag = get_alphanumeric_and_spaces(tag)
|
||||||
tag = tag[:99]
|
tag = tag[:100]
|
||||||
if tag:
|
if tag:
|
||||||
try:
|
try:
|
||||||
tag = Tag.objects.get(title=tag)
|
tag = Tag.objects.get(title=tag)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { translateString } from '../utils/helpers/';
|
import { translateString, inSelectMediaEmbedMode } from '../utils/helpers/';
|
||||||
|
|
||||||
interface MediaListHeaderProps {
|
interface MediaListHeaderProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -11,10 +11,12 @@ interface MediaListHeaderProps {
|
|||||||
|
|
||||||
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
|
export const MediaListHeader: React.FC<MediaListHeaderProps> = (props) => {
|
||||||
const viewAllText = props.viewAllText || translateString('VIEW ALL');
|
const viewAllText = props.viewAllText || translateString('VIEW ALL');
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
|
<div className={(props.className ? props.className + ' ' : '') + 'media-list-header'} style={props.style}>
|
||||||
<h2>{props.title}</h2>
|
<h2>{props.title}</h2>
|
||||||
{props.viewAllLink ? (
|
{!isSelectMediaMode && props.viewAllLink ? (
|
||||||
<h3>
|
<h3>
|
||||||
{' '}
|
{' '}
|
||||||
<a href={props.viewAllLink} title={viewAllText}>
|
<a href={props.viewAllLink} title={viewAllText}>
|
||||||
|
|||||||
@ -1,18 +1,50 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveInteger, PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveInteger, PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
|
import { MediaItemThumbnailLink, itemClassname } from './includes/items/';
|
||||||
import { Item } from './Item';
|
import { Item } from './Item';
|
||||||
|
|
||||||
export function MediaItem(props) {
|
export function MediaItem(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In embed mode, render thumbnail without link
|
||||||
|
const thumbStyle = thumbnailUrl ? { backgroundImage: "url('" + thumbnailUrl + "')" } : null;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={thumbStyle}
|
||||||
|
>
|
||||||
|
{thumbnailUrl ? (
|
||||||
|
<div key="item-type-icon" className="item-type-icon">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
return <MediaItemThumbnailLink src={thumbnailUrl} title={props.title} link={props.link} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,11 +57,13 @@ export function MediaItem(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
|
||||||
|
// In select media mode or if there's any selection active, clicking the item should toggle selection
|
||||||
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@ -59,16 +93,24 @@ export function MediaItem(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{thumbnailComponent()}
|
{thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaDurationInfo } from '../../utils/classes/';
|
import { MediaDurationInfo } from '../../utils/classes/';
|
||||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
|
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions';
|
||||||
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
import { MediaItemDuration, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||||
@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
|
|||||||
|
|
||||||
export function MediaItemAudio(props) {
|
export function MediaItemAudio(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
const _MediaDurationInfo = new MediaDurationInfo();
|
const _MediaDurationInfo = new MediaDurationInfo();
|
||||||
|
|
||||||
_MediaDurationInfo.update(props.duration);
|
_MediaDurationInfo.update(props.duration);
|
||||||
@ -22,6 +38,21 @@ export function MediaItemAudio(props) {
|
|||||||
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
const durationISO8601 = _MediaDurationInfo.ISO8601();
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In embed mode, render thumbnail without link
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
|
||||||
|
>
|
||||||
|
{props.inPlaylistView ? null : (
|
||||||
|
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const attr = {
|
const attr = {
|
||||||
key: 'item-thumb',
|
key: 'item-thumb',
|
||||||
href: props.link,
|
href: props.link,
|
||||||
@ -68,11 +99,11 @@ export function MediaItemAudio(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
// In embed mode or if there's any selection active, clicking the item should toggle selection
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@ -104,16 +135,24 @@ export function MediaItemAudio(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{thumbnailComponent()}
|
{thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{playlistOptionsComponent()}
|
{playlistOptionsComponent()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useMediaItem } from '../../utils/hooks/';
|
import { useMediaItem } from '../../utils/hooks/';
|
||||||
import { PositiveIntegerOrZero } from '../../utils/helpers/';
|
import { PositiveIntegerOrZero, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
import { MediaDurationInfo } from '../../utils/classes/';
|
import { MediaDurationInfo } from '../../utils/classes/';
|
||||||
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
|
import { MediaPlaylistOptions } from '../media-playlist-options/MediaPlaylistOptions.jsx';
|
||||||
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
import { MediaItemVideoPlayer, MediaItemDuration, MediaItemVideoPreviewer, MediaItemPlaylistIndex, itemClassname } from './includes/items/';
|
||||||
@ -9,10 +9,26 @@ import { MediaItem } from './MediaItem';
|
|||||||
|
|
||||||
export function MediaItemVideo(props) {
|
export function MediaItemVideo(props) {
|
||||||
const type = props.type;
|
const type = props.type;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
const [titleComponent, descriptionComponent, thumbnailUrl, UnderThumbWrapper, editMediaComponent, metaComponents, viewMediaComponent] =
|
const [titleComponentOrig, descriptionComponent, thumbnailUrl, UnderThumbWrapperOrig, editMediaComponent, metaComponents, viewMediaComponent] =
|
||||||
useMediaItem({ ...props, type });
|
useMediaItem({ ...props, type });
|
||||||
|
|
||||||
|
// In embed mode, override components to remove links
|
||||||
|
const ItemTitle = ({ title }) => (
|
||||||
|
<h3>
|
||||||
|
<span>{title}</span>
|
||||||
|
</h3>
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemMain = ({ children }) => <div className="item-main">{children}</div>;
|
||||||
|
|
||||||
|
const titleComponent = isSelectMediaMode
|
||||||
|
? () => <ItemTitle title={props.title} />
|
||||||
|
: titleComponentOrig;
|
||||||
|
|
||||||
|
const UnderThumbWrapper = isSelectMediaMode ? ItemMain : UnderThumbWrapperOrig;
|
||||||
|
|
||||||
const _MediaDurationInfo = new MediaDurationInfo();
|
const _MediaDurationInfo = new MediaDurationInfo();
|
||||||
|
|
||||||
_MediaDurationInfo.update(props.duration);
|
_MediaDurationInfo.update(props.duration);
|
||||||
@ -26,6 +42,24 @@ export function MediaItemVideo(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function thumbnailComponent() {
|
function thumbnailComponent() {
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
// In select media mode, render thumbnail without link
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key="item-thumb"
|
||||||
|
className={'item-thumb' + (!thumbnailUrl ? ' no-thumb' : '')}
|
||||||
|
style={!thumbnailUrl ? null : { backgroundImage: "url('" + thumbnailUrl + "')" }}
|
||||||
|
>
|
||||||
|
{props.inPlaylistView ? null : (
|
||||||
|
<MediaItemDuration ariaLabel={duration} time={durationISO8601} text={durationStr} />
|
||||||
|
)}
|
||||||
|
{props.inPlaylistView || props.inPlaylistPage ? null : (
|
||||||
|
<MediaItemVideoPreviewer url={props.preview_thumbnail} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const attr = {
|
const attr = {
|
||||||
key: 'item-thumb',
|
key: 'item-thumb',
|
||||||
href: props.link,
|
href: props.link,
|
||||||
@ -75,11 +109,11 @@ export function MediaItemVideo(props) {
|
|||||||
const finalClassname = containerClassname +
|
const finalClassname = containerClassname +
|
||||||
(props.showSelection ? ' with-selection' : '') +
|
(props.showSelection ? ' with-selection' : '') +
|
||||||
(props.isSelected ? ' selected' : '') +
|
(props.isSelected ? ' selected' : '') +
|
||||||
(props.hasAnySelection ? ' has-any-selection' : '');
|
(props.hasAnySelection || isSelectMediaMode ? ' has-any-selection' : '');
|
||||||
|
|
||||||
const handleItemClick = (e) => {
|
const handleItemClick = (e) => {
|
||||||
// If there's any selection active, clicking the item should toggle selection
|
// In select media mode or if there's any selection active, clicking the item should toggle selection
|
||||||
if (props.hasAnySelection && props.onCheckboxChange) {
|
if ((isSelectMediaMode || props.hasAnySelection) && props.onCheckboxChange) {
|
||||||
// Check if clicking on the checkbox itself, edit icon, or view icon
|
// Check if clicking on the checkbox itself, edit icon, or view icon
|
||||||
if (e.target.closest('.item-selection-checkbox') ||
|
if (e.target.closest('.item-selection-checkbox') ||
|
||||||
e.target.closest('.item-edit-icon') ||
|
e.target.closest('.item-edit-icon') ||
|
||||||
@ -111,19 +145,27 @@ export function MediaItemVideo(props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{editMediaComponent()}
|
{!isSelectMediaMode && editMediaComponent()}
|
||||||
{viewMediaComponent()}
|
{!isSelectMediaMode && viewMediaComponent()}
|
||||||
|
|
||||||
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
|
{props.hasMediaViewer ? videoViewerComponent() : thumbnailComponent()}
|
||||||
|
|
||||||
<UnderThumbWrapper title={props.title} link={props.link}>
|
{isSelectMediaMode ? (
|
||||||
{titleComponent()}
|
<UnderThumbWrapper>
|
||||||
{metaComponents()}
|
{titleComponent()}
|
||||||
{descriptionComponent()}
|
{metaComponents()}
|
||||||
</UnderThumbWrapper>
|
{descriptionComponent()}
|
||||||
</div>
|
</UnderThumbWrapper>
|
||||||
|
) : (
|
||||||
|
<UnderThumbWrapper title={props.title} link={props.link}>
|
||||||
|
{titleComponent()}
|
||||||
|
{metaComponents()}
|
||||||
|
{descriptionComponent()}
|
||||||
|
</UnderThumbWrapper>
|
||||||
|
)}
|
||||||
|
|
||||||
{playlistOptionsComponent()}
|
{playlistOptionsComponent()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { LinksContext, MemberContext, SiteContext } from '../../utils/contexts/'
|
|||||||
import { PageStore, ProfilePageStore } from '../../utils/stores/';
|
import { PageStore, ProfilePageStore } from '../../utils/stores/';
|
||||||
import { PageActions, ProfilePageActions } from '../../utils/actions/';
|
import { PageActions, ProfilePageActions } from '../../utils/actions/';
|
||||||
import { CircleIconButton, PopupMain } from '../_shared';
|
import { CircleIconButton, PopupMain } from '../_shared';
|
||||||
import { translateString } from '../../utils/helpers/';
|
import { translateString, inEmbeddedApp, inSelectMediaEmbedMode } from '../../utils/helpers/';
|
||||||
|
|
||||||
class ProfileSearchBar extends React.PureComponent {
|
class ProfileSearchBar extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -372,18 +372,22 @@ class NavMenuInlineTabs extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav ref="tabsNav" className="profile-nav items-list-outer list-inline list-slider">
|
<nav ref="tabsNav" className="profile-nav items-list-outer list-inline list-slider">
|
||||||
<div className="profile-nav-inner items-list-outer">
|
<div className="profile-nav-inner items-list-outer">
|
||||||
{this.state.displayPrev ? this.previousBtn : null}
|
{this.state.displayPrev ? this.previousBtn : null}
|
||||||
|
|
||||||
<ul className="items-list-wrap" ref="itemsListWrap">
|
<ul className="items-list-wrap" ref="itemsListWrap">
|
||||||
<InlineTab
|
{!isSelectMediaMode ? (
|
||||||
id="about"
|
<InlineTab
|
||||||
isActive={'about' === this.props.type}
|
id="about"
|
||||||
label={translateString('About')}
|
isActive={'about' === this.props.type}
|
||||||
link={LinksContext._currentValue.profile.about}
|
label={translateString('About')}
|
||||||
/>
|
link={LinksContext._currentValue.profile.about}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
<InlineTab
|
<InlineTab
|
||||||
id="media"
|
id="media"
|
||||||
isActive={'media' === this.props.type}
|
isActive={'media' === this.props.type}
|
||||||
@ -407,7 +411,7 @@ class NavMenuInlineTabs extends React.PureComponent {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{MemberContext._currentValue.can.saveMedia ? (
|
{!isSelectMediaMode && MemberContext._currentValue.can.saveMedia ? (
|
||||||
<InlineTab
|
<InlineTab
|
||||||
id="playlists"
|
id="playlists"
|
||||||
isActive={'playlists' === this.props.type}
|
isActive={'playlists' === this.props.type}
|
||||||
@ -768,7 +772,15 @@ export default function ProfilePagesHeader(props) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="profile-info-nav-wrap">
|
<div className="profile-info-nav-wrap">
|
||||||
{props.author.thumbnail_url || props.author.name ? (
|
{inSelectMediaEmbedMode() ? (
|
||||||
|
<div className="profile-info">
|
||||||
|
<div className="profile-info-inner">
|
||||||
|
<div>
|
||||||
|
<h1>{translateString('Embed Media')}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : props.author.thumbnail_url || props.author.name ? (
|
||||||
<div className="profile-info">
|
<div className="profile-info">
|
||||||
<div className="profile-info-inner">
|
<div className="profile-info-inner">
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts';
|
import { ApiUrlContext, LinksConsumer, MemberContext } from '../utils/contexts';
|
||||||
import { PageStore, ProfilePageStore } from '../utils/stores';
|
import { PageStore, ProfilePageStore } from '../utils/stores';
|
||||||
import { ProfilePageActions, PageActions } from '../utils/actions';
|
import { ProfilePageActions, PageActions } from '../utils/actions';
|
||||||
import { inEmbeddedApp, translateString } from '../utils/helpers/';
|
import { inEmbeddedApp, inSelectMediaEmbedMode, translateString } from '../utils/helpers/';
|
||||||
import { MediaListWrapper } from '../components/MediaListWrapper';
|
import { MediaListWrapper } from '../components/MediaListWrapper';
|
||||||
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
import ProfilePagesHeader from '../components/profile-page/ProfilePagesHeader';
|
||||||
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
import ProfilePagesContent from '../components/profile-page/ProfilePagesContent';
|
||||||
@ -202,13 +202,45 @@ export class ProfileMediaPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleMediaSelection(mediaId, isSelected) {
|
handleMediaSelection(mediaId, isSelected) {
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
this.setState((prevState) => {
|
this.setState((prevState) => {
|
||||||
const newSelectedMedia = new Set(prevState.selectedMedia);
|
const newSelectedMedia = new Set();
|
||||||
if (isSelected) {
|
|
||||||
newSelectedMedia.add(mediaId);
|
// In select media mode, only allow single selection
|
||||||
|
if (isSelectMediaMode) {
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
console.log('Selected media item:', mediaId);
|
||||||
|
|
||||||
|
// Send postMessage to parent window (Moodle TinyMCE plugin)
|
||||||
|
if (window.parent !== window) {
|
||||||
|
// Construct the embed URL
|
||||||
|
const baseUrl = window.location.origin;
|
||||||
|
const embedUrl = `${baseUrl}/embed?m=${mediaId}`;
|
||||||
|
|
||||||
|
// Send message in the format expected by the Moodle plugin
|
||||||
|
window.parent.postMessage({
|
||||||
|
type: 'videoSelected',
|
||||||
|
embedUrl: embedUrl,
|
||||||
|
videoId: mediaId
|
||||||
|
}, '*');
|
||||||
|
|
||||||
|
console.log('Sent postMessage to parent:', { embedUrl, videoId: mediaId });
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newSelectedMedia.delete(mediaId);
|
// Normal mode: allow multiple selection
|
||||||
|
newSelectedMedia.clear();
|
||||||
|
prevState.selectedMedia.forEach((id) => newSelectedMedia.add(id));
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
newSelectedMedia.add(mediaId);
|
||||||
|
} else {
|
||||||
|
newSelectedMedia.delete(mediaId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { selectedMedia: newSelectedMedia };
|
return { selectedMedia: newSelectedMedia };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -917,6 +949,7 @@ export class ProfileMediaPage extends Page {
|
|||||||
const authorData = ProfilePageStore.get('author-data');
|
const authorData = ProfilePageStore.get('author-data');
|
||||||
|
|
||||||
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
const isMediaAuthor = authorData && authorData.username === MemberContext._currentValue.username;
|
||||||
|
const isSelectMediaMode = inSelectMediaEmbedMode();
|
||||||
|
|
||||||
// Check if any filters are active (excluding default sort and tags)
|
// Check if any filters are active (excluding default sort and tags)
|
||||||
const hasActiveFilters =
|
const hasActiveFilters =
|
||||||
@ -948,15 +981,16 @@ export class ProfileMediaPage extends Page {
|
|||||||
this.state.author ? (
|
this.state.author ? (
|
||||||
<ProfilePagesContent key="ProfilePagesContent">
|
<ProfilePagesContent key="ProfilePagesContent">
|
||||||
<MediaListWrapper
|
<MediaListWrapper
|
||||||
title={this.state.title}
|
title={isSelectMediaMode ? undefined : this.state.title}
|
||||||
className="items-list-ver"
|
className="items-list-ver"
|
||||||
showBulkActions={isMediaAuthor}
|
style={isSelectMediaMode ? { marginTop: '24px' } : undefined}
|
||||||
|
showBulkActions={!isSelectMediaMode && isMediaAuthor}
|
||||||
selectedCount={this.state.selectedMedia.size}
|
selectedCount={this.state.selectedMedia.size}
|
||||||
totalCount={this.state.availableMediaIds.length}
|
totalCount={this.state.availableMediaIds.length}
|
||||||
onBulkAction={this.handleBulkAction}
|
onBulkAction={this.handleBulkAction}
|
||||||
onSelectAll={this.handleSelectAll}
|
onSelectAll={this.handleSelectAll}
|
||||||
onDeselectAll={this.handleDeselectAll}
|
onDeselectAll={this.handleDeselectAll}
|
||||||
showAddMediaButton={isMediaAuthor}
|
showAddMediaButton={!isSelectMediaMode && isMediaAuthor}
|
||||||
>
|
>
|
||||||
<ProfileMediaFilters
|
<ProfileMediaFilters
|
||||||
hidden={this.state.hiddenFilters}
|
hidden={this.state.hiddenFilters}
|
||||||
@ -979,7 +1013,7 @@ export class ProfileMediaPage extends Page {
|
|||||||
hideViews={!PageStore.get('config-media-item').displayViews}
|
hideViews={!PageStore.get('config-media-item').displayViews}
|
||||||
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
hideDate={!PageStore.get('config-media-item').displayPublishDate}
|
||||||
canEdit={isMediaAuthor}
|
canEdit={isMediaAuthor}
|
||||||
showSelection={isMediaAuthor}
|
showSelection={isMediaAuthor || isSelectMediaMode}
|
||||||
hasAnySelection={this.state.selectedMedia.size > 0}
|
hasAnySelection={this.state.selectedMedia.size > 0}
|
||||||
selectedMedia={this.state.selectedMedia}
|
selectedMedia={this.state.selectedMedia}
|
||||||
onMediaSelection={this.handleMediaSelection}
|
onMediaSelection={this.handleMediaSelection}
|
||||||
|
|||||||
@ -18,3 +18,18 @@ export function inEmbeddedApp() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSelectMediaMode() {
|
||||||
|
try {
|
||||||
|
const params = new URL(globalThis.location.href).searchParams;
|
||||||
|
const action = params.get('action');
|
||||||
|
|
||||||
|
return action === 'select_media';
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function inSelectMediaEmbedMode() {
|
||||||
|
return inEmbeddedApp() && isSelectMediaMode();
|
||||||
|
}
|
||||||
|
|||||||
1745
frontend/yarn.lock
1745
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
302
install-rhel.sh
302
install-rhel.sh
@ -1,302 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# should be run as root on a rhel8-like system
|
|
||||||
|
|
||||||
function update_permissions
|
|
||||||
{
|
|
||||||
# fix permissions of /srv/mediacms directory
|
|
||||||
chown -R nginx:root $1
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "Welcome to the MediacMS installation!";
|
|
||||||
|
|
||||||
if [ `id -u` -ne 0 ]; then
|
|
||||||
echo "Please run as root user"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
read -p "
|
|
||||||
This script will attempt to perform a system update, install required dependencies, and configure PostgreSQL, NGINX, Redis and a few other utilities.
|
|
||||||
It is expected to run on a new system **with no running instances of any these services**. Make sure you check the script before you continue. Then enter y or n
|
|
||||||
" yn
|
|
||||||
case $yn in
|
|
||||||
[Yy]* ) echo "OK!"; break;;
|
|
||||||
[Nn]* ) echo "Have a great day"; exit;;
|
|
||||||
* ) echo "Please answer y or n.";;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# update configuration files
|
|
||||||
|
|
||||||
sed -i 's/\/home\/mediacms\.io\/mediacms\/Bento4-SDK-1-6-0-637\.x86_64-unknown-linux\/bin\/mp4hls/\/srv\/mediacms\/bento4\/bin\/mp4hls/g' cms/settings.py
|
|
||||||
sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g' deploy/local_install/celery_*.service
|
|
||||||
sed -i 's/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.io
|
|
||||||
sed -i 's/\/home\/mediacms\.io\/bin/\/srv\/mediacms\/virtualenv\/bin/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g' deploy/local_install/mediacms.service
|
|
||||||
sed -i 's/\/home\/mediacms\.io\/mediacms/\/var\/log\/mediacms/g' deploy/local_install/mediacms_logrorate
|
|
||||||
sed -i 's/www-data/nginx/g' deploy/local_install/nginx.conf
|
|
||||||
sed -i 's/www-data/nginx/g;s/\/home\/mediacms\.io\/mediacms\/logs/\/var\/log\/mediacms/g;s/\/home\/mediacms\.io\/mediacms/\/srv\/mediacms/g;s/\/home\/mediacms\.io/\/srv\/mediacms\/virtualenv/g' deploy/local_install/uwsgi.ini
|
|
||||||
|
|
||||||
osVersion=
|
|
||||||
|
|
||||||
if [[ -f /etc/os-release ]]; then
|
|
||||||
osVersion=$(grep ^ID /etc/os-release)
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $osVersion == *"fedora"* ]] || [[ $osVersion == *"rhel"* ]] || [[ $osVersion == *"centos"* ]] || [[ *"rocky"* ]]; then
|
|
||||||
dnf install -y epel-release https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-8.noarch.rpm yum-utils
|
|
||||||
yum-config-manager --enable powertools
|
|
||||||
dnf install -y python3-virtualenv python39-devel redis postgresql postgresql-server nginx git gcc vim unzip ImageMagick python3-certbot-nginx certbot wget xz ffmpeg policycoreutils-devel cmake gcc gcc-c++ wget git bsdtar
|
|
||||||
else
|
|
||||||
echo "unsupported or unknown os"
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# fix permissions of /srv/mediacms directory
|
|
||||||
update_permissions /srv/mediacms/
|
|
||||||
|
|
||||||
read -p "Enter portal URL, or press enter for localhost : " FRONTEND_HOST
|
|
||||||
read -p "Enter portal name, or press enter for 'MediaCMS : " PORTAL_NAME
|
|
||||||
|
|
||||||
[ -z "$PORTAL_NAME" ] && PORTAL_NAME='MediaCMS'
|
|
||||||
[ -z "$FRONTEND_HOST" ] && FRONTEND_HOST='localhost'
|
|
||||||
|
|
||||||
echo "Configuring postgres"
|
|
||||||
if [ ! command -v postgresql-setup > /dev/null 2>&1 ]; then
|
|
||||||
echo "Something went wrong, the command 'postgresql-setup' was not found in the system path."
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
postgresql-setup --initdb
|
|
||||||
|
|
||||||
# set authentication method for mediacms user to scram-sha-256
|
|
||||||
sed -i 's/.*password_encryption.*/password_encryption = scram-sha-256/' /var/lib/pgsql/data/postgresql.conf
|
|
||||||
sed -i '/# IPv4 local connections:/a host\tmediacms\tmediacms\t127.0.0.1/32\tscram-sha-256' /var/lib/pgsql/data/pg_hba.conf
|
|
||||||
|
|
||||||
systemctl enable postgresql.service --now
|
|
||||||
|
|
||||||
su -c "psql -c \"CREATE DATABASE mediacms\"" postgres
|
|
||||||
su -c "psql -c \"CREATE USER mediacms WITH ENCRYPTED PASSWORD 'mediacms'\"" postgres
|
|
||||||
su -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE mediacms TO mediacms\"" postgres
|
|
||||||
|
|
||||||
echo 'Creating python virtualenv on /srv/mediacms/virtualenv/'
|
|
||||||
|
|
||||||
mkdir /srv/mediacms/virtualenv/
|
|
||||||
cd /srv/mediacms/virtualenv/
|
|
||||||
virtualenv . --python=python3
|
|
||||||
source /srv/mediacms/virtualenv/bin/activate
|
|
||||||
cd /srv/mediacms/
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
systemctl enable redis.service --now
|
|
||||||
|
|
||||||
SECRET_KEY=`python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`
|
|
||||||
|
|
||||||
# remove http or https prefix
|
|
||||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/http:\/\///g'`
|
|
||||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/https:\/\///g'`
|
|
||||||
|
|
||||||
FRONTEND_HOST_HTTP_PREFIX='http://'$FRONTEND_HOST
|
|
||||||
|
|
||||||
echo 'FRONTEND_HOST='\'"$FRONTEND_HOST_HTTP_PREFIX"\' >> cms/local_settings.py
|
|
||||||
echo 'PORTAL_NAME='\'"$PORTAL_NAME"\' >> cms/local_settings.py
|
|
||||||
echo "SSL_FRONTEND_HOST = FRONTEND_HOST.replace('http', 'https')" >> cms/local_settings.py
|
|
||||||
|
|
||||||
echo 'SECRET_KEY='\'"$SECRET_KEY"\' >> cms/local_settings.py
|
|
||||||
echo "LOCAL_INSTALL = True" >> cms/local_settings.py
|
|
||||||
|
|
||||||
mkdir /var/log/mediacms/
|
|
||||||
mkdir pids
|
|
||||||
|
|
||||||
update_permissions /var/log/mediacms/
|
|
||||||
|
|
||||||
python manage.py migrate
|
|
||||||
python manage.py loaddata fixtures/encoding_profiles.json
|
|
||||||
python manage.py loaddata fixtures/categories.json
|
|
||||||
python manage.py collectstatic --noinput
|
|
||||||
|
|
||||||
ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"`
|
|
||||||
echo "from users.models import User; User.objects.create_superuser('admin', 'admin@example.com', '$ADMIN_PASS')" | python manage.py shell
|
|
||||||
|
|
||||||
echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
|
|
||||||
|
|
||||||
update_permissions /srv/mediacms/
|
|
||||||
|
|
||||||
cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service
|
|
||||||
cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service
|
|
||||||
cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service
|
|
||||||
cp deploy/local_install/mediacms.service /etc/systemd/system/mediacms.service
|
|
||||||
|
|
||||||
mkdir -p /etc/letsencrypt/live/$FRONTEND_HOST
|
|
||||||
mkdir -p /etc/nginx/sites-enabled
|
|
||||||
mkdir -p /etc/nginx/sites-available
|
|
||||||
mkdir -p /etc/nginx/dhparams/
|
|
||||||
rm -rf /etc/nginx/conf.d/default.conf
|
|
||||||
rm -rf /etc/nginx/sites-enabled/default
|
|
||||||
cp deploy/local_install/mediacms.io_fullchain.pem /etc/letsencrypt/live/$FRONTEND_HOST/fullchain.pem
|
|
||||||
cp deploy/local_install/mediacms.io_privkey.pem /etc/letsencrypt/live/$FRONTEND_HOST/privkey.pem
|
|
||||||
cp deploy/local_install/mediacms.io /etc/nginx/sites-available/mediacms.io
|
|
||||||
ln -s /etc/nginx/sites-available/mediacms.io /etc/nginx/sites-enabled/mediacms.io
|
|
||||||
cp deploy/local_install/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
|
|
||||||
cp deploy/local_install/nginx.conf /etc/nginx/
|
|
||||||
|
|
||||||
# attempt to get a valid certificate for specified domain
|
|
||||||
while true ; do
|
|
||||||
echo "Would you like to run [c]ertbot, or [s]kip?"
|
|
||||||
read -p " : " certbotConfig
|
|
||||||
|
|
||||||
case $certbotConfig in
|
|
||||||
[cC*] )
|
|
||||||
if [ "$FRONTEND_HOST" != "localhost" ]; then
|
|
||||||
systemctl start
|
|
||||||
echo 'attempt to get a valid certificate for specified url $FRONTEND_HOST'
|
|
||||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
|
||||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
|
||||||
# unfortunately for some reason it needs to be run two times in order to create the entries
|
|
||||||
# and directory structure!!!
|
|
||||||
systemctl stop nginx
|
|
||||||
|
|
||||||
# Generate individual DH params
|
|
||||||
openssl dhparam -out /etc/nginx/dhparams/dhparams.pem 4096
|
|
||||||
fi
|
|
||||||
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
[sS*] )
|
|
||||||
echo "will not call certbot utility to update ssl certificate for url 'localhost', using default ssl certificate"
|
|
||||||
cp deploy/local_install/dhparams.pem /etc/nginx/dhparams/dhparams.pem
|
|
||||||
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
echo "Unknown option: $certbotConfig"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# configure bento4 utility installation, for HLS
|
|
||||||
while true ; do
|
|
||||||
echo "Configuring Bento4"
|
|
||||||
echo "Would you like to [d]ownload a pre-compiled bento4 binary, or [b]uild it now?"
|
|
||||||
read -p "b/d : " bentoConfig
|
|
||||||
|
|
||||||
case $bentoConfig in
|
|
||||||
[bB*] )
|
|
||||||
echo "Building bento4 from source"
|
|
||||||
git clone -b v1.6.0-640 https://github.com/axiomatic-systems/Bento4 /srv/mediacms/bento4
|
|
||||||
cd /srv/mediacms/bento4/
|
|
||||||
mkdir bin
|
|
||||||
cd /srv/mediacms/bento4/bin/
|
|
||||||
cmake -DCMAKE_BUILD_TYPE=Release ..
|
|
||||||
make -j$(nproc)
|
|
||||||
|
|
||||||
chmod +x ../Source/Python/utils/mp4-hls.py
|
|
||||||
|
|
||||||
echo -e '#!/bin/bash' >> mp4hls
|
|
||||||
echo -e 'BASEDIR=$(pwd)' >> mp4hls
|
|
||||||
echo -e 'exec python3 "$BASEDIR/../Source/Python/utils/mp4-hls.py"' >> mp4hls
|
|
||||||
|
|
||||||
chmod +x mp4hls
|
|
||||||
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
[dD*] )
|
|
||||||
cd /srv/mediacms/
|
|
||||||
wget http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
|
||||||
bsdtar -xf Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip -s '/Bento4-SDK-1-6-0-637.x86_64-unknown-linux/bento4/'
|
|
||||||
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
echo "Unknown option: $bentoConfig"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
mkdir /srv/mediacms/media_files/hls
|
|
||||||
|
|
||||||
# update permissions
|
|
||||||
|
|
||||||
update_permissions /srv/mediacms/
|
|
||||||
|
|
||||||
# configure selinux
|
|
||||||
|
|
||||||
while true ; do
|
|
||||||
echo "Configuring SELinux"
|
|
||||||
echo "Would you like to [d]isable SELinux until next reboot, [c]onfigure our SELinux module, or [s]kip and not do any SELinux confgiguration?"
|
|
||||||
read -p "d/c/s : " seConfig
|
|
||||||
|
|
||||||
case $seConfig in
|
|
||||||
[Dd]* )
|
|
||||||
echo "Disabling SELinux until next reboot"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
[Cc]* )
|
|
||||||
echo "Configuring custom mediacms selinux module"
|
|
||||||
|
|
||||||
semanage fcontext -a -t bin_t /srv/mediacms/virtualenv/bin/
|
|
||||||
semanage fcontext -a -t httpd_sys_content_t "/srv/mediacms(/.*)?"
|
|
||||||
restorecon -FRv /srv/mediacms/
|
|
||||||
|
|
||||||
sebools=(httpd_can_network_connect httpd_graceful_shutdown httpd_can_network_relay nis_enabled httpd_setrlimit domain_can_mmap_files)
|
|
||||||
|
|
||||||
for bool in "${sebools[@]}"
|
|
||||||
do
|
|
||||||
setsebool -P $bool 1
|
|
||||||
done
|
|
||||||
|
|
||||||
cd /srv/mediacms/deploy/local_install/
|
|
||||||
make -f /usr/share/selinux/devel/Makefile selinux-mediacms.pp
|
|
||||||
semodule -i selinux-mediacms.pp
|
|
||||||
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
[Ss]* )
|
|
||||||
echo "Skipping SELinux configuration"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
echo "Unknown option: $seConfig"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
# configure firewall
|
|
||||||
if command -v firewall-cmd > /dev/null 2>&1 ; then
|
|
||||||
while true ; do
|
|
||||||
echo "Configuring firewall"
|
|
||||||
echo "Would you like to configure http, https, or skip and not do any firewall configuration?"
|
|
||||||
read -p "http/https/skip : " fwConfig
|
|
||||||
|
|
||||||
case $fwConfig in
|
|
||||||
http )
|
|
||||||
echo "Opening port 80 until next reboot"
|
|
||||||
firewall-cmd --add-port=80/tcp
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
https )
|
|
||||||
echo "Opening port 443 permanently"
|
|
||||||
firewall-cmd --add-port=443/tcp --permanent
|
|
||||||
firewall-cmd --reload
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
skip )
|
|
||||||
echo "Skipping firewall configuration"
|
|
||||||
break
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
echo "Unknown option: $fwConfig"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
systemctl daemon-reload
|
|
||||||
systemctl start celery_long.service
|
|
||||||
systemctl start celery_short.service
|
|
||||||
systemctl start celery_beat.service
|
|
||||||
systemctl start mediacms.service
|
|
||||||
systemctl start nginx.service
|
|
||||||
|
|
||||||
echo 'MediaCMS installation completed, open browser on http://'"$FRONTEND_HOST"' and login with user admin and password '"$ADMIN_PASS"''
|
|
||||||
140
install.sh
140
install.sh
@ -1,140 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# should be run as root and only on Ubuntu 20/22, Debian 10/11 (Buster/Bullseye) versions!
|
|
||||||
echo "Welcome to the MediacMS installation!";
|
|
||||||
|
|
||||||
if [ `id -u` -ne 0 ]
|
|
||||||
then echo "Please run as root"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
while true; do
|
|
||||||
read -p "
|
|
||||||
This script will attempt to perform a system update and install services including PostgreSQL, nginx and Django.
|
|
||||||
It is expected to run on a new system **with no running instances of any these services**.
|
|
||||||
This has been tested only in Ubuntu Linux 22 and 24. Make sure you check the script before you continue. Then enter yes or no
|
|
||||||
" yn
|
|
||||||
case $yn in
|
|
||||||
[Yy]* ) echo "OK!"; break;;
|
|
||||||
[Nn]* ) echo "Have a great day"; exit;;
|
|
||||||
* ) echo "Please answer yes or no.";;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
apt-get update && apt-get -y upgrade && apt-get install pkg-config python3-venv python3-dev virtualenv redis-server postgresql nginx git gcc vim unzip imagemagick procps libxml2-dev libxmlsec1-dev libxmlsec1-openssl python3-certbot-nginx certbot wget xz-utils -y
|
|
||||||
|
|
||||||
# install ffmpeg
|
|
||||||
echo "Downloading and installing ffmpeg"
|
|
||||||
wget -q https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz
|
|
||||||
mkdir -p tmp
|
|
||||||
tar -xf ffmpeg-release-amd64-static.tar.xz --strip-components 1 -C tmp
|
|
||||||
cp -v tmp/{ffmpeg,ffprobe,qt-faststart} /usr/local/bin
|
|
||||||
rm -rf tmp ffmpeg-release-amd64-static.tar.xz
|
|
||||||
echo "ffmpeg installed to /usr/local/bin"
|
|
||||||
|
|
||||||
read -p "Enter portal URL, or press enter for localhost : " FRONTEND_HOST
|
|
||||||
read -p "Enter portal name, or press enter for 'MediaCMS : " PORTAL_NAME
|
|
||||||
|
|
||||||
[ -z "$PORTAL_NAME" ] && PORTAL_NAME='MediaCMS'
|
|
||||||
[ -z "$FRONTEND_HOST" ] && FRONTEND_HOST='localhost'
|
|
||||||
|
|
||||||
echo 'Creating database to be used in MediaCMS'
|
|
||||||
|
|
||||||
su -c "psql -c \"CREATE DATABASE mediacms\"" postgres
|
|
||||||
su -c "psql -c \"CREATE USER mediacms WITH ENCRYPTED PASSWORD 'mediacms'\"" postgres
|
|
||||||
su -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE mediacms TO mediacms\"" postgres
|
|
||||||
su -c "psql -d mediacms -c \"GRANT CREATE, USAGE ON SCHEMA public TO mediacms\"" postgres
|
|
||||||
|
|
||||||
echo 'Creating python virtualenv on /home/mediacms.io'
|
|
||||||
|
|
||||||
cd /home/mediacms.io
|
|
||||||
virtualenv . --python=python3
|
|
||||||
source /home/mediacms.io/bin/activate
|
|
||||||
cd mediacms
|
|
||||||
pip install --no-binary lxml,xmlsec -r requirements.txt
|
|
||||||
|
|
||||||
SECRET_KEY=`python -c 'from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())'`
|
|
||||||
|
|
||||||
# remove http or https prefix
|
|
||||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/http:\/\///g'`
|
|
||||||
FRONTEND_HOST=`echo "$FRONTEND_HOST" | sed -r 's/https:\/\///g'`
|
|
||||||
|
|
||||||
sed -i s/localhost/$FRONTEND_HOST/g deploy/local_install/mediacms.io
|
|
||||||
|
|
||||||
FRONTEND_HOST_HTTP_PREFIX='http://'$FRONTEND_HOST
|
|
||||||
|
|
||||||
echo 'FRONTEND_HOST='\'"$FRONTEND_HOST_HTTP_PREFIX"\' >> cms/local_settings.py
|
|
||||||
echo 'PORTAL_NAME='\'"$PORTAL_NAME"\' >> cms/local_settings.py
|
|
||||||
echo "SSL_FRONTEND_HOST = FRONTEND_HOST.replace('http', 'https')" >> cms/local_settings.py
|
|
||||||
|
|
||||||
echo 'SECRET_KEY='\'"$SECRET_KEY"\' >> cms/local_settings.py
|
|
||||||
echo "LOCAL_INSTALL = True" >> cms/local_settings.py
|
|
||||||
|
|
||||||
mkdir logs
|
|
||||||
mkdir pids
|
|
||||||
python manage.py migrate
|
|
||||||
python manage.py loaddata fixtures/encoding_profiles.json
|
|
||||||
python manage.py loaddata fixtures/categories.json
|
|
||||||
python manage.py collectstatic --noinput
|
|
||||||
|
|
||||||
ADMIN_PASS=`python -c "import secrets;chars = 'abcdefghijklmnopqrstuvwxyz0123456789';print(''.join(secrets.choice(chars) for i in range(10)))"`
|
|
||||||
echo "from users.models import User; User.objects.create_superuser('admin', 'admin@example.com', '$ADMIN_PASS')" | python manage.py shell
|
|
||||||
|
|
||||||
echo "from django.contrib.sites.models import Site; Site.objects.update(name='$FRONTEND_HOST', domain='$FRONTEND_HOST')" | python manage.py shell
|
|
||||||
|
|
||||||
chown -R www-data. /home/mediacms.io/
|
|
||||||
cp deploy/local_install/celery_long.service /etc/systemd/system/celery_long.service && systemctl enable celery_long && systemctl start celery_long
|
|
||||||
cp deploy/local_install/celery_short.service /etc/systemd/system/celery_short.service && systemctl enable celery_short && systemctl start celery_short
|
|
||||||
cp deploy/local_install/celery_beat.service /etc/systemd/system/celery_beat.service && systemctl enable celery_beat &&systemctl start celery_beat
|
|
||||||
cp deploy/local_install/mediacms.service /etc/systemd/system/mediacms.service && systemctl enable mediacms.service && systemctl start mediacms.service
|
|
||||||
|
|
||||||
mkdir -p /etc/letsencrypt/live/mediacms.io/
|
|
||||||
mkdir -p /etc/letsencrypt/live/$FRONTEND_HOST
|
|
||||||
mkdir -p /etc/nginx/sites-enabled
|
|
||||||
mkdir -p /etc/nginx/sites-available
|
|
||||||
mkdir -p /etc/nginx/dhparams/
|
|
||||||
rm -rf /etc/nginx/conf.d/default.conf
|
|
||||||
rm -rf /etc/nginx/sites-enabled/default
|
|
||||||
cp deploy/local_install/mediacms.io_fullchain.pem /etc/letsencrypt/live/$FRONTEND_HOST/fullchain.pem
|
|
||||||
cp deploy/local_install/mediacms.io_privkey.pem /etc/letsencrypt/live/$FRONTEND_HOST/privkey.pem
|
|
||||||
cp deploy/local_install/dhparams.pem /etc/nginx/dhparams/dhparams.pem
|
|
||||||
cp deploy/local_install/mediacms.io /etc/nginx/sites-available/mediacms.io
|
|
||||||
ln -s /etc/nginx/sites-available/mediacms.io /etc/nginx/sites-enabled/mediacms.io
|
|
||||||
cp deploy/local_install/uwsgi_params /etc/nginx/sites-enabled/uwsgi_params
|
|
||||||
cp deploy/local_install/nginx.conf /etc/nginx/
|
|
||||||
systemctl stop nginx
|
|
||||||
systemctl start nginx
|
|
||||||
|
|
||||||
# attempt to get a valid certificate for specified domain
|
|
||||||
|
|
||||||
if [ "$FRONTEND_HOST" != "localhost" ]; then
|
|
||||||
echo 'attempt to get a valid certificate for specified url $FRONTEND_HOST'
|
|
||||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
|
||||||
certbot --nginx -n --agree-tos --register-unsafely-without-email -d $FRONTEND_HOST
|
|
||||||
# unfortunately for some reason it needs to be run two times in order to create the entries
|
|
||||||
# and directory structure!!!
|
|
||||||
systemctl restart nginx
|
|
||||||
else
|
|
||||||
echo "will not call certbot utility to update ssl certificate for url 'localhost', using default ssl certificate"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Generate individual DH params
|
|
||||||
if [ "$FRONTEND_HOST" != "localhost" ]; then
|
|
||||||
# Only generate new DH params when using "real" certificates.
|
|
||||||
openssl dhparam -out /etc/nginx/dhparams/dhparams.pem 4096
|
|
||||||
systemctl restart nginx
|
|
||||||
else
|
|
||||||
echo "will not generate new DH params for url 'localhost', using default DH params"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Bento4 utility installation, for HLS
|
|
||||||
|
|
||||||
cd /home/mediacms.io/mediacms
|
|
||||||
wget http://zebulon.bok.net/Bento4/binaries/Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
|
||||||
unzip Bento4-SDK-1-6-0-637.x86_64-unknown-linux.zip
|
|
||||||
mkdir /home/mediacms.io/mediacms/media_files/hls
|
|
||||||
|
|
||||||
# last, set default owner
|
|
||||||
chown -R www-data. /home/mediacms.io/
|
|
||||||
|
|
||||||
echo 'MediaCMS installation completed, open browser on http://'"$FRONTEND_HOST"' and login with user admin and password '"$ADMIN_PASS"''
|
|
||||||
73
lms-plugins/mediacms-moodle/README.md
Normal file
73
lms-plugins/mediacms-moodle/README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
# MediaCMS for Moodle
|
||||||
|
|
||||||
|
This package provides the integration between MediaCMS and Moodle (versions 4.x and 5.x).
|
||||||
|
It consists of two separate plugins that work together to provide a seamless video experience:
|
||||||
|
|
||||||
|
1. **Filter Plugin (filter_mediacms):**
|
||||||
|
* **Purpose:** Handles the display of videos using secure LTI 1.3 launches and provides "Auto-convert" to turn URLs into players.
|
||||||
|
* **Location:** `filter/mediacms`
|
||||||
|
|
||||||
|
2. **Editor Plugin (tiny_mediacms):**
|
||||||
|
* **Purpose:** Adds a "Insert MediaCMS Media" button to the TinyMCE editor, allowing users to select videos from the MediaCMS library or paste URLs.
|
||||||
|
* **Location:** `lib/editor/tiny/plugins/mediacms`
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
This package is distributed as a single repository but contains two distinct Moodle plugins that must be installed in their respective directories.
|
||||||
|
|
||||||
|
### 1. Copy Files
|
||||||
|
|
||||||
|
Copy the directories into your Moodle installation as follows (example assuming Moodle is at `/var/www/moodle/public`):
|
||||||
|
|
||||||
|
* Copy `filter/mediacms` to `/var/www/moodle/public/filter/mediacms`.
|
||||||
|
* Copy `tiny/mediacms` to `/var/www/moodle/public/lib/editor/tiny/plugins/mediacms`.
|
||||||
|
|
||||||
|
### 2. Set Permissions
|
||||||
|
|
||||||
|
Ensure the web server user (typically `www-data`) has ownership of the new directories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Example for Ubuntu/Debian systems
|
||||||
|
chown -R www-data:www-data /var/www/moodle/public/filter/mediacms
|
||||||
|
chown -R www-data:www-data /var/www/moodle/public/lib/editor/tiny/plugins/mediacms
|
||||||
|
chmod -R 755 /var/www/moodle/public/filter/mediacms
|
||||||
|
chmod -R 755 /var/www/moodle/public/lib/editor/tiny/plugins/mediacms
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Install Plugins
|
||||||
|
|
||||||
|
1. Log in to Moodle as an Administrator.
|
||||||
|
2. Go to **Site administration > Notifications**.
|
||||||
|
3. Follow the prompts to upgrade the database and install the new plugins.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
You must configure both plugins to fully enable the integration.
|
||||||
|
|
||||||
|
### Filter Configuration
|
||||||
|
1. Go to **Site administration > Plugins > Filters > Manage filters**.
|
||||||
|
2. Enable **MediaCMS** (set it to "On").
|
||||||
|
3. Click **Settings** next to MediaCMS.
|
||||||
|
4. **MediaCMS URL:** Enter the base URL of your MediaCMS instance (e.g., `https://lti.mediacms.io`).
|
||||||
|
5. **LTI Tool:** Select the External Tool configuration that corresponds to MediaCMS.
|
||||||
|
* *Note:* You must first create an LTI 1.3 External Tool in *Site administration > Plugins > Activity modules > External tool > Manage tools*.
|
||||||
|
6. **Auto-convert:** Check "Enable auto-convert" if you want plain text URLs (e.g., `https://video.example.com/view?m=xyz`) to automatically become video players.
|
||||||
|
|
||||||
|
### Editor Configuration (TinyMCE)
|
||||||
|
1. Go to **Site administration > Plugins > Text editors > TinyMCE editor > MediaCMS settings**.
|
||||||
|
2. **LTI Tool:** Select the same Tool configured for the Filter to enable the "Video Library" picker button.
|
||||||
|
3. **Auto-convert:** (Implicitly enabled) Pasting MediaCMS URLs into the editor will automatically convert them to placeholders.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### For Teachers (Editor)
|
||||||
|
|
||||||
|
1. In any text editor (TinyMCE), click the **MediaCMS** icon (or "Insert MediaCMS Media" from the Insert menu).
|
||||||
|
2. You can:
|
||||||
|
* **Paste a URL:** Paste a View or Embed URL.
|
||||||
|
* **Video Library:** Click the "Video Library" tab to browse and select videos (requires LTI Deep Linking configuration).
|
||||||
|
3. The video will appear as a placeholder or iframe in the editor.
|
||||||
|
|
||||||
|
### For Students (Display)
|
||||||
|
|
||||||
|
When content is viewed, the Filter will ensure the video is loaded securely via LTI 1.3, authenticating the user with MediaCMS automatically.
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
namespace filter_mediacms\privacy;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
class provider implements \core_privacy\local\metadata\null_provider {
|
||||||
|
public static function get_reason(): string {
|
||||||
|
return 'privacy:metadata';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace filter_mediacms;
|
||||||
|
|
||||||
|
use moodle_url;
|
||||||
|
use html_writer;
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MediaCMS text filter.
|
||||||
|
*
|
||||||
|
* @package filter_mediacms
|
||||||
|
* @copyright 2026 MediaCMS
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
class text_filter extends \core_filters\text_filter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter method.
|
||||||
|
*
|
||||||
|
* @param string $text The text to filter.
|
||||||
|
* @param array $options Filter options.
|
||||||
|
* @return string The filtered text.
|
||||||
|
*/
|
||||||
|
public function filter($text, array $options = array()) {
|
||||||
|
if (!is_string($text) or empty($text)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mediacmsurl = get_config('filter_mediacms', 'mediacmsurl');
|
||||||
|
if (empty($mediacmsurl)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$newtext = $text;
|
||||||
|
|
||||||
|
// 1. Handle [mediacms:TOKEN] tag
|
||||||
|
$pattern_tag = '/\[mediacms:([a-zA-Z0-9]+)\]/';
|
||||||
|
$newtext = preg_replace_callback($pattern_tag, [$this, 'callback_tag'], $newtext);
|
||||||
|
|
||||||
|
// 2. Handle Auto-convert URLs if enabled
|
||||||
|
if (get_config('filter_mediacms', 'enableautoconvert')) {
|
||||||
|
// Regex for MediaCMS view URLs: https://domain/view?m=TOKEN
|
||||||
|
// We need to be careful to match the configured domain
|
||||||
|
$parsed_url = parse_url($mediacmsurl);
|
||||||
|
$host = preg_quote($parsed_url['host'] ?? '', '/');
|
||||||
|
$scheme = preg_quote($parsed_url['scheme'] ?? 'https', '/');
|
||||||
|
|
||||||
|
// Allow http or https, and optional path prefix
|
||||||
|
$path_prefix = preg_quote(rtrim($parsed_url['path'] ?? '', '/'), '/');
|
||||||
|
|
||||||
|
// Pattern: https://HOST/PREFIX/view?m=TOKEN
|
||||||
|
// Also handle /embed?m=TOKEN
|
||||||
|
$pattern_url = '/(' . $scheme . ':\/\/' . $host . $path_prefix . '\/(view|embed)\?m=([a-zA-Z0-9]+)(?:&[^\s<]*)?)/';
|
||||||
|
|
||||||
|
$newtext = preg_replace_callback($pattern_url, [$this, 'callback_url'], $newtext);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $newtext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for [mediacms:TOKEN]
|
||||||
|
*/
|
||||||
|
public function callback_tag($matches) {
|
||||||
|
return $this->generate_iframe($matches[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for URLs
|
||||||
|
*/
|
||||||
|
public function callback_url($matches) {
|
||||||
|
// matches[1] is full URL, matches[3] is token
|
||||||
|
$token = $matches[3];
|
||||||
|
return $this->generate_iframe($token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the Iframe pointing to launch.php
|
||||||
|
*/
|
||||||
|
private function generate_iframe($token) {
|
||||||
|
global $CFG, $COURSE;
|
||||||
|
|
||||||
|
$width = get_config('filter_mediacms', 'iframewidth') ?: 960;
|
||||||
|
$height = get_config('filter_mediacms', 'iframeheight') ?: 540;
|
||||||
|
$courseid = $COURSE->id ?? 0;
|
||||||
|
|
||||||
|
$launchurl = new moodle_url('/filter/mediacms/launch.php', [
|
||||||
|
'token' => $token,
|
||||||
|
'courseid' => $courseid,
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height
|
||||||
|
]);
|
||||||
|
|
||||||
|
$iframe = html_writer::tag('iframe', '', [
|
||||||
|
'src' => $launchurl->out(false),
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height,
|
||||||
|
'frameborder' => 0,
|
||||||
|
'allowfullscreen' => 'allowfullscreen',
|
||||||
|
'class' => 'mediacms-embed',
|
||||||
|
'title' => 'MediaCMS Video'
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $iframe;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
lms-plugins/mediacms-moodle/filter/mediacms/db/install.php
Normal file
41
lms-plugins/mediacms-moodle/filter/mediacms/db/install.php
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post-installation hook.
|
||||||
|
*/
|
||||||
|
function xmldb_filter_mediacms_install() {
|
||||||
|
global $CFG, $DB;
|
||||||
|
require_once($CFG->libdir . '/filterlib.php');
|
||||||
|
|
||||||
|
// 1. Enable the filter globally.
|
||||||
|
filter_set_global_state('filter_mediacms', TEXTFILTER_ON);
|
||||||
|
|
||||||
|
// 2. Move to top priority (lowest sortorder).
|
||||||
|
// Get all global active filters.
|
||||||
|
$filters = $DB->get_records('filter_active', ['contextid' => SYSCONTEXTID], 'sortorder ASC', 'filter, id, sortorder');
|
||||||
|
|
||||||
|
// If we are already the only one or something failed, stop.
|
||||||
|
if (empty($filters)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the new order: mediacms first, then everyone else (excluding mediacms if present).
|
||||||
|
$sortedfilters = ['filter_mediacms'];
|
||||||
|
foreach ($filters as $filtername => $record) {
|
||||||
|
if ($filtername !== 'filter_mediacms') {
|
||||||
|
$sortedfilters[] = $filtername;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write back the new sort orders.
|
||||||
|
$sortorder = 1;
|
||||||
|
foreach ($sortedfilters as $filtername) {
|
||||||
|
if ($record = $DB->get_record('filter_active', ['filter' => $filtername, 'contextid' => SYSCONTEXTID])) {
|
||||||
|
$record->sortorder = $sortorder;
|
||||||
|
$DB->update_record('filter_active', $record);
|
||||||
|
$sortorder++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
$string['filtername'] = 'MediaCMS';
|
||||||
|
$string['pluginname'] = 'MediaCMS';
|
||||||
|
$string['mediacmsurl'] = 'MediaCMS URL';
|
||||||
|
$string['mediacmsurl_desc'] = 'The base URL of your MediaCMS instance (e.g., https://lti.mediacms.io).';
|
||||||
|
$string['ltitoolid'] = 'LTI Tool';
|
||||||
|
$string['ltitoolid_desc'] = 'Select the External Tool configuration for MediaCMS. If "Auto-detect" is selected, the plugin will try to find a tool matching the MediaCMS URL.';
|
||||||
|
$string['noltitoolsfound'] = 'No LTI tools found';
|
||||||
|
$string['iframewidth'] = 'Default Width';
|
||||||
|
$string['iframewidth_desc'] = 'Default width for embedded videos (pixels).';
|
||||||
|
$string['iframeheight'] = 'Default Height';
|
||||||
|
$string['iframeheight_desc'] = 'Default height for embedded videos (pixels).';
|
||||||
|
$string['enableautoconvert'] = 'Auto-convert URLs';
|
||||||
|
$string['enableautoconvert_desc'] = 'Automatically convert MediaCMS URLs (e.g., /view?m=xyz) in text to embedded players.';
|
||||||
|
$string['privacy:metadata'] = 'The MediaCMS filter does not store any personal data.';
|
||||||
101
lms-plugins/mediacms-moodle/filter/mediacms/launch.php
Normal file
101
lms-plugins/mediacms-moodle/filter/mediacms/launch.php
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* LTI Launch for MediaCMS Filter
|
||||||
|
*
|
||||||
|
* @package filter_mediacms
|
||||||
|
* @copyright 2026 MediaCMS
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
require_once(__DIR__ . '/../../config.php');
|
||||||
|
require_once($CFG->dirroot . '/mod/lti/lib.php');
|
||||||
|
require_once($CFG->dirroot . '/mod/lti/locallib.php');
|
||||||
|
|
||||||
|
global $SITE, $DB, $PAGE, $OUTPUT, $CFG;
|
||||||
|
|
||||||
|
require_login();
|
||||||
|
|
||||||
|
$mediatoken = required_param('token', PARAM_ALPHANUMEXT);
|
||||||
|
$courseid = optional_param('courseid', 0, PARAM_INT);
|
||||||
|
$height = optional_param('height', 0, PARAM_INT);
|
||||||
|
$width = optional_param('width', 0, PARAM_INT);
|
||||||
|
|
||||||
|
// Get configuration
|
||||||
|
$mediacmsurl = get_config('filter_mediacms', 'mediacmsurl');
|
||||||
|
$ltitoolid = get_config('filter_mediacms', 'ltitoolid');
|
||||||
|
$defaultwidth = get_config('filter_mediacms', 'iframewidth') ?: 960;
|
||||||
|
$defaultheight = get_config('filter_mediacms', 'iframeheight') ?: 540;
|
||||||
|
|
||||||
|
if (empty($width)) {
|
||||||
|
$width = $defaultwidth;
|
||||||
|
}
|
||||||
|
if (empty($height)) {
|
||||||
|
$height = $defaultheight;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($mediacmsurl)) {
|
||||||
|
die('MediaCMS URL not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tool Selection Logic
|
||||||
|
$type = false;
|
||||||
|
if (!empty($ltitoolid)) {
|
||||||
|
$type = $DB->get_record('lti_types', ['id' => $ltitoolid]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$type) {
|
||||||
|
die('LTI tool not found or not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up context
|
||||||
|
if ($courseid && $courseid != SITEID) {
|
||||||
|
$context = context_course::instance($courseid);
|
||||||
|
$course = get_course($courseid);
|
||||||
|
} else {
|
||||||
|
$context = context_system::instance();
|
||||||
|
$course = $SITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up page
|
||||||
|
$PAGE->set_url(new moodle_url('/filter/mediacms/launch.php', [
|
||||||
|
'token' => $mediatoken,
|
||||||
|
'courseid' => $courseid,
|
||||||
|
'width' => $width,
|
||||||
|
'height' => $height
|
||||||
|
]));
|
||||||
|
$PAGE->set_context($context);
|
||||||
|
$PAGE->set_pagelayout('embedded');
|
||||||
|
$PAGE->set_title('MediaCMS');
|
||||||
|
|
||||||
|
// Create a dummy LTI instance object
|
||||||
|
$instance = new stdClass();
|
||||||
|
$instance->id = 0;
|
||||||
|
$instance->course = $course->id;
|
||||||
|
$instance->typeid = $type->id;
|
||||||
|
$instance->name = 'MediaCMS Video';
|
||||||
|
$instance->instructorchoiceacceptgrades = 0;
|
||||||
|
$instance->grade = 0;
|
||||||
|
$instance->instructorchoicesendname = 1;
|
||||||
|
$instance->instructorchoicesendemailaddr = 1;
|
||||||
|
$instance->launchcontainer = LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
|
||||||
|
|
||||||
|
// Pass the token in custom parameters
|
||||||
|
// MediaCMS expects 'media_friendly_token' to identify the video
|
||||||
|
$instance->instructorcustomparameters = "media_friendly_token=" . $mediatoken;
|
||||||
|
|
||||||
|
// Get type config
|
||||||
|
$typeconfig = lti_get_type_type_config($type->id);
|
||||||
|
|
||||||
|
// Initiate LTI Login
|
||||||
|
$content = lti_initiate_login($course->id, 0, $instance, $typeconfig, null, 'MediaCMS Video');
|
||||||
|
|
||||||
|
// Inject media_token as a hidden field for OIDC flow state if needed
|
||||||
|
// This ensures the token survives the OIDC roundtrip if the provider supports it
|
||||||
|
// Standard LTI 1.3 passes it via Custom Claims (instructorcustomparameters) which is handled above.
|
||||||
|
// However, the original plugin also injected it into the form. We'll keep it for safety.
|
||||||
|
$hidden_field = '<input type="hidden" name="media_token" value="' . htmlspecialchars($mediatoken, ENT_QUOTES) . '" />';
|
||||||
|
$content = str_replace('</form>', $hidden_field . '</form>', $content);
|
||||||
|
|
||||||
|
echo $OUTPUT->header();
|
||||||
|
echo $content;
|
||||||
|
echo $OUTPUT->footer();
|
||||||
60
lms-plugins/mediacms-moodle/filter/mediacms/settings.php
Normal file
60
lms-plugins/mediacms-moodle/filter/mediacms/settings.php
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
defined('MOODLE_INTERNAL') || die;
|
||||||
|
|
||||||
|
if ($ADMIN->fulltree) {
|
||||||
|
// MediaCMS URL
|
||||||
|
$settings->add(new admin_setting_configtext(
|
||||||
|
'filter_mediacms/mediacmsurl',
|
||||||
|
get_string('mediacmsurl', 'filter_mediacms'),
|
||||||
|
get_string('mediacmsurl_desc', 'filter_mediacms'),
|
||||||
|
'https://lti.mediacms.io',
|
||||||
|
PARAM_URL
|
||||||
|
));
|
||||||
|
|
||||||
|
// LTI Tool Selector
|
||||||
|
$ltioptions = [0 => get_string('noltitoolsfound', 'filter_mediacms')];
|
||||||
|
try {
|
||||||
|
$tools = $DB->get_records('lti_types', null, 'name ASC', 'id, name, baseurl');
|
||||||
|
if (!empty($tools)) {
|
||||||
|
$ltioptions = [0 => get_string('choose')];
|
||||||
|
foreach ($tools as $tool) {
|
||||||
|
$ltioptions[$tool->id] = $tool->name . ' (' . $tool->baseurl . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
// Database might not be ready during install
|
||||||
|
}
|
||||||
|
|
||||||
|
$settings->add(new admin_setting_configselect(
|
||||||
|
'filter_mediacms/ltitoolid',
|
||||||
|
get_string('ltitoolid', 'filter_mediacms'),
|
||||||
|
get_string('ltitoolid_desc', 'filter_mediacms'),
|
||||||
|
0,
|
||||||
|
$ltioptions
|
||||||
|
));
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
$settings->add(new admin_setting_configtext(
|
||||||
|
'filter_mediacms/iframewidth',
|
||||||
|
get_string('iframewidth', 'filter_mediacms'),
|
||||||
|
get_string('iframewidth_desc', 'filter_mediacms'),
|
||||||
|
'960',
|
||||||
|
PARAM_INT
|
||||||
|
));
|
||||||
|
|
||||||
|
$settings->add(new admin_setting_configtext(
|
||||||
|
'filter_mediacms/iframeheight',
|
||||||
|
get_string('iframeheight', 'filter_mediacms'),
|
||||||
|
get_string('iframeheight_desc', 'filter_mediacms'),
|
||||||
|
'540',
|
||||||
|
PARAM_INT
|
||||||
|
));
|
||||||
|
|
||||||
|
// Auto-convert
|
||||||
|
$settings->add(new admin_setting_configcheckbox(
|
||||||
|
'filter_mediacms/enableautoconvert',
|
||||||
|
get_string('enableautoconvert', 'filter_mediacms'),
|
||||||
|
get_string('enableautoconvert_desc', 'filter_mediacms'),
|
||||||
|
1
|
||||||
|
));
|
||||||
|
}
|
||||||
8
lms-plugins/mediacms-moodle/filter/mediacms/version.php
Normal file
8
lms-plugins/mediacms-moodle/filter/mediacms/version.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
defined('MOODLE_INTERNAL') || die();
|
||||||
|
|
||||||
|
$plugin->version = 2026020100;
|
||||||
|
$plugin->requires = 2024100700; // Requires Moodle 4.5+
|
||||||
|
$plugin->component = 'filter_mediacms';
|
||||||
|
$plugin->maturity = MATURITY_STABLE;
|
||||||
|
$plugin->release = 'v1.0.0';
|
||||||
2457
lms-plugins/mediacms-moodle/package-lock.json
generated
Normal file
2457
lms-plugins/mediacms-moodle/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
89
lms-plugins/mediacms-moodle/package.json
Normal file
89
lms-plugins/mediacms-moodle/package.json
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
{
|
||||||
|
"name": "mediacms-moodle",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "This package provides the integration between MediaCMS and Moodle (versions 4.x and 5.x). It consists of two components that work together to provide a seamless video experience:",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/cli": "^7.28.6",
|
||||||
|
"@babel/core": "^7.29.0",
|
||||||
|
"@babel/plugin-transform-modules-amd": "^7.27.1",
|
||||||
|
"@babel/preset-env": "^7.29.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"anymatch": "^3.1.3",
|
||||||
|
"babel-plugin-polyfill-corejs2": "^0.4.15",
|
||||||
|
"babel-plugin-polyfill-corejs3": "^0.14.0",
|
||||||
|
"babel-plugin-polyfill-regenerator": "^0.6.6",
|
||||||
|
"balanced-match": "^1.0.2",
|
||||||
|
"baseline-browser-mapping": "^2.9.19",
|
||||||
|
"binary-extensions": "^2.3.0",
|
||||||
|
"brace-expansion": "^1.1.12",
|
||||||
|
"braces": "^3.0.3",
|
||||||
|
"browserslist": "^4.28.1",
|
||||||
|
"caniuse-lite": "^1.0.30001766",
|
||||||
|
"chokidar": "^3.6.0",
|
||||||
|
"commander": "^6.2.1",
|
||||||
|
"concat-map": "^0.0.1",
|
||||||
|
"convert-source-map": "^2.0.0",
|
||||||
|
"core-js-compat": "^3.48.0",
|
||||||
|
"debug": "^4.4.3",
|
||||||
|
"electron-to-chromium": "^1.5.283",
|
||||||
|
"escalade": "^3.2.0",
|
||||||
|
"esutils": "^2.0.3",
|
||||||
|
"fill-range": "^7.1.1",
|
||||||
|
"fs-readdir-recursive": "^1.1.0",
|
||||||
|
"fs.realpath": "^1.0.0",
|
||||||
|
"function-bind": "^1.1.2",
|
||||||
|
"gensync": "^1.0.0-beta.2",
|
||||||
|
"glob": "^7.2.3",
|
||||||
|
"glob-parent": "^5.1.2",
|
||||||
|
"hasown": "^2.0.2",
|
||||||
|
"inflight": "^1.0.6",
|
||||||
|
"inherits": "^2.0.4",
|
||||||
|
"is-binary-path": "^2.1.0",
|
||||||
|
"is-core-module": "^2.16.1",
|
||||||
|
"is-extglob": "^2.1.1",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"is-number": "^7.0.0",
|
||||||
|
"js-tokens": "^4.0.0",
|
||||||
|
"jsesc": "^3.1.0",
|
||||||
|
"json5": "^2.2.3",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lru-cache": "^5.1.1",
|
||||||
|
"make-dir": "^2.1.0",
|
||||||
|
"minimatch": "^3.1.2",
|
||||||
|
"ms": "^2.1.3",
|
||||||
|
"node-releases": "^2.0.27",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"once": "^1.4.0",
|
||||||
|
"path-is-absolute": "^1.0.1",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"picocolors": "^1.1.1",
|
||||||
|
"picomatch": "^2.3.1",
|
||||||
|
"pify": "^4.0.1",
|
||||||
|
"readdirp": "^3.6.0",
|
||||||
|
"regenerate": "^1.4.2",
|
||||||
|
"regenerate-unicode-properties": "^10.2.2",
|
||||||
|
"regexpu-core": "^6.4.0",
|
||||||
|
"regjsgen": "^0.8.0",
|
||||||
|
"regjsparser": "^0.13.0",
|
||||||
|
"resolve": "^1.22.11",
|
||||||
|
"semver": "^6.3.1",
|
||||||
|
"slash": "^2.0.0",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0",
|
||||||
|
"to-regex-range": "^5.0.1",
|
||||||
|
"unicode-canonical-property-names-ecmascript": "^2.0.1",
|
||||||
|
"unicode-match-property-ecmascript": "^2.0.0",
|
||||||
|
"unicode-match-property-value-ecmascript": "^2.2.1",
|
||||||
|
"unicode-property-aliases-ecmascript": "^2.2.0",
|
||||||
|
"update-browserslist-db": "^1.2.3",
|
||||||
|
"wrappy": "^1.0.2",
|
||||||
|
"yallist": "^3.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
33
lms-plugins/mediacms-moodle/tiny/build_instructions.md
Normal file
33
lms-plugins/mediacms-moodle/tiny/build_instructions.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# JavaScript Build Instructions
|
||||||
|
|
||||||
|
These instructions explain how to manually rebuild the JavaScript modules for the TinyMCE plugin. Moodle requires AMD modules, but the source code is written in ES6.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
* Node.js 16+ (Node 22 recommended)
|
||||||
|
|
||||||
|
### Build Steps
|
||||||
|
|
||||||
|
1. **Navigate to the package root:**
|
||||||
|
```bash
|
||||||
|
cd lms-plugins/mediacms-moodle
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Initialize dependencies:**
|
||||||
|
(Only needed the first time)
|
||||||
|
```bash
|
||||||
|
npm init -y
|
||||||
|
npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/plugin-transform-modules-amd
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Run the Build:**
|
||||||
|
This command uses the local Babel binary to avoid version conflicts and transpiles the code to AMD format.
|
||||||
|
```bash
|
||||||
|
./node_modules/.bin/babel tiny/mediacms/amd/src --out-dir tiny/mediacms/amd/build --presets=@babel/preset-env --plugins=@babel/plugin-transform-modules-amd
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Minify (Optional but Recommended):**
|
||||||
|
Moodle loads `.min.js` files by default. This creates copies for production use.
|
||||||
|
```bash
|
||||||
|
cd tiny/mediacms/amd/build
|
||||||
|
for f in *.js; do cp "$f" "${f%.js}.min.js"; done
|
||||||
|
```
|
||||||
206
lms-plugins/mediacms-moodle/tiny/mediacms/AUTOCONVERT.md
Executable file
206
lms-plugins/mediacms-moodle/tiny/mediacms/AUTOCONVERT.md
Executable file
@ -0,0 +1,206 @@
|
|||||||
|
# MediaCMS URL Auto-Convert Feature
|
||||||
|
|
||||||
|
This feature automatically converts pasted MediaCMS video URLs into embedded video players within the TinyMCE editor.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
When a user pastes a MediaCMS video URL like:
|
||||||
|
```
|
||||||
|
https://deic.mediacms.io/view?m=JpBd1Zvdl
|
||||||
|
```
|
||||||
|
|
||||||
|
It is automatically converted to an embedded video player:
|
||||||
|
```html
|
||||||
|
<div class="tiny-iframe-responsive" contenteditable="false">
|
||||||
|
<iframe
|
||||||
|
style="width: 100%; max-width: calc(100vh * 16 / 9); aspect-ratio: 16 / 9; display: block; margin: auto; border: 0;"
|
||||||
|
src="https://deic.mediacms.io/embed?m=JpBd1Zvdl&showTitle=1&showRelated=1&showUserAvatar=1&linkTitle=1"
|
||||||
|
allowfullscreen="allowfullscreen">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported URL Formats
|
||||||
|
|
||||||
|
The auto-convert feature recognizes MediaCMS view URLs in this format:
|
||||||
|
- `https://[domain]/view?m=[VIDEO_ID]`
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
- `https://deic.mediacms.io/view?m=JpBd1Zvdl`
|
||||||
|
- `https://your-mediacms-instance.com/view?m=abc123`
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Accessing Settings
|
||||||
|
|
||||||
|
1. Log in to Moodle as an administrator
|
||||||
|
2. Navigate to: **Site administration** → **Plugins** → **Text editors** → **TinyMCE editor** → **MediaCMS**
|
||||||
|
3. Scroll to the **Auto-convert MediaCMS URLs** section
|
||||||
|
|
||||||
|
### Available Settings
|
||||||
|
|
||||||
|
| Setting | Description | Default |
|
||||||
|
|---------|-------------|---------|
|
||||||
|
| **Enable auto-convert** | Turn the auto-convert feature on or off | Enabled |
|
||||||
|
| **MediaCMS base URL** | Restrict auto-conversion to a specific MediaCMS domain | Empty (allow all) |
|
||||||
|
| **Show video title** | Display the video title in the embedded player | Enabled |
|
||||||
|
| **Link video title** | Make the video title clickable, linking to the original video page | Enabled |
|
||||||
|
| **Show related videos** | Display related videos after the current video ends | Enabled |
|
||||||
|
| **Show user avatar** | Display the uploader's avatar in the embedded player | Enabled |
|
||||||
|
|
||||||
|
### Settings Location in Moodle
|
||||||
|
|
||||||
|
The settings are stored in the Moodle database under the `tiny_mediacms` plugin configuration:
|
||||||
|
|
||||||
|
- `tiny_mediacms/autoconvertenabled` - Enable/disable auto-convert
|
||||||
|
- `tiny_mediacms/autoconvert_baseurl` - MediaCMS base URL (e.g., https://deic.mediacms.io)
|
||||||
|
- `tiny_mediacms/autoconvert_showtitle` - Show title option
|
||||||
|
- `tiny_mediacms/autoconvert_linktitle` - Link title option
|
||||||
|
- `tiny_mediacms/autoconvert_showrelated` - Show related option
|
||||||
|
- `tiny_mediacms/autoconvert_showuseravatar` - Show user avatar option
|
||||||
|
|
||||||
|
### Base URL Configuration
|
||||||
|
|
||||||
|
The **MediaCMS base URL** setting controls which MediaCMS instances are recognized for auto-conversion:
|
||||||
|
|
||||||
|
- **Empty (default)**: Any MediaCMS URL will be auto-converted (e.g., URLs from any `*/view?m=*` pattern)
|
||||||
|
- **Specific URL**: Only URLs from the specified domain will be auto-converted
|
||||||
|
|
||||||
|
Example configurations:
|
||||||
|
- `https://deic.mediacms.io` - Only convert URLs from deic.mediacms.io
|
||||||
|
- `https://media.myuniversity.edu` - Only convert URLs from your institution's MediaCMS
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
### File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
amd/src/
|
||||||
|
├── autoconvert.js # Main auto-convert module
|
||||||
|
├── plugin.js # Plugin initialization (imports autoconvert)
|
||||||
|
└── options.js # Configuration options definition
|
||||||
|
|
||||||
|
classes/
|
||||||
|
└── plugininfo.php # Passes PHP settings to JavaScript
|
||||||
|
|
||||||
|
settings.php # Admin settings page definition
|
||||||
|
|
||||||
|
lang/en/
|
||||||
|
└── tiny_mediacms.php # Language strings for settings
|
||||||
|
```
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. **Paste Detection**: The `autoconvert.js` module listens for `paste` events on the TinyMCE editor
|
||||||
|
2. **URL Validation**: When text is pasted, it checks if it matches the MediaCMS URL pattern
|
||||||
|
3. **HTML Generation**: If valid, it generates the responsive iframe HTML with configured options
|
||||||
|
4. **Content Insertion**: The original URL is replaced with the embedded video
|
||||||
|
|
||||||
|
### JavaScript Configuration
|
||||||
|
|
||||||
|
The settings are passed from PHP to JavaScript via the `plugininfo.php` class:
|
||||||
|
|
||||||
|
```php
|
||||||
|
protected static function get_autoconvert_configuration(): array {
|
||||||
|
$baseurl = get_config('tiny_mediacms', 'autoconvert_baseurl');
|
||||||
|
|
||||||
|
return [
|
||||||
|
'data' => [
|
||||||
|
'autoConvertEnabled' => (bool) get_config('tiny_mediacms', 'autoconvertenabled'),
|
||||||
|
'autoConvertBaseUrl' => !empty($baseurl) ? $baseurl : '',
|
||||||
|
'autoConvertOptions' => [
|
||||||
|
'showTitle' => (bool) get_config('tiny_mediacms', 'autoconvert_showtitle'),
|
||||||
|
'linkTitle' => (bool) get_config('tiny_mediacms', 'autoconvert_linktitle'),
|
||||||
|
'showRelated' => (bool) get_config('tiny_mediacms', 'autoconvert_showrelated'),
|
||||||
|
'showUserAvatar' => (bool) get_config('tiny_mediacms', 'autoconvert_showuseravatar'),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Default Values (in options.js)
|
||||||
|
|
||||||
|
If PHP settings are not configured, the JavaScript uses these defaults:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
registerOption(dataName, {
|
||||||
|
processor: 'object',
|
||||||
|
"default": {
|
||||||
|
autoConvertEnabled: true,
|
||||||
|
autoConvertBaseUrl: '', // Empty = allow all MediaCMS domains
|
||||||
|
autoConvertOptions: {
|
||||||
|
showTitle: true,
|
||||||
|
linkTitle: true,
|
||||||
|
showRelated: true,
|
||||||
|
showUserAvatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
### Disabling Auto-Convert
|
||||||
|
|
||||||
|
To disable the feature entirely:
|
||||||
|
1. Go to the plugin settings (see "Accessing Settings" above)
|
||||||
|
2. Uncheck **Enable auto-convert**
|
||||||
|
3. Save changes
|
||||||
|
|
||||||
|
### Programmatic Configuration
|
||||||
|
|
||||||
|
You can also set these values directly in the database using Moodle's `set_config()` function:
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Disable auto-convert
|
||||||
|
set_config('autoconvertenabled', 0, 'tiny_mediacms');
|
||||||
|
|
||||||
|
// Set the MediaCMS base URL (restrict to specific domain)
|
||||||
|
set_config('autoconvert_baseurl', 'https://deic.mediacms.io', 'tiny_mediacms');
|
||||||
|
|
||||||
|
// Customize embed options
|
||||||
|
set_config('autoconvert_showtitle', 1, 'tiny_mediacms');
|
||||||
|
set_config('autoconvert_linktitle', 0, 'tiny_mediacms');
|
||||||
|
set_config('autoconvert_showrelated', 0, 'tiny_mediacms');
|
||||||
|
set_config('autoconvert_showuseravatar', 1, 'tiny_mediacms');
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Configuration
|
||||||
|
|
||||||
|
Using Moodle CLI:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Enable auto-convert
|
||||||
|
php admin/cli/cfg.php --component=tiny_mediacms --name=autoconvertenabled --set=1
|
||||||
|
|
||||||
|
# Set the MediaCMS base URL
|
||||||
|
php admin/cli/cfg.php --component=tiny_mediacms --name=autoconvert_baseurl --set=https://deic.mediacms.io
|
||||||
|
|
||||||
|
# Disable showing related videos
|
||||||
|
php admin/cli/cfg.php --component=tiny_mediacms --name=autoconvert_showrelated --set=0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Auto-convert not working
|
||||||
|
|
||||||
|
1. **Check if enabled**: Verify the setting is enabled in plugin settings
|
||||||
|
2. **Clear caches**: Purge all caches (Site administration → Development → Purge all caches)
|
||||||
|
3. **Check URL format**: Ensure the URL matches the pattern `https://[domain]/view?m=[VIDEO_ID]`
|
||||||
|
4. **Browser console**: Check for JavaScript errors in the browser developer console
|
||||||
|
|
||||||
|
### Rebuilding JavaScript
|
||||||
|
|
||||||
|
If you modify the source files, rebuild using:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/moodle
|
||||||
|
npx grunt amd --root=public/lib/editor/tiny/plugins/mediacms
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: Requires Node.js 22.x or compatible version as specified in Moodle's requirements.
|
||||||
|
|
||||||
|
## Version History
|
||||||
|
|
||||||
|
- **1.0.0** - Initial implementation of auto-convert feature
|
||||||
15
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/autoconvert.min.js
vendored
Executable file
15
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/autoconvert.min.js
vendored
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
define("tiny_mediacms/autoconvert",["exports","./options"],(function(_exports,_options){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupAutoConvert=_exports.isMediaCMSUrl=_exports.convertToEmbed=void 0;
|
||||||
|
/**
|
||||||
|
* Tiny MediaCMS Auto-convert module.
|
||||||
|
*
|
||||||
|
* This module automatically converts pasted MediaCMS URLs into embedded videos.
|
||||||
|
* When a user pastes a MediaCMS video URL (e.g., https://deic.mediacms.io/view?m=JpBd1Zvdl),
|
||||||
|
* it will be automatically converted to an iframe embed.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/autoconvert
|
||||||
|
* @copyright 2024
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
const MEDIACMS_VIEW_URL_PATTERN=/^(https?:\/\/[^\/]+)\/view\?m=([a-zA-Z0-9_-]+)$/,parseMediaCMSUrl=text=>{if(!text||"string"!=typeof text)return null;const trimmed=text.trim(),match=trimmed.match(MEDIACMS_VIEW_URL_PATTERN);return match?{baseUrl:match[1],videoId:match[2],originalUrl:trimmed}:null},isDomainAllowed=(parsed,config)=>{const configuredBaseUrl=config.autoConvertBaseUrl||config.mediacmsBaseUrl;if(!configuredBaseUrl)return!0;try{const configuredUrl=new URL(configuredBaseUrl),pastedUrl=new URL(parsed.baseUrl);return configuredUrl.host===pastedUrl.host}catch(e){return!0}},generateEmbedHtml=function(parsed){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const embedUrl=new URL("".concat(parsed.baseUrl,"/embed"));embedUrl.searchParams.set("m",parsed.videoId),embedUrl.searchParams.set("showTitle",!1!==options.showTitle?"1":"0"),embedUrl.searchParams.set("showRelated",!1!==options.showRelated?"1":"0"),embedUrl.searchParams.set("showUserAvatar",!1!==options.showUserAvatar?"1":"0"),embedUrl.searchParams.set("linkTitle",!1!==options.linkTitle?"1":"0");const html='<iframe width="400" height="300" style="display: block; border: 0;" '+'src="'.concat(embedUrl.toString(),'" ')+'allowfullscreen="allowfullscreen"></iframe>';return html};_exports.setupAutoConvert=editor=>{const config=(0,_options.getData)(editor)||{};!1!==config.autoConvertEnabled&&(editor.on("paste",(e=>{handlePasteEvent(editor,e,config)})),editor.on("input",(e=>{handleInputEvent(editor,e,config)})))};const handlePasteEvent=(editor,e,config)=>{const clipboardData=e.clipboardData||window.clipboardData;if(!clipboardData)return;const text=clipboardData.getData("text/plain")||clipboardData.getData("text");if(!text)return;const parsed=parseMediaCMSUrl(text);if(!parsed)return;if(!isDomainAllowed(parsed,config))return;e.preventDefault(),e.stopPropagation();const embedHtml=generateEmbedHtml(parsed,config.autoConvertOptions||{});setTimeout((()=>{editor.insertContent(embedHtml),editor.selection.collapse(!1)}),0)},handleInputEvent=(editor,e,config)=>{if("insertFromPaste"!==e.inputType&&"insertText"!==e.inputType)return;const node=editor.selection.getNode();if(!node||"P"!==node.nodeName)return;const text=node.textContent||"",parsed=parseMediaCMSUrl(text);if(!parsed||!isDomainAllowed(parsed,config))return;const trimmedHtml=node.innerHTML.trim();if(trimmedHtml!==text.trim()&&!trimmedHtml.startsWith(text.trim()))return;const embedHtml=generateEmbedHtml(parsed,config.autoConvertOptions||{});setTimeout((()=>{const currentText=node.textContent||"",currentParsed=parseMediaCMSUrl(currentText);currentParsed&¤tParsed.originalUrl===parsed.originalUrl&&(editor.selection.select(node),editor.insertContent(embedHtml))}),100)};_exports.isMediaCMSUrl=text=>null!==parseMediaCMSUrl(text);_exports.convertToEmbed=function(url){let options=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const parsed=parseMediaCMSUrl(url);return parsed?generateEmbedHtml(parsed,options):null}}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=autoconvert.min.js.map
|
||||||
File diff suppressed because one or more lines are too long
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js
vendored
Executable file
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/commands.min.js.map
Executable file
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/common.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/common.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/common",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={pluginName:"tiny_mediacms/plugin",component:"tiny_mediacms",iframeButtonName:"tiny_mediacms_iframe",iframeMenuItemName:"tiny_mediacms_iframe",iframeIcon:"tiny_mediacms_iframe"},_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=common.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/common.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/common.min.js.map
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"common.min.js","sources":["../src/common.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media common values.\n *\n * @module tiny_mediacms/common\n * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nexport default {\n pluginName: 'tiny_mediacms/plugin',\n component: 'tiny_mediacms',\n iframeButtonName: 'tiny_mediacms_iframe',\n iframeMenuItemName: 'tiny_mediacms_iframe',\n iframeIcon: 'tiny_mediacms_iframe',\n};\n"],"names":["pluginName","component","iframeButtonName","iframeMenuItemName","iframeIcon"],"mappings":"sKAuBe,CACXA,WAAY,uBACZC,UAAW,gBACXC,iBAAkB,uBAClBC,mBAAoB,uBACpBC,WAAY"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/configuration.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/configuration.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/configuration",["exports","./common","editor_tiny/utils"],(function(_exports,_common,_utils){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.configure=void 0;_exports.configure=instanceConfig=>{return{contextmenu:(0,_utils.addContextmenuItem)(instanceConfig.contextmenu,_common.iframeButtonName),menu:(menu=instanceConfig.menu,menu.insert.items="".concat(_common.iframeMenuItemName," ").concat(menu.insert.items),menu),toolbar:(toolbar=instanceConfig.toolbar,toolbar.map((section=>("content"===section.name&§ion.items.unshift(_common.iframeButtonName),section))))};var toolbar,menu}}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=configuration.min.js.map
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"configuration.min.js","sources":["../src/configuration.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media configuration.\n *\n * @module tiny_mediacms/configuration\n * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {\n iframeButtonName,\n iframeMenuItemName,\n} from './common';\nimport {\n addContextmenuItem,\n} from 'editor_tiny/utils';\n\nconst configureMenu = (menu) => {\n // Add the Iframe Embed to the insert menu.\n menu.insert.items = `${iframeMenuItemName} ${menu.insert.items}`;\n\n return menu;\n};\n\nconst configureToolbar = (toolbar) => {\n // The toolbar contains an array of named sections.\n // The Moodle integration ensures that there is a section called 'content'.\n\n return toolbar.map((section) => {\n if (section.name === 'content') {\n // Insert the iframe button at the start of it.\n section.items.unshift(iframeButtonName);\n }\n\n return section;\n });\n};\n\nexport const configure = (instanceConfig) => {\n // Update the instance configuration to add the Iframe Embed menu option to the menus and toolbars.\n return {\n contextmenu: addContextmenuItem(instanceConfig.contextmenu, iframeButtonName),\n menu: configureMenu(instanceConfig.menu),\n toolbar: configureToolbar(instanceConfig.toolbar),\n };\n};\n"],"names":["instanceConfig","contextmenu","iframeButtonName","menu","insert","items","iframeMenuItemName","toolbar","map","section","name","unshift"],"mappings":"wNAoD0BA,uBAEf,CACHC,aAAa,6BAAmBD,eAAeC,YAAaC,0BAC5DC,MAzBeA,KAyBKH,eAAeG,KAvBvCA,KAAKC,OAAOC,gBAAWC,uCAAsBH,KAAKC,OAAOC,OAElDF,MAsBHI,SAnBkBA,QAmBQP,eAAeO,QAftCA,QAAQC,KAAKC,UACK,YAAjBA,QAAQC,MAERD,QAAQJ,MAAMM,QAAQT,0BAGnBO,aAVWF,IAAAA,QAPHJ"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embed.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embed.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embed.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embed.min.js.map
Executable file
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embedmodal.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/embedmodal.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/embedmodal",["exports","core/modal","./common"],(function(_exports,_modal,_common){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class EmbedModal extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.removeOnClose=!0,modalConfig.show=!0,super.configure(modalConfig)}}return _exports.default=EmbedModal,_defineProperty(EmbedModal,"TYPE","".concat(_common.component,"/modal")),_defineProperty(EmbedModal,"TEMPLATE","".concat(_common.component,"/embed_media_modal")),_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=embedmodal.min.js.map
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"embedmodal.min.js","sources":["../src/embedmodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Embedded Media Management Modal for Tiny.\n *\n * @module tiny_mediacms/embedmodal\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport {component} from './common';\n\nexport default class EmbedModal extends Modal {\n static TYPE = `${component}/modal`;\n static TEMPLATE = `${component}/embed_media_modal`;\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.removeOnClose = true;\n modalConfig.show = true;\n\n super.configure(modalConfig);\n }\n}\n"],"names":["EmbedModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","configure","modalConfig","large","removeOnClose","show","component"],"mappings":"iaA0BqBA,mBAAmBC,eAIpCC,+BAEUA,8BAGDC,2BACAC,wBAGTC,UAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,eAAgB,EAC5BF,YAAYG,MAAO,QAEbJ,UAAUC,iEAlBHN,4BACAU,6CADAV,gCAEIU"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframeembed.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframemodal.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/iframemodal.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/iframemodal",["exports","core/modal","./common"],(function(_exports,_modal,_common){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class IframeModal extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.removeOnClose=!0,modalConfig.show=!0,super.configure(modalConfig)}}return _exports.default=IframeModal,_defineProperty(IframeModal,"TYPE","".concat(_common.component,"/iframemodal")),_defineProperty(IframeModal,"TEMPLATE","".concat(_common.component,"/iframe_embed_modal")),_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=iframemodal.min.js.map
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"iframemodal.min.js","sources":["../src/iframemodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Iframe Embed Modal for Tiny Media2.\n *\n * @module tiny_mediacms/iframemodal\n * @copyright 2024\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport {component} from './common';\n\nexport default class IframeModal extends Modal {\n static TYPE = `${component}/iframemodal`;\n static TEMPLATE = `${component}/iframe_embed_modal`;\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.removeOnClose = true;\n modalConfig.show = true;\n\n super.configure(modalConfig);\n }\n}\n"],"names":["IframeModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","configure","modalConfig","large","removeOnClose","show","component"],"mappings":"kaA0BqBA,oBAAoBC,eAIrCC,+BAEUA,8BAGDC,2BACAC,wBAGTC,UAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,eAAgB,EAC5BF,YAAYG,MAAO,QAEbJ,UAAUC,kEAlBHN,6BACAU,mDADAV,iCAEIU"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/image.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/image.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/image.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/image.min.js.map
Executable file
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagedetails.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagedetails.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagehelpers.min.js
vendored
Executable file
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagehelpers.min.js
vendored
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
define("tiny_mediacms/imagehelpers",["exports","core/templates"],(function(_exports,_templates){var obj;
|
||||||
|
/**
|
||||||
|
* Tiny media plugin image helpers.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/imagehelpers
|
||||||
|
* @copyright 2024 Meirza <meirza.arson@moodle.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.showElements=_exports.isPercentageValue=_exports.hideElements=_exports.footerImageInsert=_exports.footerImageDetails=_exports.bodyImageInsert=_exports.bodyImageDetails=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};_exports.bodyImageInsert=async(templateContext,root)=>_templates.default.renderForPromise("tiny_mediacms/insert_image_modal_insert",{...templateContext}).then((_ref=>{let{html:html,js:js}=_ref;_templates.default.replaceNodeContents(root.querySelector(".tiny_imagecms_body_template"),html,js)})).catch((error=>{window.console.log(error)}));_exports.footerImageInsert=async(templateContext,root)=>_templates.default.renderForPromise("tiny_mediacms/insert_image_modal_insert_footer",{...templateContext}).then((_ref2=>{let{html:html,js:js}=_ref2;_templates.default.replaceNodeContents(root.querySelector(".tiny_imagecms_footer_template"),html,js)})).catch((error=>{window.console.log(error)}));_exports.bodyImageDetails=async(templateContext,root)=>_templates.default.renderForPromise("tiny_mediacms/insert_image_modal_details",{...templateContext}).then((_ref3=>{let{html:html,js:js}=_ref3;_templates.default.replaceNodeContents(root.querySelector(".tiny_imagecms_body_template"),html,js)})).catch((error=>{window.console.log(error)}));_exports.footerImageDetails=async(templateContext,root)=>_templates.default.renderForPromise("tiny_mediacms/insert_image_modal_details_footer",{...templateContext}).then((_ref4=>{let{html:html,js:js}=_ref4;_templates.default.replaceNodeContents(root.querySelector(".tiny_imagecms_footer_template"),html,js)})).catch((error=>{window.console.log(error)}));_exports.showElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.remove("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.remove("d-none")}};_exports.hideElements=(elements,root)=>{if(elements instanceof Array)elements.forEach((elementSelector=>{const element=root.querySelector(elementSelector);element&&element.classList.add("d-none")}));else{const element=root.querySelector(elements);element&&element.classList.add("d-none")}};_exports.isPercentageValue=value=>value.match(/\d+%/)}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=imagehelpers.min.js.map
|
||||||
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imageinsert.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imageinsert.min.js
vendored
Executable file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagemodal.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/imagemodal.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/imagemodal",["exports","core/modal","./common"],(function(_exports,_modal,_common){var obj;function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_modal=(obj=_modal)&&obj.__esModule?obj:{default:obj};class ImageModal extends _modal.default{registerEventListeners(){super.registerEventListeners(),this.registerCloseOnSave(),this.registerCloseOnCancel()}configure(modalConfig){modalConfig.large=!0,modalConfig.removeOnClose=!0,modalConfig.show=!0,super.configure(modalConfig)}}return _exports.default=ImageModal,_defineProperty(ImageModal,"TYPE","".concat(_common.component,"/imagemodal")),_defineProperty(ImageModal,"TEMPLATE","".concat(_common.component,"/insert_image_modal")),ImageModal.registerModalType(),_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=imagemodal.min.js.map
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"imagemodal.min.js","sources":["../src/imagemodal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Image Modal for Tiny.\n *\n * @module tiny_mediacms/imagemodal\n * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Modal from 'core/modal';\nimport {component} from './common';\n\nexport default class ImageModal extends Modal {\n static TYPE = `${component}/imagemodal`;\n static TEMPLATE = `${component}/insert_image_modal`;\n\n registerEventListeners() {\n // Call the parent registration.\n super.registerEventListeners();\n\n // Register to close on save/cancel.\n this.registerCloseOnSave();\n this.registerCloseOnCancel();\n }\n\n configure(modalConfig) {\n modalConfig.large = true;\n modalConfig.removeOnClose = true;\n modalConfig.show = true;\n\n super.configure(modalConfig);\n }\n}\n\nImageModal.registerModalType();\n"],"names":["ImageModal","Modal","registerEventListeners","registerCloseOnSave","registerCloseOnCancel","configure","modalConfig","large","removeOnClose","show","component","registerModalType"],"mappings":"iaA0BqBA,mBAAmBC,eAIpCC,+BAEUA,8BAGDC,2BACAC,wBAGTC,UAAUC,aACNA,YAAYC,OAAQ,EACpBD,YAAYE,eAAgB,EAC5BF,YAAYG,MAAO,QAEbJ,UAAUC,iEAlBHN,4BACAU,kDADAV,gCAEIU,0CAoBzBV,WAAWW"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/manager.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/manager.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/manager",["exports","core/templates","core/str","core/modal","core/modal_events","./options","core/config"],(function(_exports,_templates,_str,_modal,ModalEvents,_options,_config){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_templates=_interopRequireDefault(_templates),_modal=_interopRequireDefault(_modal),ModalEvents=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(ModalEvents),_config=_interopRequireDefault(_config);return _exports.default=class{constructor(editor){_defineProperty(this,"editor",null),_defineProperty(this,"area",null),this.editor=editor;const data=(0,_options.getData)(editor);this.area=data.params.area,this.area.itemid=data.fpoptions.image.itemid}async displayDialogue(){const modal=await _modal.default.create({large:!0,title:(0,_str.getString)("mediamanagerproperties","tiny_mediacms"),body:_templates.default.render("tiny_mediacms/mm2_iframe",{src:this.getIframeURL()}),removeOnClose:!0,show:!0});return modal.getRoot().on(ModalEvents.bodyRendered,(()=>{this.selectFirstElement()})),document.querySelector(".modal-lg").style.cssText="max-width: 850px",modal}selectFirstElement(){const iframe=document.getElementById("mm2-iframe");iframe.addEventListener("load",(function(){let intervalId=setInterval((function(){const iDocument=iframe.contentWindow.document;if(iDocument.querySelector(".filemanager")){const firstFocusableElement=iDocument.querySelector(".fp-navbar a:not([disabled])");firstFocusableElement&&firstFocusableElement.focus(),clearInterval(intervalId)}}),200)}))}getIframeURL(){const url=new URL("".concat(_config.default.wwwroot,"/lib/editor/tiny/plugins/mediacms/manage.php"));url.searchParams.append("elementid",this.editor.getElement().id);for(const key in this.area)url.searchParams.append(key,this.area[key]);return url.toString()}},_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=manager.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/manager.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/manager.min.js.map
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"manager.min.js","sources":["../src/manager.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media Manager plugin class for Moodle.\n *\n * @module tiny_mediacms/manager\n * @copyright 2022, Stevani Andolo <stevani@hotmail.com.au>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Templates from 'core/templates';\nimport {getString} from 'core/str';\nimport Modal from 'core/modal';\nimport * as ModalEvents from 'core/modal_events';\nimport {getData} from './options';\nimport Config from 'core/config';\n\nexport default class MediaManager {\n\n editor = null;\n area = null;\n\n constructor(editor) {\n this.editor = editor;\n const data = getData(editor);\n this.area = data.params.area;\n this.area.itemid = data.fpoptions.image.itemid;\n }\n\n async displayDialogue() {\n const modal = await Modal.create({\n large: true,\n title: getString('mediamanagerproperties', 'tiny_mediacms'),\n body: Templates.render('tiny_mediacms/mm2_iframe', {\n src: this.getIframeURL()\n }),\n removeOnClose: true,\n show: true,\n });\n modal.getRoot().on(ModalEvents.bodyRendered, () => {\n this.selectFirstElement();\n });\n\n document.querySelector('.modal-lg').style.cssText = `max-width: 850px`;\n return modal;\n }\n\n // It will select the first element in the file manager.\n selectFirstElement() {\n const iframe = document.getElementById('mm2-iframe');\n iframe.addEventListener('load', function() {\n let intervalId = setInterval(function() {\n const iDocument = iframe.contentWindow.document;\n if (iDocument.querySelector('.filemanager')) {\n const firstFocusableElement = iDocument.querySelector('.fp-navbar a:not([disabled])');\n if (firstFocusableElement) {\n firstFocusableElement.focus();\n }\n clearInterval(intervalId);\n }\n }, 200);\n });\n }\n\n getIframeURL() {\n const url = new URL(`${Config.wwwroot}/lib/editor/tiny/plugins/mediacms/manage.php`);\n url.searchParams.append('elementid', this.editor.getElement().id);\n for (const key in this.area) {\n url.searchParams.append(key, this.area[key]);\n }\n return url.toString();\n }\n}\n"],"names":["constructor","editor","data","area","params","itemid","fpoptions","image","modal","Modal","create","large","title","body","Templates","render","src","this","getIframeURL","removeOnClose","show","getRoot","on","ModalEvents","bodyRendered","selectFirstElement","document","querySelector","style","cssText","iframe","getElementById","addEventListener","intervalId","setInterval","iDocument","contentWindow","firstFocusableElement","focus","clearInterval","url","URL","Config","wwwroot","searchParams","append","getElement","id","key","toString"],"mappings":"mmDAmCIA,YAAYC,sCAHH,kCACF,WAGEA,OAASA,aACRC,MAAO,oBAAQD,aAChBE,KAAOD,KAAKE,OAAOD,UACnBA,KAAKE,OAASH,KAAKI,UAAUC,MAAMF,qCAIlCG,YAAcC,eAAMC,OAAO,CAC7BC,OAAO,EACPC,OAAO,kBAAU,yBAA0B,iBAC3CC,KAAMC,mBAAUC,OAAO,2BAA4B,CAC/CC,IAAKC,KAAKC,iBAEdC,eAAe,EACfC,MAAM,WAEVZ,MAAMa,UAAUC,GAAGC,YAAYC,cAAc,UACpCC,wBAGTC,SAASC,cAAc,aAAaC,MAAMC,2BACnCrB,MAIXiB,2BACUK,OAASJ,SAASK,eAAe,cACvCD,OAAOE,iBAAiB,QAAQ,eACxBC,WAAaC,aAAY,iBACnBC,UAAYL,OAAOM,cAAcV,YACnCS,UAAUR,cAAc,gBAAiB,OACnCU,sBAAwBF,UAAUR,cAAc,gCAClDU,uBACAA,sBAAsBC,QAE1BC,cAAcN,eAEnB,QAIXf,qBACUsB,IAAM,IAAIC,cAAOC,gBAAOC,yDAC9BH,IAAII,aAAaC,OAAO,YAAa5B,KAAKhB,OAAO6C,aAAaC,QACzD,MAAMC,OAAO/B,KAAKd,KACnBqC,IAAII,aAAaC,OAAOG,IAAK/B,KAAKd,KAAK6C,aAEpCR,IAAIS"}
|
||||||
11
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/options.min.js
vendored
Executable file
11
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/options.min.js
vendored
Executable file
@ -0,0 +1,11 @@
|
|||||||
|
define("tiny_mediacms/options",["exports","editor_tiny/options","./common"],(function(_exports,_options,_common){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.register=_exports.getPermissions=_exports.getLti=_exports.getImagePermissions=_exports.getEmbedPermissions=_exports.getData=void 0;
|
||||||
|
/**
|
||||||
|
* Options helper for Tiny Media plugin.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/options
|
||||||
|
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
const dataName=(0,_options.getPluginOptionName)(_common.pluginName,"data"),permissionsName=(0,_options.getPluginOptionName)(_common.pluginName,"permissions"),ltiName=(0,_options.getPluginOptionName)(_common.pluginName,"lti");_exports.register=editor=>{const registerOption=editor.options.register;registerOption(permissionsName,{processor:"object",default:{image:{filepicker:!1}}}),registerOption(dataName,{processor:"object",default:{mediacmsApiUrl:"",mediacmsBaseUrl:"",mediacmsPageSize:12,autoConvertEnabled:!0,autoConvertBaseUrl:"",autoConvertOptions:{showTitle:!0,linkTitle:!0,showRelated:!0,showUserAvatar:!0}}}),registerOption(ltiName,{processor:"object",default:{toolId:0,courseId:0,contentItemUrl:""}})};const getPermissions=editor=>editor.options.get(permissionsName);_exports.getPermissions=getPermissions;_exports.getImagePermissions=editor=>getPermissions(editor).image;_exports.getEmbedPermissions=editor=>getPermissions(editor).embed;_exports.getData=editor=>editor.options.get(dataName);_exports.getLti=editor=>editor.options.get(ltiName)}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=options.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/options.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/options.min.js.map
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"options.min.js","sources":["../src/options.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Options helper for Tiny Media plugin.\n *\n * @module tiny_mediacms/options\n * @copyright 2022 Huong Nguyen <huongnv13@gmail.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {getPluginOptionName} from 'editor_tiny/options';\nimport {pluginName} from './common';\n\nconst dataName = getPluginOptionName(pluginName, 'data');\nconst permissionsName = getPluginOptionName(pluginName, 'permissions');\nconst ltiName = getPluginOptionName(pluginName, 'lti');\n\n/**\n * Register the options for the Tiny Media plugin.\n *\n * @param {TinyMCE} editor\n */\nexport const register = (editor) => {\n const registerOption = editor.options.register;\n\n registerOption(permissionsName, {\n processor: 'object',\n \"default\": {\n image: {\n filepicker: false,\n }\n },\n });\n\n registerOption(dataName, {\n processor: 'object',\n \"default\": {\n // MediaCMS video library configuration\n mediacmsApiUrl: '', // e.g., 'https://deic.mediacms.io/api/v1/media'\n mediacmsBaseUrl: '', // e.g., 'https://deic.mediacms.io'\n mediacmsPageSize: 12,\n // Auto-conversion settings\n autoConvertEnabled: true, // Enable/disable auto-conversion of pasted MediaCMS URLs\n autoConvertBaseUrl: '', // Base URL to restrict auto-conversion (empty = allow all MediaCMS domains)\n autoConvertOptions: {\n // Default embed options for auto-converted videos\n showTitle: true,\n linkTitle: true,\n showRelated: true,\n showUserAvatar: true,\n },\n },\n });\n\n registerOption(ltiName, {\n processor: 'object',\n \"default\": {\n // LTI configuration for MediaCMS iframe library\n toolId: 0, // LTI external tool ID\n courseId: 0, // Current course ID\n contentItemUrl: '', // URL to /mod/lti/contentitem.php for Deep Linking\n },\n });\n};\n\n/**\n * Get the permissions configuration for the Tiny Media plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getPermissions = (editor) => editor.options.get(permissionsName);\n\n/**\n * Get the permissions configuration for the Tiny Media plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getImagePermissions = (editor) => getPermissions(editor).image;\n\n/**\n * Get the permissions configuration for the Tiny Media plugin.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getEmbedPermissions = (editor) => getPermissions(editor).embed;\n\n/**\n * Get the data configuration for the Media Manager.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getData = (editor) => editor.options.get(dataName);\n\n/**\n * Get the LTI configuration for the MediaCMS iframe library.\n *\n * @param {TinyMCE} editor\n * @returns {object}\n */\nexport const getLti = (editor) => editor.options.get(ltiName);\n"],"names":["dataName","pluginName","permissionsName","ltiName","editor","registerOption","options","register","processor","image","filepicker","mediacmsApiUrl","mediacmsBaseUrl","mediacmsPageSize","autoConvertEnabled","autoConvertBaseUrl","autoConvertOptions","showTitle","linkTitle","showRelated","showUserAvatar","toolId","courseId","contentItemUrl","getPermissions","get","embed"],"mappings":";;;;;;;;MA0BMA,UAAW,gCAAoBC,mBAAY,QAC3CC,iBAAkB,gCAAoBD,mBAAY,eAClDE,SAAU,gCAAoBF,mBAAY,yBAOvBG,eACfC,eAAiBD,OAAOE,QAAQC,SAEtCF,eAAeH,gBAAiB,CAC5BM,UAAW,iBACA,CACPC,MAAO,CACHC,YAAY,MAKxBL,eAAeL,SAAU,CACrBQ,UAAW,iBACA,CAEPG,eAAgB,GAChBC,gBAAiB,GACjBC,iBAAkB,GAElBC,oBAAoB,EACpBC,mBAAoB,GACpBC,mBAAoB,CAEhBC,WAAW,EACXC,WAAW,EACXC,aAAa,EACbC,gBAAgB,MAK5Bf,eAAeF,QAAS,CACpBK,UAAW,iBACA,CAEPa,OAAQ,EACRC,SAAU,EACVC,eAAgB,aAWfC,eAAkBpB,QAAWA,OAAOE,QAAQmB,IAAIvB,qFAQzBE,QAAWoB,eAAepB,QAAQK,mCAQlCL,QAAWoB,eAAepB,QAAQsB,uBAQ9CtB,QAAWA,OAAOE,QAAQmB,IAAIzB,0BAQ/BI,QAAWA,OAAOE,QAAQmB,IAAItB"}
|
||||||
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/plugin.min.js
vendored
Executable file
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/plugin.min.js
vendored
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
define("tiny_mediacms/plugin",["exports","editor_tiny/loader","editor_tiny/utils","./common","./commands","./configuration","./options","./autoconvert"],(function(_exports,_loader,_utils,_common,Commands,Configuration,Options,_autoconvert){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
|
||||||
|
/**
|
||||||
|
* Tiny Media plugin for Moodle.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/plugin
|
||||||
|
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,Commands=_interopRequireWildcard(Commands),Configuration=_interopRequireWildcard(Configuration),Options=_interopRequireWildcard(Options);var _default=new Promise((async resolve=>{const[tinyMCE,setupCommands,pluginMetadata]=await Promise.all([(0,_loader.getTinyMCE)(),Commands.getSetup(),(0,_utils.getPluginMetadata)(_common.component,_common.pluginName)]);tinyMCE.PluginManager.add("".concat(_common.component,"/plugin"),(editor=>(Options.register(editor),setupCommands(editor),(0,_autoconvert.setupAutoConvert)(editor),editor.on("GetContent",(e=>{if("html"===e.format){const tempDiv=document.createElement("div");tempDiv.innerHTML=e.content,tempDiv.querySelectorAll(".tiny-mediacms-edit-btn").forEach((btn=>btn.remove())),tempDiv.querySelectorAll(".tiny-mediacms-iframe-wrapper").forEach((wrapper=>{const iframe=wrapper.querySelector("iframe");iframe&&wrapper.parentNode.insertBefore(iframe,wrapper),wrapper.remove()})),tempDiv.querySelectorAll(".tiny-iframe-responsive").forEach((wrapper=>{const iframe=wrapper.querySelector("iframe");iframe&&wrapper.parentNode.insertBefore(iframe,wrapper),wrapper.remove()})),e.content=tempDiv.innerHTML}})),pluginMetadata))),resolve(["".concat(_common.component,"/plugin"),Configuration])}));return _exports.default=_default,_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=plugin.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/plugin.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/plugin.min.js.map
Executable file
@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"plugin.min.js","sources":["../src/plugin.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Tiny Media plugin for Moodle.\n *\n * @module tiny_mediacms/plugin\n * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {getTinyMCE} from 'editor_tiny/loader';\nimport {getPluginMetadata} from 'editor_tiny/utils';\n\nimport {component, pluginName} from './common';\nimport * as Commands from './commands';\nimport * as Configuration from './configuration';\nimport * as Options from './options';\nimport {setupAutoConvert} from './autoconvert';\n\n// eslint-disable-next-line no-async-promise-executor\nexport default new Promise(async(resolve) => {\n const [\n tinyMCE,\n setupCommands,\n pluginMetadata,\n ] = await Promise.all([\n getTinyMCE(),\n Commands.getSetup(),\n getPluginMetadata(component, pluginName),\n ]);\n\n tinyMCE.PluginManager.add(`${component}/plugin`, (editor) => {\n // Register options.\n Options.register(editor);\n\n // Setup the Commands (buttons, menu items, and so on).\n setupCommands(editor);\n\n // Setup auto-conversion of pasted MediaCMS URLs.\n setupAutoConvert(editor);\n\n // Clean up editor-only elements before content is saved.\n // Remove wrapper divs and edit buttons that are only for the editor UI.\n editor.on('GetContent', (e) => {\n if (e.format === 'html') {\n // Create a temporary container to manipulate the HTML\n const tempDiv = document.createElement('div');\n tempDiv.innerHTML = e.content;\n\n // Remove edit buttons\n tempDiv.querySelectorAll('.tiny-mediacms-edit-btn').forEach(btn => btn.remove());\n\n // Unwrap iframes from tiny-mediacms-iframe-wrapper\n tempDiv.querySelectorAll('.tiny-mediacms-iframe-wrapper').forEach(wrapper => {\n const iframe = wrapper.querySelector('iframe');\n if (iframe) {\n wrapper.parentNode.insertBefore(iframe, wrapper);\n }\n wrapper.remove();\n });\n\n // Unwrap iframes from tiny-iframe-responsive\n tempDiv.querySelectorAll('.tiny-iframe-responsive').forEach(wrapper => {\n const iframe = wrapper.querySelector('iframe');\n if (iframe) {\n wrapper.parentNode.insertBefore(iframe, wrapper);\n }\n wrapper.remove();\n });\n\n e.content = tempDiv.innerHTML;\n }\n });\n\n return pluginMetadata;\n });\n\n // Resolve the Media Plugin and include configuration.\n resolve([`${component}/plugin`, Configuration]);\n});\n"],"names":["Promise","async","tinyMCE","setupCommands","pluginMetadata","all","Commands","getSetup","component","pluginName","PluginManager","add","editor","Options","register","on","e","format","tempDiv","document","createElement","innerHTML","content","querySelectorAll","forEach","btn","remove","wrapper","iframe","querySelector","parentNode","insertBefore","resolve","Configuration"],"mappings":";;;;;;;2OAgCe,IAAIA,SAAQC,MAAAA,gBAEnBC,QACAC,cACAC,sBACMJ,QAAQK,IAAI,EAClB,wBACAC,SAASC,YACT,4BAAkBC,kBAAWC,sBAGjCP,QAAQQ,cAAcC,cAAOH,8BAAqBI,SAE9CC,QAAQC,SAASF,QAGjBT,cAAcS,0CAGGA,QAIjBA,OAAOG,GAAG,cAAeC,OACJ,SAAbA,EAAEC,OAAmB,OAEfC,QAAUC,SAASC,cAAc,OACvCF,QAAQG,UAAYL,EAAEM,QAGtBJ,QAAQK,iBAAiB,2BAA2BC,SAAQC,KAAOA,IAAIC,WAGvER,QAAQK,iBAAiB,iCAAiCC,SAAQG,gBACxDC,OAASD,QAAQE,cAAc,UACjCD,QACAD,QAAQG,WAAWC,aAAaH,OAAQD,SAE5CA,QAAQD,YAIZR,QAAQK,iBAAiB,2BAA2BC,SAAQG,gBAClDC,OAASD,QAAQE,cAAc,UACjCD,QACAD,QAAQG,WAAWC,aAAaH,OAAQD,SAE5CA,QAAQD,YAGZV,EAAEM,QAAUJ,QAAQG,cAIrBjB,kBAIX4B,QAAQ,WAAIxB,6BAAoByB"}
|
||||||
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js
vendored
Executable file
3
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js
vendored
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
define("tiny_mediacms/selectors",["exports"],(function(_exports){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0;return _exports.default={IMAGE:{actions:{submit:".tiny_imagecms_urlentrysubmit",imageBrowser:".openimagecmsbrowser",addUrl:".tiny_imagecms_addurl",deleteImage:".tiny_imagecms_deleteicon"},elements:{form:"form.tiny_imagecms_form",alignSettings:".tiny_imagecms_button",alt:".tiny_imagecms_altentry",altWarning:".tiny_imagecms_altwarning",height:".tiny_imagecms_heightentry",width:".tiny_imagecms_widthentry",url:".tiny_imagecms_urlentry",urlWarning:".tiny_imagecms_urlwarning",size:".tiny_imagecms_size",presentation:".tiny_imagecms_presentation",constrain:".tiny_imagecms_constrain",customStyle:".tiny_imagecms_customstyle",preview:".tiny_imagecms_preview",previewBox:".tiny_imagecms_preview_box",loaderIcon:".tiny_imagecms_loader",loaderIconContainer:".tiny_imagecms_loader_container",insertImage:".tiny_imagecms_insert_image",modalFooter:".modal-footer",dropzoneContainer:".tiny_imagecms_dropzone_container",fileInput:"#tiny_imagecms_fileinput",fileNameLabel:".tiny_imagecms_filename",sizeOriginal:".tiny_imagecms_sizeoriginal",sizeCustom:".tiny_imagecms_sizecustom",properties:".tiny_imagecms_properties"},styles:{responsive:"img-fluid"}},EMBED:{actions:{submit:".tiny_mediacms_submit",mediaBrowser:".openmediacmsbrowser"},elements:{form:"form.tiny_mediacms_form",source:".tiny_mediacms_source",track:".tiny_mediacms_track",mediaSource:".tiny_mediacms_media_source",linkSource:".tiny_mediacms_link_source",linkSize:".tiny_mediacms_link_size",posterSource:".tiny_mediacms_poster_source",posterSize:".tiny_mediacms_poster_size",displayOptions:".tiny_mediacms_display_options",name:".tiny_mediacms_name_entry",title:".tiny_mediacms_title_entry",url:".tiny_mediacms_url_entry",width:".tiny_mediacms_width_entry",height:".tiny_mediacms_height_entry",trackSource:".tiny_mediacms_track_source",trackKind:".tiny_mediacms_track_kind_entry",trackLabel:".tiny_mediacms_track_label_entry",trackLang:".tiny_mediacms_track_lang_entry",trackDefault:".tiny_mediacms_track_default",mediaControl:".tiny_mediacms_controls",mediaAutoplay:".tiny_mediacms_autoplay",mediaMute:".tiny_mediacms_mute",mediaLoop:".tiny_mediacms_loop",advancedSettings:".tiny_mediacms_advancedsettings",linkTab:'li[data-medium-type="link"]',videoTab:'li[data-medium-type="video"]',audioTab:'li[data-medium-type="audio"]',linkPane:'.tab-pane[data-medium-type="link"]',videoPane:'.tab-pane[data-medium-type="video"]',audioPane:'.tab-pane[data-medium-type="audio"]',trackSubtitlesTab:'li[data-track-kind="subtitles"]',trackCaptionsTab:'li[data-track-kind="captions"]',trackDescriptionsTab:'li[data-track-kind="descriptions"]',trackChaptersTab:'li[data-track-kind="chapters"]',trackMetadataTab:'li[data-track-kind="metadata"]',trackSubtitlesPane:'.tab-pane[data-track-kind="subtitles"]',trackCaptionsPane:'.tab-pane[data-track-kind="captions"]',trackDescriptionsPane:'.tab-pane[data-track-kind="descriptions"]',trackChaptersPane:'.tab-pane[data-track-kind="chapters"]',trackMetadataPane:'.tab-pane[data-track-kind="metadata"]'},mediaTypes:{link:"LINK",video:"VIDEO",audio:"AUDIO"},trackKinds:{subtitles:"SUBTITLES",captions:"CAPTIONS",descriptions:"DESCRIPTIONS",chapters:"CHAPTERS",metadata:"METADATA"}},IFRAME:{actions:{remove:'[data-action="remove"]'},elements:{form:"form.tiny_iframecms_form",url:".tiny_iframecms_url",urlWarning:".tiny_iframecms_url_warning",showTitle:".tiny_iframecms_showtitle",linkTitle:".tiny_iframecms_linktitle",showRelated:".tiny_iframecms_showrelated",showUserAvatar:".tiny_iframecms_showuseravatar",responsive:".tiny_iframecms_responsive",startAt:".tiny_iframecms_startat",startAtEnabled:".tiny_iframecms_startat_enabled",aspectRatio:".tiny_iframecms_aspectratio",width:".tiny_iframecms_width",height:".tiny_iframecms_height",preview:".tiny_iframecms_preview",previewContainer:".tiny_iframecms_preview_container",tabs:".tiny_iframecms_tabs",tabUrlBtn:".tiny_iframecms_tab_url_btn",tabIframeLibraryBtn:".tiny_iframecms_tab_iframe_library_btn",paneUrl:".tiny_iframecms_pane_url",paneIframeLibrary:".tiny_iframecms_pane_iframe_library",iframeLibraryContainer:".tiny_iframecms_iframe_library_container",iframeLibraryPlaceholder:".tiny_iframecms_iframe_library_placeholder",iframeLibraryLoading:".tiny_iframecms_iframe_library_loading",iframeLibraryFrame:".tiny_iframecms_iframe_library_frame"},aspectRatios:{"16:9":{width:560,height:315},"4:3":{width:560,height:420},"1:1":{width:400,height:400},custom:null}}},_exports.default}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=selectors.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/selectors.min.js.map
Executable file
File diff suppressed because one or more lines are too long
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/usedfiles.min.js
vendored
Executable file
10
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/usedfiles.min.js
vendored
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
define("tiny_mediacms/usedfiles",["exports","core/templates","core/config"],(function(_exports,Templates,_config){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,Templates=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}
|
||||||
|
/**
|
||||||
|
* Tiny Media Manager usedfiles.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/usedfiles
|
||||||
|
* @copyright 2022, Stevani Andolo <stevani@hotmail.com.au>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/(Templates),_config=(obj=_config)&&obj.__esModule?obj:{default:obj};class UsedFileManager{constructor(files,userContext,itemId,elementId){this.files=files,this.userContext=userContext,this.itemId=itemId,this.elementId=elementId}getElementId(){return this.elementId}getUsedFiles(){const editor=window.parent.tinymce.EditorManager.get(this.getElementId());if(!editor)return window.console.error("Editor not found for ".concat(this.getElementId())),[];const content=editor.getContent(),baseUrl="".concat(_config.default.wwwroot,"/draftfile.php/").concat(this.userContext,"/user/draft/").concat(this.itemId,"/"),pattern=new RegExp("[\"']"+baseUrl.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")+"(?<filename>.+?)[\\?\"']","gm");return[...content.matchAll(pattern)].map((match=>decodeURIComponent(match.groups.filename)))}findUnusedFiles(usedFiles){return Object.entries(this.files).filter((_ref=>{let[filename]=_ref;return!usedFiles.includes(filename)})).map((_ref2=>{let[filename]=_ref2;return filename}))}findMissingFiles(usedFiles){return usedFiles.filter((filename=>!this.files.hasOwnProperty(filename)))}updateFiles(){const form=document.querySelector("form"),usedFiles=this.getUsedFiles(),unusedFiles=this.findUnusedFiles(usedFiles),missingFiles=this.findMissingFiles(usedFiles);return form.querySelectorAll('input[type=checkbox][name^="deletefile"]').forEach((checkbox=>{unusedFiles.includes(checkbox.dataset.filename)||checkbox.closest(".fitem").remove()})),form.classList.toggle("has-missing-files",!!missingFiles.length),form.classList.toggle("has-unused-files",!!unusedFiles.length),Templates.renderForPromise("tiny_mediacms/missingfiles",{missingFiles:missingFiles}).then((_ref3=>{let{html:html,js:js}=_ref3;Templates.replaceNodeContents(form.querySelector(".missing-files"),html,js)}))}}_exports.init=(files,usercontext,itemid,elementid)=>{const manager=new UsedFileManager(files,usercontext,itemid,elementid);return manager.updateFiles(),manager}}));
|
||||||
|
|
||||||
|
//# sourceMappingURL=usedfiles.min.js.map
|
||||||
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/usedfiles.min.js.map
Executable file
1
lms-plugins/mediacms-moodle/tiny/mediacms/amd/build/usedfiles.min.js.map
Executable file
File diff suppressed because one or more lines are too long
265
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/autoconvert.js
Executable file
265
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/autoconvert.js
Executable file
@ -0,0 +1,265 @@
|
|||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny MediaCMS Auto-convert module.
|
||||||
|
*
|
||||||
|
* This module automatically converts pasted MediaCMS URLs into embedded videos.
|
||||||
|
* When a user pastes a MediaCMS video URL (e.g., https://deic.mediacms.io/view?m=JpBd1Zvdl),
|
||||||
|
* it will be automatically converted to an iframe embed.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/autoconvert
|
||||||
|
* @copyright 2024
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getData} from './options';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression patterns for MediaCMS URLs.
|
||||||
|
* Matches URLs like:
|
||||||
|
* - https://deic.mediacms.io/view?m=JpBd1Zvdl
|
||||||
|
* - https://example.mediacms.io/view?m=VIDEO_ID
|
||||||
|
* - Custom domains configured in the plugin
|
||||||
|
*/
|
||||||
|
const MEDIACMS_VIEW_URL_PATTERN = /^(https?:\/\/[^\/]+)\/view\?m=([a-zA-Z0-9_-]+)$/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a string is a valid MediaCMS view URL.
|
||||||
|
*
|
||||||
|
* @param {string} text - The text to check
|
||||||
|
* @returns {Object|null} - Parsed URL info or null if not a valid MediaCMS URL
|
||||||
|
*/
|
||||||
|
const parseMediaCMSUrl = (text) => {
|
||||||
|
if (!text || typeof text !== 'string') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmed = text.trim();
|
||||||
|
|
||||||
|
// Check for MediaCMS view URL pattern
|
||||||
|
const match = trimmed.match(MEDIACMS_VIEW_URL_PATTERN);
|
||||||
|
if (match) {
|
||||||
|
return {
|
||||||
|
baseUrl: match[1],
|
||||||
|
videoId: match[2],
|
||||||
|
originalUrl: trimmed,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the pasted URL's domain is allowed based on configuration.
|
||||||
|
*
|
||||||
|
* @param {Object} parsed - Parsed URL info
|
||||||
|
* @param {Object} config - Plugin configuration
|
||||||
|
* @returns {boolean} - True if the domain is allowed
|
||||||
|
*/
|
||||||
|
const isDomainAllowed = (parsed, config) => {
|
||||||
|
// If no specific base URL is configured, allow all MediaCMS domains
|
||||||
|
const configuredBaseUrl = config.autoConvertBaseUrl || config.mediacmsBaseUrl;
|
||||||
|
if (!configuredBaseUrl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the URL's base matches the configured base URL
|
||||||
|
try {
|
||||||
|
const configuredUrl = new URL(configuredBaseUrl);
|
||||||
|
const pastedUrl = new URL(parsed.baseUrl);
|
||||||
|
return configuredUrl.host === pastedUrl.host;
|
||||||
|
} catch (e) {
|
||||||
|
// If URL parsing fails, allow the conversion
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the iframe embed HTML for a MediaCMS video.
|
||||||
|
*
|
||||||
|
* @param {Object} parsed - Parsed URL info
|
||||||
|
* @param {Object} options - Embed options
|
||||||
|
* @returns {string} - The iframe HTML
|
||||||
|
*/
|
||||||
|
const generateEmbedHtml = (parsed, options = {}) => {
|
||||||
|
// Build the embed URL with default options
|
||||||
|
const embedUrl = new URL(`${parsed.baseUrl}/embed`);
|
||||||
|
embedUrl.searchParams.set('m', parsed.videoId);
|
||||||
|
|
||||||
|
// Apply default options (all enabled by default for best user experience)
|
||||||
|
embedUrl.searchParams.set('showTitle', options.showTitle !== false ? '1' : '0');
|
||||||
|
embedUrl.searchParams.set('showRelated', options.showRelated !== false ? '1' : '0');
|
||||||
|
embedUrl.searchParams.set('showUserAvatar', options.showUserAvatar !== false ? '1' : '0');
|
||||||
|
embedUrl.searchParams.set('linkTitle', options.linkTitle !== false ? '1' : '0');
|
||||||
|
|
||||||
|
// Generate clean iframe HTML (wrapper will be added by editor for UI, then stripped on save)
|
||||||
|
const html = `<iframe ` +
|
||||||
|
`width="400" height="300" ` +
|
||||||
|
`style="display: block; border: 0;" ` +
|
||||||
|
`src="${embedUrl.toString()}" ` +
|
||||||
|
`allowfullscreen="allowfullscreen">` +
|
||||||
|
`</iframe>`;
|
||||||
|
|
||||||
|
return html;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up auto-conversion for the editor.
|
||||||
|
* This registers event handlers to detect pasted MediaCMS URLs.
|
||||||
|
*
|
||||||
|
* @param {TinyMCE} editor - The TinyMCE editor instance
|
||||||
|
*/
|
||||||
|
export const setupAutoConvert = (editor) => {
|
||||||
|
const config = getData(editor) || {};
|
||||||
|
|
||||||
|
// Check if auto-convert is enabled (default: true)
|
||||||
|
if (config.autoConvertEnabled === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle paste events
|
||||||
|
editor.on('paste', (e) => {
|
||||||
|
handlePasteEvent(editor, e, config);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also handle input events for drag-and-drop text or keyboard paste
|
||||||
|
editor.on('input', (e) => {
|
||||||
|
handleInputEvent(editor, e, config);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle paste events to detect and convert MediaCMS URLs.
|
||||||
|
*
|
||||||
|
* @param {TinyMCE} editor - The TinyMCE editor instance
|
||||||
|
* @param {Event} e - The paste event
|
||||||
|
* @param {Object} config - Plugin configuration
|
||||||
|
*/
|
||||||
|
const handlePasteEvent = (editor, e, config) => {
|
||||||
|
// Get pasted text from clipboard
|
||||||
|
const clipboardData = e.clipboardData || window.clipboardData;
|
||||||
|
if (!clipboardData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get plain text first
|
||||||
|
const text = clipboardData.getData('text/plain') || clipboardData.getData('text');
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's a MediaCMS URL
|
||||||
|
const parsed = parseMediaCMSUrl(text);
|
||||||
|
if (!parsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if domain is allowed
|
||||||
|
if (!isDomainAllowed(parsed, config)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent default paste behavior
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Generate and insert the embed HTML
|
||||||
|
const embedHtml = generateEmbedHtml(parsed, config.autoConvertOptions || {});
|
||||||
|
|
||||||
|
// Use a slight delay to ensure the editor is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
editor.insertContent(embedHtml);
|
||||||
|
// Move cursor after the inserted content
|
||||||
|
editor.selection.collapse(false);
|
||||||
|
}, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle input events to catch URLs that might have been pasted without triggering paste event.
|
||||||
|
* This is a fallback for certain browsers/scenarios.
|
||||||
|
*
|
||||||
|
* @param {TinyMCE} editor - The TinyMCE editor instance
|
||||||
|
* @param {Event} e - The input event
|
||||||
|
* @param {Object} config - Plugin configuration
|
||||||
|
*/
|
||||||
|
const handleInputEvent = (editor, e, config) => {
|
||||||
|
// Only process inputType 'insertFromPaste' if paste event didn't catch it
|
||||||
|
if (e.inputType !== 'insertFromPaste' && e.inputType !== 'insertText') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current node and check if it contains just a URL
|
||||||
|
const node = editor.selection.getNode();
|
||||||
|
if (!node || node.nodeName !== 'P') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the paragraph contains only a MediaCMS URL
|
||||||
|
const text = node.textContent || '';
|
||||||
|
const parsed = parseMediaCMSUrl(text);
|
||||||
|
|
||||||
|
if (!parsed || !isDomainAllowed(parsed, config)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't convert if there's other content in the paragraph
|
||||||
|
const trimmedHtml = node.innerHTML.trim();
|
||||||
|
if (trimmedHtml !== text.trim() && !trimmedHtml.startsWith(text.trim())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the embed HTML
|
||||||
|
const embedHtml = generateEmbedHtml(parsed, config.autoConvertOptions || {});
|
||||||
|
|
||||||
|
// Replace the paragraph content with the embed
|
||||||
|
// Use a slight delay to let the input event complete
|
||||||
|
setTimeout(() => {
|
||||||
|
// Re-check that the node still contains the URL (user might have typed more)
|
||||||
|
const currentText = node.textContent || '';
|
||||||
|
const currentParsed = parseMediaCMSUrl(currentText);
|
||||||
|
|
||||||
|
if (currentParsed && currentParsed.originalUrl === parsed.originalUrl) {
|
||||||
|
// Select and replace the entire node
|
||||||
|
editor.selection.select(node);
|
||||||
|
editor.insertContent(embedHtml);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a text is a MediaCMS URL (public helper).
|
||||||
|
*
|
||||||
|
* @param {string} text - The text to check
|
||||||
|
* @returns {boolean} - True if it's a MediaCMS URL
|
||||||
|
*/
|
||||||
|
export const isMediaCMSUrl = (text) => {
|
||||||
|
return parseMediaCMSUrl(text) !== null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a MediaCMS URL to embed HTML (public helper).
|
||||||
|
*
|
||||||
|
* @param {string} url - The MediaCMS URL
|
||||||
|
* @param {Object} options - Embed options
|
||||||
|
* @returns {string|null} - The embed HTML or null if not a valid URL
|
||||||
|
*/
|
||||||
|
export const convertToEmbed = (url, options = {}) => {
|
||||||
|
const parsed = parseMediaCMSUrl(url);
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return generateEmbedHtml(parsed, options);
|
||||||
|
};
|
||||||
282
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/commands.js
Executable file
282
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/commands.js
Executable file
@ -0,0 +1,282 @@
|
|||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny Media commands.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/commands
|
||||||
|
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {getStrings} from 'core/str';
|
||||||
|
import {
|
||||||
|
component,
|
||||||
|
iframeButtonName,
|
||||||
|
iframeMenuItemName,
|
||||||
|
iframeIcon,
|
||||||
|
} from './common';
|
||||||
|
import IframeEmbed from './iframeembed';
|
||||||
|
import {getButtonImage} from 'editor_tiny/utils';
|
||||||
|
|
||||||
|
const isIframe = (node) => node.nodeName.toLowerCase() === 'iframe' ||
|
||||||
|
(node.classList && node.classList.contains('tiny-iframe-responsive')) ||
|
||||||
|
(node.classList && node.classList.contains('tiny-mediacms-iframe-wrapper'));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap iframes with overlay containers that allow hover detection.
|
||||||
|
* Since iframes capture mouse events, we add an invisible overlay on top
|
||||||
|
* that shows the edit button on hover.
|
||||||
|
*
|
||||||
|
* @param {TinyMCE} editor - The editor instance
|
||||||
|
* @param {Function} handleIframeAction - The action to perform when clicking the button
|
||||||
|
*/
|
||||||
|
const setupIframeOverlays = (editor, handleIframeAction) => {
|
||||||
|
/**
|
||||||
|
* Process all iframes in the editor and add overlay wrappers.
|
||||||
|
*/
|
||||||
|
const processIframes = () => {
|
||||||
|
const editorBody = editor.getBody();
|
||||||
|
if (!editorBody) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iframes = editorBody.querySelectorAll('iframe');
|
||||||
|
iframes.forEach((iframe) => {
|
||||||
|
// Skip if already wrapped
|
||||||
|
if (iframe.parentElement?.classList.contains('tiny-mediacms-iframe-wrapper')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip TinyMCE internal iframes
|
||||||
|
if (iframe.hasAttribute('data-mce-object') || iframe.hasAttribute('data-mce-placeholder')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create wrapper div
|
||||||
|
const wrapper = editor.getDoc().createElement('div');
|
||||||
|
wrapper.className = 'tiny-mediacms-iframe-wrapper';
|
||||||
|
wrapper.setAttribute('contenteditable', 'false');
|
||||||
|
|
||||||
|
// Create edit button (positioned inside wrapper, over the iframe)
|
||||||
|
const editBtn = editor.getDoc().createElement('button');
|
||||||
|
editBtn.className = 'tiny-mediacms-edit-btn';
|
||||||
|
editBtn.setAttribute('type', 'button');
|
||||||
|
editBtn.setAttribute('title', 'Edit video embed options');
|
||||||
|
// Use clean inline SVG to avoid TinyMCE wrapper issues
|
||||||
|
editBtn.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
|
||||||
|
'<circle cx="50" cy="50" r="48" fill="#2EAF5A"/>' +
|
||||||
|
'<polygon points="38,28 38,72 75,50" fill="#FFFFFF"/>' +
|
||||||
|
'</svg>';
|
||||||
|
|
||||||
|
// Wrap the iframe: insert wrapper, move iframe into it, add button
|
||||||
|
iframe.parentNode.insertBefore(wrapper, iframe);
|
||||||
|
wrapper.appendChild(iframe);
|
||||||
|
wrapper.appendChild(editBtn);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add CSS styles for hover effects to the editor's document.
|
||||||
|
*/
|
||||||
|
const addStyles = () => {
|
||||||
|
const editorDoc = editor.getDoc();
|
||||||
|
if (!editorDoc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if styles already added
|
||||||
|
if (editorDoc.getElementById('tiny-mediacms-overlay-styles')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const style = editorDoc.createElement('style');
|
||||||
|
style.id = 'tiny-mediacms-overlay-styles';
|
||||||
|
style.textContent = `
|
||||||
|
.tiny-mediacms-iframe-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
.tiny-mediacms-iframe-wrapper iframe {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.tiny-mediacms-edit-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 48px;
|
||||||
|
left: 6px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 10;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.35);
|
||||||
|
transition: transform 0.15s, box-shadow 0.15s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.tiny-mediacms-edit-btn:hover {
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: 0 3px 10px rgba(0,0,0,0.45);
|
||||||
|
}
|
||||||
|
.tiny-mediacms-edit-btn svg {
|
||||||
|
width: 18px !important;
|
||||||
|
height: 18px !important;
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
editorDoc.head.appendChild(style);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle click on the edit button.
|
||||||
|
*
|
||||||
|
* @param {Event} e - The click event
|
||||||
|
*/
|
||||||
|
const handleOverlayClick = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
|
||||||
|
// Check if clicked on edit button or its child (svg/path)
|
||||||
|
const editBtn = target.closest('.tiny-mediacms-edit-btn');
|
||||||
|
if (!editBtn) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
// Find the associated wrapper and iframe
|
||||||
|
const wrapper = editBtn.closest('.tiny-mediacms-iframe-wrapper');
|
||||||
|
if (!wrapper) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const iframe = wrapper.querySelector('iframe');
|
||||||
|
if (!iframe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the wrapper so TinyMCE knows which element is selected
|
||||||
|
editor.selection.select(wrapper);
|
||||||
|
|
||||||
|
// Open the edit dialog
|
||||||
|
handleIframeAction();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Setup on editor init
|
||||||
|
editor.on('init', () => {
|
||||||
|
addStyles();
|
||||||
|
processIframes();
|
||||||
|
|
||||||
|
// Handle clicks on the overlay
|
||||||
|
editor.getBody().addEventListener('click', handleOverlayClick);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-process when content changes
|
||||||
|
editor.on('SetContent', () => {
|
||||||
|
processIframes();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-process when content is pasted
|
||||||
|
editor.on('PastePostProcess', () => {
|
||||||
|
setTimeout(processIframes, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-process after undo/redo
|
||||||
|
editor.on('Undo Redo', () => {
|
||||||
|
processIframes();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-process on any content change (covers modal updates)
|
||||||
|
editor.on('Change', () => {
|
||||||
|
setTimeout(processIframes, 50);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-process when node changes (selection changes)
|
||||||
|
editor.on('NodeChange', () => {
|
||||||
|
processIframes();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const registerIframeCommand = (editor, iframeButtonText, iframeButtonImage) => {
|
||||||
|
const handleIframeAction = () => {
|
||||||
|
const iframeEmbed = new IframeEmbed(editor);
|
||||||
|
iframeEmbed.displayDialogue();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register the iframe icon
|
||||||
|
editor.ui.registry.addIcon(iframeIcon, iframeButtonImage.html);
|
||||||
|
|
||||||
|
// Register the Menu Button as a toggle.
|
||||||
|
// This means that when highlighted over an existing iframe element it will show as toggled on.
|
||||||
|
editor.ui.registry.addToggleButton(iframeButtonName, {
|
||||||
|
icon: iframeIcon,
|
||||||
|
tooltip: iframeButtonText,
|
||||||
|
onAction: handleIframeAction,
|
||||||
|
onSetup: api => {
|
||||||
|
return editor.selection.selectorChangedWithUnbind(
|
||||||
|
'iframe:not([data-mce-object]):not([data-mce-placeholder]),.tiny-iframe-responsive,.tiny-mediacms-iframe-wrapper',
|
||||||
|
api.setActive
|
||||||
|
).unbind;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.ui.registry.addMenuItem(iframeMenuItemName, {
|
||||||
|
icon: iframeIcon,
|
||||||
|
text: iframeButtonText,
|
||||||
|
onAction: handleIframeAction,
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.ui.registry.addContextToolbar(iframeButtonName, {
|
||||||
|
predicate: isIframe,
|
||||||
|
items: iframeButtonName,
|
||||||
|
position: 'node',
|
||||||
|
scope: 'node'
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.ui.registry.addContextMenu(iframeButtonName, {
|
||||||
|
update: isIframe,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup iframe overlays with edit button on hover
|
||||||
|
setupIframeOverlays(editor, handleIframeAction);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSetup = async() => {
|
||||||
|
const [
|
||||||
|
iframeButtonText,
|
||||||
|
] = await getStrings([
|
||||||
|
'iframebuttontitle',
|
||||||
|
].map((key) => ({key, component})));
|
||||||
|
|
||||||
|
const [
|
||||||
|
iframeButtonImage,
|
||||||
|
] = await Promise.all([
|
||||||
|
getButtonImage('icon', component),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Note: The function returned here must be synchronous and cannot use promises.
|
||||||
|
// All promises must be resolved prior to returning the function.
|
||||||
|
return (editor) => {
|
||||||
|
registerIframeCommand(editor, iframeButtonText, iframeButtonImage);
|
||||||
|
};
|
||||||
|
};
|
||||||
30
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/common.js
Executable file
30
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/common.js
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny Media common values.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/common
|
||||||
|
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default {
|
||||||
|
pluginName: 'tiny_mediacms/plugin',
|
||||||
|
component: 'tiny_mediacms',
|
||||||
|
iframeButtonName: 'tiny_mediacms_iframe',
|
||||||
|
iframeMenuItemName: 'tiny_mediacms_iframe',
|
||||||
|
iframeIcon: 'tiny_mediacms_iframe',
|
||||||
|
};
|
||||||
60
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/configuration.js
Executable file
60
lms-plugins/mediacms-moodle/tiny/mediacms/amd/src/configuration.js
Executable file
@ -0,0 +1,60 @@
|
|||||||
|
// This file is part of Moodle - http://moodle.org/
|
||||||
|
//
|
||||||
|
// Moodle is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Moodle is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tiny Media configuration.
|
||||||
|
*
|
||||||
|
* @module tiny_mediacms/configuration
|
||||||
|
* @copyright 2022 Huong Nguyen <huongnv13@gmail.com>
|
||||||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
iframeButtonName,
|
||||||
|
iframeMenuItemName,
|
||||||
|
} from './common';
|
||||||
|
import {
|
||||||
|
addContextmenuItem,
|
||||||
|
} from 'editor_tiny/utils';
|
||||||
|
|
||||||
|
const configureMenu = (menu) => {
|
||||||
|
// Add the Iframe Embed to the insert menu.
|
||||||
|
menu.insert.items = `${iframeMenuItemName} ${menu.insert.items}`;
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
};
|
||||||
|
|
||||||
|
const configureToolbar = (toolbar) => {
|
||||||
|
// The toolbar contains an array of named sections.
|
||||||
|
// The Moodle integration ensures that there is a section called 'content'.
|
||||||
|
|
||||||
|
return toolbar.map((section) => {
|
||||||
|
if (section.name === 'content') {
|
||||||
|
// Insert the iframe button at the start of it.
|
||||||
|
section.items.unshift(iframeButtonName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return section;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const configure = (instanceConfig) => {
|
||||||
|
// Update the instance configuration to add the Iframe Embed menu option to the menus and toolbars.
|
||||||
|
return {
|
||||||
|
contextmenu: addContextmenuItem(instanceConfig.contextmenu, iframeButtonName),
|
||||||
|
menu: configureMenu(instanceConfig.menu),
|
||||||
|
toolbar: configureToolbar(instanceConfig.toolbar),
|
||||||
|
};
|
||||||
|
};
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user