mirror of
https://github.com/mediacms-io/mediacms.git
synced 2026-06-06 17:13:02 -04:00
feat: LTI support and Moodle plugin
This commit is contained in:
+303
-224
@@ -1,224 +1,303 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% load static %}
|
||||
|
||||
{% block headtitle %}Add new media - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block externallinks %}
|
||||
{% if LOAD_FROM_CDN %}
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/file-uploader/5.13.0/fine-uploader.min.js" rel="preload" as="script">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/file-uploader/5.13.0/fine-uploader.min.js"></script>
|
||||
{% else %}
|
||||
<link href="{% static "lib/file-uploader/5.13.0/fine-uploader.min.js" %}" rel="preload" as="script">
|
||||
<script src="{% static "lib/file-uploader/5.13.0/fine-uploader.min.js" %}"></script>
|
||||
{% endif %}
|
||||
{% endblock externallinks %}
|
||||
|
||||
{% block topimports %}
|
||||
<link href="{% static "css/add-media.css" %}" rel="preload" as="style">
|
||||
<link href="{% static "css/add-media.css" %}" rel="stylesheet">
|
||||
{%endblock topimports %}
|
||||
|
||||
{% block innercontent %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
|
||||
{% if can_add %}
|
||||
|
||||
<div class="media-uploader-wrap">
|
||||
<div class="media-uploader-top-wrap">
|
||||
<div class="media-uploader-top-left-wrap">
|
||||
<h1>{{ "Upload media" | custom_translate:LANGUAGE_CODE}}</h1>
|
||||
</div>
|
||||
<div class="media-uploader-top-right-wrap"> </div>
|
||||
</div>
|
||||
<script type="text/template" id="qq-template">
|
||||
<div class="media-uploader-bottom-wrap qq-uploader-selector">
|
||||
<div class="media-uploader-bottom-left-wrap">
|
||||
<div class="media-drag-drop-wrap">
|
||||
<div class="media-drag-drop-inner" qq-drop-area-text="Drop files here">
|
||||
<div class="media-drag-drop-content">
|
||||
<div class="media-drag-drop-content-inner">
|
||||
<span><i class="material-icons">cloud_upload</i></span>
|
||||
<span>{{ "Drag and drop files" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
<span>{{ "or" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
<span class="browse-files-btn-wrap">
|
||||
<span class="qq-upload-button-selector">{{ "Browse your files" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
</span>
|
||||
|
||||
<div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-uploader-bottom-right-wrap">
|
||||
<ul class="media-upload-items-list qq-upload-list-selector">
|
||||
<li>
|
||||
<div class="media-upload-item-main">
|
||||
<div class="media-upload-item-thumb">
|
||||
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale alt="" />
|
||||
<span class="media-upload-item-spinner qq-upload-spinner-selector"><i class="material-icons">autorenew</i></span>
|
||||
<button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry"><i class="material-icons">refresh</i> Retry</button>
|
||||
</div>
|
||||
<div class="media-upload-item-details">
|
||||
<div class="media-upload-item-name">
|
||||
<span class="media-upload-item-filename qq-upload-file-selector"></span>
|
||||
<input class="media-upload-item-filename-input qq-edit-filename-selector" tab-index="0" type="text" />
|
||||
</div>
|
||||
<div class="media-upload-item-details-bottom">
|
||||
<div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="media-upload-item-progress-bar qq-progress-bar-selector"></div>
|
||||
</div>
|
||||
<span class="media-upload-item-upload-size qq-upload-size-selector"></span>
|
||||
<span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"></span>
|
||||
</div>
|
||||
<div class="media-upload-item-top-actions">
|
||||
<span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename">Edit filename <i class="material-icons">create</i></span>
|
||||
<button type="button" class="delete-media-upload-item qq-upload-delete-selector" aria-label="Delete">Delete <i class="material-icons">delete</i></button>
|
||||
<button type="button" class="cancel-media-upload-item qq-upload-cancel-selector" aria-label="Cancel">Cancel <i class="material-icons">cancel</i></button>
|
||||
<a href="#" class="view-uploaded-media-link qq-hide" target="_blank">{{ "View media" | custom_translate:LANGUAGE_CODE}}<i class="material-icons">open_in_new</i></a>
|
||||
</div>
|
||||
<div class="media-upload-item-bottom-actions">
|
||||
<button type="button" class="continue-media-upload-item qq-upload-continue-selector" aria-label="Continue"><i class="material-icons">play_circle_outline</i> Continue</button>
|
||||
<button type="button" class="pause-media-upload-item qq-upload-pause-selector" aria-label="Pause"><i class="material-icons">pause_circle_outline</i> Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">CLOSE</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">NO</button>
|
||||
<button type="button" class="qq-ok-button-selector">YES</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">CANCEL</button>
|
||||
<button type="button" class="qq-ok-button-selector">OK</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<div class="media-uploader"></div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
{{can_upload_exp}}
|
||||
|
||||
<br>
|
||||
|
||||
<a href='/contact'>Contact</a> portal owners for more information.
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock innercontent %}
|
||||
|
||||
{% block bottomimports %}
|
||||
<script src="{% static "js/add-media.js" %}?v={{ VERSION }}"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
function getCSRFToken() {
|
||||
var i, cookies, cookie, cookieVal = null;
|
||||
if ( document.cookie && '' !== document.cookie ) {
|
||||
cookies = document.cookie.split(';');
|
||||
i = 0;
|
||||
while( i < cookies.length ){
|
||||
cookie = cookies[i].trim();
|
||||
if ( 'csrftoken=' === cookie.substring(0, 10) ) {
|
||||
cookieVal = decodeURIComponent( cookie.substring(10) );
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return cookieVal;
|
||||
}
|
||||
var default_concurrent_chunked_uploader = new qq.FineUploader({
|
||||
debug: false,
|
||||
element: document.querySelector('.media-uploader'),
|
||||
request: {
|
||||
endpoint: '{% url 'uploader:upload' %}',
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCSRFToken('csrftoken'),
|
||||
},
|
||||
},
|
||||
retry: {
|
||||
enableAuto: true,
|
||||
maxAutoAttempts: 2,
|
||||
},
|
||||
validation: {
|
||||
itemLimit: {{UPLOAD_MAX_FILES_NUMBER}},
|
||||
sizeLimit: {{UPLOAD_MAX_SIZE}},
|
||||
},
|
||||
chunking: {
|
||||
enabled: true,
|
||||
concurrent: {
|
||||
enabled: true,
|
||||
},
|
||||
success: {
|
||||
endpoint: '{% url 'uploader:upload' %}?done',
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
onError: function(id, name, errorReason, xhrOrXdr) {
|
||||
console.warn(qq.format("Error on file number {} - {}. Reason: {}", id, name, errorReason));
|
||||
},
|
||||
onComplete: function( id, name, response, request ) {
|
||||
|
||||
if( response.success ){
|
||||
|
||||
if( response.media_url ) {
|
||||
if( 1 === this._currentItemLimit ) {
|
||||
setTimeout(function(){ window.location.href = response.media_url; }, 500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var listEl = document.querySelector( '.qq-file-id-' + id );
|
||||
var viewFileEl = listEl.querySelector( '.view-uploaded-media-link' );
|
||||
|
||||
if( listEl ){
|
||||
var fileUrl = response.media_url;
|
||||
listEl.style.cursor = 'pointer';
|
||||
listEl.addEventListener( 'click', function(ev){
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
var win = window.open( fileUrl, '_blank' );
|
||||
win.focus();
|
||||
});
|
||||
}
|
||||
|
||||
if( viewFileEl ){
|
||||
viewFileEl.setAttribute( 'href', response.media_url );
|
||||
viewFileEl.setAttribute( 'class', 'view-uploaded-media-link' );
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock bottomimports %}
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load custom_filters %}
|
||||
|
||||
{% block headtitle %}Add new media - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||
|
||||
{% block externallinks %}
|
||||
{% if LOAD_FROM_CDN %}
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/file-uploader/5.13.0/fine-uploader.min.js" rel="preload" as="script">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/file-uploader/5.13.0/fine-uploader.min.js"></script>
|
||||
{% else %}
|
||||
<link href="{% static "lib/file-uploader/5.13.0/fine-uploader.min.js" %}" rel="preload" as="script">
|
||||
<script src="{% static "lib/file-uploader/5.13.0/fine-uploader.min.js" %}"></script>
|
||||
{% endif %}
|
||||
{% endblock externallinks %}
|
||||
|
||||
{% block topimports %}
|
||||
<link href="{% static "css/add-media.css" %}" rel="preload" as="style">
|
||||
<link href="{% static "css/add-media.css" %}" rel="stylesheet">
|
||||
<style>
|
||||
/* LMS Embed Mode specific styling */
|
||||
body.lms-embed-mode {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.media-uploader-wrap.is-lms-embed {
|
||||
max-width: 680px;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
.media-uploader-wrap.is-lms-embed .media-uploader {
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.is-lms-embed .media-uploader-top-wrap {
|
||||
margin-bottom: 10px;
|
||||
padding: 0;
|
||||
}
|
||||
.is-lms-embed .media-uploader-top-left-wrap h1 {
|
||||
display: none;
|
||||
}
|
||||
.is-lms-embed .media-uploader-bottom-wrap {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.is-lms-embed .media-drag-drop-inner {
|
||||
padding: 20px 10px;
|
||||
min-height: 140px;
|
||||
border-width: 2px;
|
||||
}
|
||||
.is-lms-embed .media-drag-drop-content-inner span {
|
||||
font-size: 13px;
|
||||
margin-bottom: 6px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.is-lms-embed .media-drag-drop-content-inner i.material-icons {
|
||||
font-size: 32px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.is-lms-embed .qq-upload-button-selector {
|
||||
padding: 6px 16px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
.is-lms-embed .media-upload-items-list {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
{% endblock topimports %}
|
||||
|
||||
{% block innercontent %}
|
||||
{% get_current_language as LANGUAGE_CODE %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
|
||||
{% if can_add %}
|
||||
|
||||
<div class="media-uploader-wrap" id="media-uploader-wrap">
|
||||
<div class="media-uploader-top-wrap">
|
||||
<div class="media-uploader-top-left-wrap">
|
||||
<h1>{{ "Upload media" | custom_translate:LANGUAGE_CODE}}</h1>
|
||||
</div>
|
||||
<div class="media-uploader-top-right-wrap"> </div>
|
||||
</div>
|
||||
|
||||
<script type="text/template" id="qq-template">
|
||||
<div class="media-uploader-bottom-wrap qq-uploader-selector">
|
||||
<div class="media-uploader-bottom-left-wrap">
|
||||
<div class="media-drag-drop-wrap">
|
||||
<div class="media-drag-drop-inner" qq-drop-area-text="Drop files here">
|
||||
<div class="media-drag-drop-content">
|
||||
<div class="media-drag-drop-content-inner">
|
||||
<span><i class="material-icons">cloud_upload</i></span>
|
||||
<span>{{ "Drag and drop files" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
<span>{{ "or" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
<span class="browse-files-btn-wrap">
|
||||
<span class="qq-upload-button-selector">{{ "Browse your files" | custom_translate:LANGUAGE_CODE}}</span>
|
||||
</span>
|
||||
|
||||
<div class="qq-upload-drop-area-selector media-dropzone" qq-hide-dropzone>
|
||||
<span class="qq-upload-drop-area-text-selector"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="media-uploader-bottom-right-wrap">
|
||||
<ul class="media-upload-items-list qq-upload-list-selector">
|
||||
<li>
|
||||
<div class="media-upload-item-main">
|
||||
<div class="media-upload-item-thumb">
|
||||
<img class="qq-thumbnail-selector" qq-max-size="120" qq-server-scale alt="" />
|
||||
<span class="media-upload-item-spinner qq-upload-spinner-selector"><i class="material-icons">autorenew</i></span>
|
||||
<button type="button" class="qq-upload-retry-selector retry-media-upload-item" aria-label="Retry"><i class="material-icons">refresh</i> Retry</button>
|
||||
</div>
|
||||
<div class="media-upload-item-details">
|
||||
<div class="media-upload-item-name">
|
||||
<span class="media-upload-item-filename qq-upload-file-selector"></span>
|
||||
<input class="media-upload-item-filename-input qq-edit-filename-selector" tab-index="0" type="text" />
|
||||
</div>
|
||||
<div class="media-upload-item-details-bottom">
|
||||
<div class="media-upload-item-progress-bar-container qq-progress-bar-container-selector">
|
||||
<div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" class="media-upload-item-progress-bar qq-progress-bar-selector"></div>
|
||||
</div>
|
||||
<span class="media-upload-item-upload-size qq-upload-size-selector"></span>
|
||||
<span role="status" class="media-upload-item-status-text qq-upload-status-text-selector"></span>
|
||||
</div>
|
||||
<div class="media-upload-item-top-actions">
|
||||
<span class="filename-edit qq-edit-filename-icon-selector" aria-label="Edit filename">Edit filename <i class="material-icons">create</i></span>
|
||||
<button type="button" class="delete-media-upload-item qq-upload-delete-selector" aria-label="Delete">Delete <i class="material-icons">delete</i></button>
|
||||
<button type="button" class="cancel-media-upload-item qq-upload-cancel-selector" aria-label="Cancel">Cancel <i class="material-icons">cancel</i></button>
|
||||
<a href="#" class="view-uploaded-media-link qq-hide">{{ "View media" | custom_translate:LANGUAGE_CODE}}<i class="material-icons">open_in_new</i></a>
|
||||
</div>
|
||||
<div class="media-upload-item-bottom-actions">
|
||||
<button type="button" class="continue-media-upload-item qq-upload-continue-selector" aria-label="Continue"><i class="material-icons">play_circle_outline</i> Continue</button>
|
||||
<button type="button" class="pause-media-upload-item qq-upload-pause-selector" aria-label="Pause"><i class="material-icons">pause_circle_outline</i> Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<dialog class="qq-alert-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">CLOSE</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="qq-confirm-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">NO</button>
|
||||
<button type="button" class="qq-ok-button-selector">YES</button>
|
||||
</div>
|
||||
</dialog>
|
||||
<dialog class="qq-prompt-dialog-selector">
|
||||
<div class="qq-dialog-message-selector"></div>
|
||||
<input type="text">
|
||||
<div class="qq-dialog-buttons">
|
||||
<button type="button" class="qq-cancel-button-selector">CANCEL</button>
|
||||
<button type="button" class="qq-ok-button-selector">OK</button>
|
||||
</div>
|
||||
</dialog>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<div class="media-uploader"></div>
|
||||
</div>
|
||||
|
||||
{% else %}
|
||||
|
||||
{{can_upload_exp}}
|
||||
|
||||
<br>
|
||||
|
||||
<a href='/contact'>Contact</a> portal owners for more information.
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
{% endblock innercontent %}
|
||||
|
||||
{% block bottomimports %}
|
||||
<script src="{% static "js/add-media.js" %}?v={{ VERSION }}"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
var isLmsEmbedMode = sessionStorage.getItem('lms_embed_mode') === 'true' && urlParams.get('action') === 'select_media';
|
||||
|
||||
// Add compact styles if in LMS embed mode
|
||||
if (isLmsEmbedMode) {
|
||||
var wrap = document.getElementById('media-uploader-wrap');
|
||||
if (wrap) {
|
||||
wrap.classList.add('is-lms-embed');
|
||||
}
|
||||
document.body.classList.add('lms-embed-mode');
|
||||
}
|
||||
|
||||
function getCSRFToken() {
|
||||
var i, cookies, cookie, cookieVal = null;
|
||||
if ( document.cookie && '' !== document.cookie ) {
|
||||
cookies = document.cookie.split(';');
|
||||
i = 0;
|
||||
while( i < cookies.length ){
|
||||
cookie = cookies[i].trim();
|
||||
if ( 'csrftoken=' === cookie.substring(0, 10) ) {
|
||||
cookieVal = decodeURIComponent( cookie.substring(10) );
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
return cookieVal;
|
||||
}
|
||||
|
||||
var default_concurrent_chunked_uploader = new qq.FineUploader({
|
||||
debug: true,
|
||||
element: document.querySelector('.media-uploader'),
|
||||
request: {
|
||||
endpoint: '{% url 'uploader:upload' %}',
|
||||
params: {},
|
||||
customHeaders: {
|
||||
'X-CSRFToken': getCSRFToken('csrftoken'),
|
||||
},
|
||||
},
|
||||
retry: {
|
||||
enableAuto: true,
|
||||
maxAutoAttempts: 2,
|
||||
},
|
||||
validation: {
|
||||
itemLimit: isLmsEmbedMode ? 1 : {{UPLOAD_MAX_FILES_NUMBER}},
|
||||
sizeLimit: {{UPLOAD_MAX_SIZE}},
|
||||
},
|
||||
chunking: {
|
||||
enabled: true,
|
||||
concurrent: {
|
||||
enabled: true,
|
||||
},
|
||||
success: {
|
||||
endpoint: '{% url 'uploader:upload' %}?done',
|
||||
},
|
||||
},
|
||||
callbacks: {
|
||||
onError: function(id, name, errorReason, xhrOrXdr) {
|
||||
console.warn(qq.format("Error on file number {} - {}. Reason: {}", id, name, errorReason));
|
||||
},
|
||||
onComplete: function( id, name, response, request ) {
|
||||
|
||||
if ( response.success ) {
|
||||
|
||||
if ( response.media_url ) {
|
||||
if ( 1 === this._currentItemLimit ) {
|
||||
if (isLmsEmbedMode && window.parent !== window) {
|
||||
var mediaUrl = response.media_url;
|
||||
var mediaId = '';
|
||||
if (mediaUrl.indexOf('m=') > -1) {
|
||||
mediaId = mediaUrl.split('m=')[1].split('&')[0];
|
||||
} else {
|
||||
var parts = mediaUrl.split('/').filter(Boolean);
|
||||
mediaId = parts[parts.length - 1];
|
||||
}
|
||||
var baseUrl = window.location.origin;
|
||||
var embedUrl = baseUrl + '/embed?m=' + mediaId;
|
||||
|
||||
var sendPostMsg = function() {
|
||||
window.parent.postMessage({
|
||||
type: 'videoSelected',
|
||||
embedUrl: embedUrl,
|
||||
videoId: mediaId
|
||||
}, '*');
|
||||
};
|
||||
|
||||
sendPostMsg();
|
||||
return;
|
||||
}
|
||||
setTimeout(function(){ window.location.href = response.media_url; }, 500);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var listEl = document.querySelector( '.qq-file-id-' + id );
|
||||
var viewFileEl = listEl.querySelector( '.view-uploaded-media-link' );
|
||||
|
||||
if ( listEl ) {
|
||||
var fileUrl = response.media_url;
|
||||
listEl.style.cursor = 'pointer';
|
||||
listEl.addEventListener( 'click', function(ev) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
window.location.href = fileUrl;
|
||||
});
|
||||
}
|
||||
|
||||
if ( viewFileEl ) {
|
||||
viewFileEl.setAttribute( 'href', response.media_url );
|
||||
viewFileEl.setAttribute( 'class', 'view-uploaded-media-link' );
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock bottomimports %}
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
<div class="form-group{% if field.errors %} has-error{% endif %}">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
mediaId: "{{ media_object.friendly_token }}",
|
||||
redirectURL: "{{ media_object.get_absolute_url }}",
|
||||
redirectUserMediaURL: "{{ media_object.user.get_absolute_url }}",
|
||||
chapters: {{ chapters|safe }},
|
||||
chapters: {{ chapters }},
|
||||
};
|
||||
</script>
|
||||
{%endblock topimports %}
|
||||
|
||||
@@ -6,28 +6,28 @@
|
||||
<div class="media-edit-nav" style="background-color: #f0f0f0; padding: 10px 0; margin-bottom: 20px;">
|
||||
<ul style="list-style: none; display: flex; justify-content: space-around; margin: 0; padding: 0;">
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'edit_media' %}?m={{media_object.friendly_token}}"
|
||||
<a href="{% url 'edit_media' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
style="text-decoration: none; {% if active_tab == 'metadata' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Metadata" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
{% if media_object.media_type == 'video' or media_object.media_type == 'audio' %}
|
||||
{% if media_object.media_type == 'video' or media_object.media_type == 'audio' %}
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'edit_video' %}?m={{media_object.friendly_token}}"
|
||||
<a href="{% url 'edit_video' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
|
||||
style="text-decoration: none; {% if active_tab == 'trim' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Trim" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'add_subtitle' %}?m={{media_object.friendly_token}}"
|
||||
<a href="{% url 'add_subtitle' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
style="text-decoration: none; {% if active_tab == 'subtitles' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Captions" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
<li style="display: inline-block">
|
||||
<a
|
||||
href="{% url 'edit_chapters' %}?m={{media_object.friendly_token}}"
|
||||
href="{% url 'edit_chapters' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
style="text-decoration: none; {% if active_tab == 'chapters' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}"
|
||||
>
|
||||
Chapters
|
||||
@@ -43,19 +43,19 @@
|
||||
</li>
|
||||
{% endcomment %}
|
||||
{% endif %}
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'publish_media' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
style="text-decoration: none; {% if active_tab == 'publish' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Publish" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
{% if ALLOW_MEDIA_REPLACEMENT %}
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'replace_media' %}?m={{media_object.friendly_token}}"
|
||||
<a href="{% url 'replace_media' %}?m={{media_object.friendly_token}}{% if request.GET.mode == 'lms_embed_mode' %}&mode=lms_embed_mode{% endif %}"
|
||||
style="text-decoration: none; {% if active_tab == 'replace' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Replace" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
<li style="display: inline-block;">
|
||||
<a href="{% url 'publish_media' %}?m={{media_object.friendly_token}}"
|
||||
style="text-decoration: none; {% if active_tab == 'publish' %}font-weight: bold; color: #333; padding-bottom: 3px; border-bottom: 2px solid #333;{% else %}color: #666;{% endif %}">
|
||||
{{ "Publish" | custom_translate:LANGUAGE_CODE}}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
<div class="form-group{% if form.state.errors or form.confirm_state.errors %} has-error{% endif %}">
|
||||
<div class="control-label-container">
|
||||
<label class="control-label">State</label>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="state-options">
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'private' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="private"
|
||||
{% if form.state.value == 'private' %}checked{% endif %}>
|
||||
Private
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'unlisted' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="unlisted"
|
||||
{% if form.state.value == 'unlisted' %}checked{% endif %}>
|
||||
Unlisted
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
{% if form.fields.shared %}
|
||||
<label class="state-option shared-option">
|
||||
<input type="checkbox" name="shared" id="id_shared"
|
||||
{% if form.shared.value %}checked{% endif %}>
|
||||
Shared
|
||||
</label>
|
||||
{% endif %}
|
||||
{% for val, lbl in form.fields.state.choices %}{% if val == 'public' %}
|
||||
<label class="state-option">
|
||||
<input type="radio" name="state" value="public"
|
||||
{% if form.state.value == 'public' %}checked{% endif %}>
|
||||
Public
|
||||
</label>
|
||||
{% endif %}{% endfor %}
|
||||
</div>
|
||||
<div id="unlisted-hint" style="display:none; margin-top:0.75rem; font-size:0.875rem; color:#0c5460; padding:0.5rem 0.75rem; background:#d1ecf1; border:1px solid #bee5eb; border-radius:4px;">
|
||||
To publish an external link to the media, click Publish Media below to view the media, right-click the viewing window, next copy URL or embed code, and add this to your external portal or client.
|
||||
</div>
|
||||
{% if form.state.errors %}
|
||||
<div class="error-container" style="margin-top:0.5rem;">
|
||||
{% for error in form.state.errors %}<p class="invalid-feedback">{{ error }}</p>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if form.fields.shared %}
|
||||
<div id="shared-info" style="display:none; margin-top:0.5rem; font-size:0.875rem; color:#555;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:middle; margin-right:4px; flex-shrink:0;"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>To share media with someone, go to My Media > select media > Bulk Actions > Share with…
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if form.fields.shared %}{% if form.was_shared %}
|
||||
<div id="shared-deselect-warning" style="display:none; margin-top:0.75rem; padding:0.75rem; background:#fff3cd; border:1px solid #ffc107; border-radius:4px;">
|
||||
<label style="display:flex; gap:0.5rem; align-items:flex-start; cursor:pointer; margin:0;">
|
||||
<input type="checkbox" name="confirm_state" id="id_confirm_state"
|
||||
{% if form.confirm_state.value %}checked{% endif %}
|
||||
style="margin-top:3px; flex-shrink:0;">
|
||||
<span id="shared-deselect-msg-private">I understand that changing to Private will remove all sharing. Currently this media is shared by me with other users (visible in 'My Media > Shared by Me' page).</span>
|
||||
<span id="shared-deselect-msg-other" style="display:none;">I understand that unchecking Shared will affect existing sharing settings.</span>
|
||||
</label>
|
||||
{% if form.confirm_state.errors %}
|
||||
<div style="margin-top:0.5rem;">
|
||||
{% for error in form.confirm_state.errors %}<p class="invalid-feedback" style="color:#dc3545;">{{ error }}</p>{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}{% endif %}
|
||||
{% if form.fields.shared %}
|
||||
<script>
|
||||
(function() {
|
||||
var sharedCb = document.getElementById('id_shared');
|
||||
var warning = document.getElementById('shared-deselect-warning');
|
||||
var sharedInfo = document.getElementById('shared-info');
|
||||
var msgPrivate = document.getElementById('shared-deselect-msg-private');
|
||||
var msgOther = document.getElementById('shared-deselect-msg-other');
|
||||
function getSelectedState() {
|
||||
var radios = document.querySelectorAll('input[name="state"]');
|
||||
for (var i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) return radios[i].value;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
function updateWarning() {
|
||||
var isShared = sharedCb.checked;
|
||||
if (warning) warning.style.display = isShared ? 'none' : 'block';
|
||||
if (sharedInfo) sharedInfo.style.display = isShared ? 'block' : 'none';
|
||||
if (!isShared) {
|
||||
var state = getSelectedState();
|
||||
if (msgPrivate) msgPrivate.style.display = state === 'private' ? 'inline' : 'none';
|
||||
if (msgOther) msgOther.style.display = state !== 'private' ? 'inline' : 'none';
|
||||
}
|
||||
}
|
||||
if (sharedCb) {
|
||||
sharedCb.addEventListener('change', updateWarning);
|
||||
document.querySelectorAll('input[name="state"]').forEach(function(r) {
|
||||
r.addEventListener('change', updateWarning);
|
||||
});
|
||||
updateWarning();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% endif %}
|
||||
<script>
|
||||
(function () {
|
||||
var hint = document.getElementById('unlisted-hint');
|
||||
function updateUnlistedHint() {
|
||||
var radios = document.querySelectorAll('input[name="state"]');
|
||||
var selected = '';
|
||||
for (var i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) { selected = radios[i].value; break; }
|
||||
}
|
||||
if (hint) hint.style.display = selected === 'unlisted' ? 'block' : 'none';
|
||||
}
|
||||
document.querySelectorAll('input[name="state"]').forEach(function (r) {
|
||||
r.addEventListener('change', updateUnlistedHint);
|
||||
});
|
||||
updateUnlistedHint();
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
@@ -1,17 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block headtitle %}Publish media - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||
|
||||
{% block headermeta %}{% endblock headermeta %}
|
||||
|
||||
{% block innercontent %}
|
||||
<div class="user-action-form-wrap">
|
||||
{% include "cms/media_nav.html" with active_tab="publish" %}
|
||||
<div style="max-width: 900px; margin: 0 auto; padding: 20px; border-radius: 8px; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);">
|
||||
{% csrf_token %}
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock innercontent %}
|
||||
{% extends "base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load static %}
|
||||
|
||||
{% block headtitle %}Publish media - {{PORTAL_NAME}}{% endblock headtitle %}
|
||||
|
||||
{% block headermeta %}{% endblock headermeta %}
|
||||
|
||||
{% block extra_head %}
|
||||
<script>
|
||||
(function() {
|
||||
var url = new URL(window.location.href);
|
||||
if (sessionStorage.getItem('lms_embed_mode') === 'true' && url.searchParams.get('mode') !== 'lms_embed_mode') {
|
||||
url.searchParams.set('mode', 'lms_embed_mode');
|
||||
window.location.replace(url.toString());
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<style>
|
||||
.state-options {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
.state-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.state-option input { cursor: pointer; }
|
||||
.shared-option {
|
||||
border-left: 1px solid #dee2e6;
|
||||
padding-left: 1.5rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
.confirm-state-section {
|
||||
margin-top: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: #fff3cd;
|
||||
border: 1px solid #ffc107;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.confirm-state-section label {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.confirm-state-section input[type="checkbox"] {
|
||||
margin-top: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock extra_head %}
|
||||
|
||||
{% block innercontent %}
|
||||
<div class="user-action-form-wrap">
|
||||
{% include "cms/media_nav.html" with active_tab="publish" %}
|
||||
<div style="max-width: 900px; margin: 0 auto; padding: 20px; border-radius: 8px; box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.1);">
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock innercontent %}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{% if can_add %}
|
||||
|
||||
<div class="custom-page-wrapper">
|
||||
<h2>{{ "Record Screen" | custom_translate:LANGUAGE_CODE}}</h2>
|
||||
<h2>{{ "Record Screen with Audio" | custom_translate:LANGUAGE_CODE}}</h2>
|
||||
<hr/>
|
||||
|
||||
<div style="text-align: center; padding: 40px 0;">
|
||||
@@ -60,7 +60,7 @@ document.addEventListener("DOMContentLoaded", function(event) {
|
||||
startBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
document.querySelector('h2').textContent = 'Record Video';
|
||||
document.querySelector('h2').textContent = 'Record Video and Audio';
|
||||
document.querySelector('p').textContent = 'Click \'Start Recording\' to start recording from your camera. Once recording is finished, click \'Stop Recording,\' and the recording will be uploaded.';
|
||||
} else {
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<div style="margin-bottom: 20px; padding: 15px; background-color: #fff3cd; border: 1px solid #ffc107; border-radius: 4px;">
|
||||
<h4 style="margin-top: 0; color: #856404;">⚠️ Important Information</h4>
|
||||
<p style="margin-bottom: 0; color: #856404;">The new file will be processed but all metadata is preserved</p>
|
||||
<p style="margin-bottom: 0; color: #856404;">The new file will be processed but metadata, such as: title, tags, date produced, description, poster image, captions, comments and chapters, will be preserved. Adjust metadata individually, if needed.</p>
|
||||
</div>
|
||||
|
||||
{% csrf_token %}
|
||||
|
||||
@@ -25,6 +25,7 @@ MediaCMS.features = {
|
||||
timestampTimebar: {% if TIMESTAMP_IN_TIMEBAR %}true{% else %}false{% endif %},
|
||||
comment_mention: {% if CAN_MENTION_IN_COMMENTS %}true{% else %}false{% endif %},
|
||||
save: true,
|
||||
allowMediaReplacement: {% if ALLOW_MEDIA_REPLACEMENT %}true{% else %}false{% endif %},
|
||||
},
|
||||
shareOptions: [ 'embed', 'fb', 'tw', 'whatsapp', 'telegram', 'reddit', 'tumblr', 'vk', 'pinterest', 'mix', 'linkedin', 'email' ],
|
||||
},
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Returning to Course...</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.loading {
|
||||
text-align: center;
|
||||
}
|
||||
.spinner {
|
||||
border: 4px solid #f3f3f3;
|
||||
border-top: 4px solid #2196F3;
|
||||
border-radius: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<p>Returning to your course...</p>
|
||||
</div>
|
||||
|
||||
<!-- Auto-submit form that posts JWT back to Moodle -->
|
||||
<form id="deepLinkReturnForm" method="post" action="{{ return_url }}" style="display: none;">
|
||||
<input type="hidden" name="JWT" value="{{ jwt }}">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
// Auto-submit on page load
|
||||
document.getElementById('deepLinkReturnForm').submit();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,94 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>LTI Launch Error</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
max-width: 600px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.error-container {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 30px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #d32f2f;
|
||||
margin-top: 0;
|
||||
}
|
||||
.error-icon {
|
||||
font-size: 48px;
|
||||
color: #d32f2f;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.error-message {
|
||||
background: #ffebee;
|
||||
border-left: 4px solid #d32f2f;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.help-text {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.troubleshooting {
|
||||
background: #e3f2fd;
|
||||
border-left: 4px solid #1976d2;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.troubleshooting h3 {
|
||||
color: #1976d2;
|
||||
margin-top: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
.troubleshooting ol {
|
||||
margin: 10px 0;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.troubleshooting li {
|
||||
margin: 8px 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="error-container">
|
||||
<div class="error-icon">⚠️</div>
|
||||
<h1>{{ error }}</h1>
|
||||
<div class="error-message">
|
||||
<strong>Error Details:</strong><br>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word; font-family: inherit;">{{ message }}</pre>
|
||||
</div>
|
||||
|
||||
{% if 'cookie' in message|lower or 'session' in message|lower or 'Authentication Failed' in error %}
|
||||
<div class="troubleshooting">
|
||||
<h3>🔧 Troubleshooting Steps:</h3>
|
||||
<ol>
|
||||
<li><strong>Enable Cookies:</strong> Ensure your browser allows cookies for this site. Check your browser settings under Privacy & Security.</li>
|
||||
<li><strong>Disable Tracking Protection:</strong> Turn off Enhanced Tracking Protection, Shield, or similar privacy features for this site.</li>
|
||||
<li><strong>Allow Third-Party Cookies:</strong> Some browsers block cookies from embedded content. Try allowing cookies from all sites temporarily.</li>
|
||||
<li><strong>Clear Browser Cache:</strong> Clear cookies and cached data for this site, then try launching again from your course.</li>
|
||||
<li><strong>Try a Different Browser:</strong> Test with Chrome, Firefox, Safari, or Edge to rule out browser-specific issues.</li>
|
||||
<li><strong>Disable Extensions:</strong> Browser extensions (ad blockers, privacy tools) can interfere with authentication. Try disabling them.</li>
|
||||
<li><strong>Contact Support:</strong> If the issue persists after trying the above, contact your system administrator with the error details above.</li>
|
||||
</ol>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="help-text">
|
||||
<p>If this problem persists, please contact your system administrator.</p>
|
||||
<p>You may close this window and return to your course to try again.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Media Not Found</title>
|
||||
</head>
|
||||
<body style="margin:0;padding:10px;font-family:sans-serif;font-size:14px;">
|
||||
This media no longer exists
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,6 +11,27 @@
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<script>
|
||||
// This script is necessary because the React-based PageHeader component (which handles alert dismissal)
|
||||
// is not mounted on all Django-rendered pages. This ensures the close button works globally.
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
var closeButtons = document.querySelectorAll('.alert .close');
|
||||
closeButtons.forEach(function(btn) {
|
||||
btn.addEventListener('click', function(e) {
|
||||
var alert = this.closest('.alert');
|
||||
if (alert) {
|
||||
alert.classList.add('hiding');
|
||||
setTimeout(function() {
|
||||
if (alert.parentNode) {
|
||||
alert.parentNode.removeChild(alert);
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
{% block topimports %}{%endblock topimports %}
|
||||
|
||||
{% block extra_head %}{% endblock extra_head %}
|
||||
|
||||
{% include "config/index.html" %}
|
||||
|
||||
{% if not USE_ROUNDED_CORNERS %}
|
||||
|
||||
Reference in New Issue
Block a user