This mirrors a fix in [frontend-app-library-authoring](https://github.com/edx/frontend-app-library-authoring/pull/25/files#diff-4102b1726b612b7813106191ef87849273f5d89da904f5ab69fb1974a31987d5R307).
296 lines
13 KiB
HTML
296 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
|
|
<html>
|
|
<head>
|
|
<!-- Open links in a new tab, not this iframe -->
|
|
<base target="_blank">
|
|
<meta charset="UTF-8">
|
|
<!-- gettext & XBlock JS i18n code -->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/js/i18n/en/djangojs.js"></script>
|
|
<!-- Most XBlocks require jQuery: -->
|
|
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
|
|
<!-- The Video XBlock requires "ajaxWithPrefix" -->
|
|
<script type="text/javascript">
|
|
$.postWithPrefix = $.post;
|
|
$.getWithPrefix = $.get;
|
|
$.ajaxWithPrefix = $.ajax;
|
|
</script>
|
|
<!-- The Video XBlock requires "Slider" from jQuery-UI: -->
|
|
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>
|
|
<!-- The video XBlock depends on Underscore.JS -->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/common/js/vendor/underscore.js"></script>
|
|
<!-- The video XBlock depends on jquery-cookie -->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/js/vendor/jquery.cookie.js"></script>
|
|
<!--The Video XBlock has an undeclared dependency on 'Logger' -->
|
|
<script>
|
|
window.Logger = { log: function() { } };
|
|
</script>
|
|
<!-- Builtin XBlock types depend on RequireJS -->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/common/js/vendor/require.js"></script>
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/js/RequireJS-namespace-undefine.js"></script>
|
|
<script>
|
|
// The minimal RequireJS configuration required for common LMS building XBlock types to work:
|
|
(function (require, define) {
|
|
require.config({
|
|
baseUrl: "{{ lms_root_url }}/static/",
|
|
paths: {
|
|
accessibility: 'js/src/accessibility_tools',
|
|
draggabilly: 'js/vendor/draggabilly',
|
|
hls: 'common/js/vendor/hls',
|
|
moment: 'common/js/vendor/moment-with-locales',
|
|
HtmlUtils: 'edx-ui-toolkit/js/utils/html-utils',
|
|
},
|
|
});
|
|
define('gettext', [], function() { return window.gettext; });
|
|
define('jquery', [], function() { return window.jQuery; });
|
|
define('jquery-migrate', [], function() { return window.jQuery; });
|
|
define('underscore', [], function() { return window._; });
|
|
}).call(this, require || RequireJS.require, define || RequireJS.define);
|
|
</script>
|
|
<!-- edX HTML Utils requires GlobalLoader -->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/edx-ui-toolkit/js/utils/global-loader.js"></script>
|
|
<script>
|
|
// The video XBlock has an undeclared dependency on edX HTML Utils
|
|
RequireJS.require(['HtmlUtils'], function (HtmlUtils) {
|
|
window.edx.HtmlUtils = HtmlUtils;
|
|
// The problem XBlock depends on window.SR, though 'accessibility_tools' has an undeclared dependency on HtmlUtils:
|
|
RequireJS.require(['accessibility']);
|
|
});
|
|
RequireJS.require(['edx-ui-toolkit/js/utils/string-utils'], function (StringUtils) {
|
|
window.edx.StringUtils = StringUtils;
|
|
});
|
|
</script>
|
|
<!--
|
|
commons.js: this file produced by webpack contains many shared chunks of code.
|
|
By including this, you have only to also import any of the smaller entrypoint
|
|
files (defined in webpack.common.config.js) to get that entry point and all
|
|
of its dependencies.
|
|
-->
|
|
<script type="text/javascript" src="{{ lms_root_url }}/static/bundles/commons.js"></script>
|
|
<!-- The video XBlock (and perhaps others?) expect this global: -->
|
|
<script>
|
|
window.onTouchBasedDevice = function() { return navigator.userAgent.match(/iPhone|iPod|iPad|Android/i); };
|
|
</script>
|
|
<!-- At least one XBlock (drag and drop v2) expects Font Awesome -->
|
|
<link rel="stylesheet"
|
|
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
<!-- Capa Problem Editing requires CodeMirror -->
|
|
<link rel="stylesheet" href="{{ lms_root_url }}/static/js/vendor/CodeMirror/codemirror.css">
|
|
<!-- Built-in XBlocks (and some plugins) depends on LMS CSS -->
|
|
<link rel="stylesheet" href="{{ lms_root_url }}/static/css/lms-course.css">
|
|
<!-- Configure and load MathJax -->
|
|
<script type="text/x-mathjax-config">
|
|
MathJax.Hub.Config({
|
|
tex2jax: {
|
|
inlineMath: [
|
|
["\\(","\\)"],
|
|
['[mathjaxinline]','[/mathjaxinline]']
|
|
],
|
|
displayMath: [
|
|
["\\[","\\]"],
|
|
['[mathjax]','[/mathjax]']
|
|
]
|
|
}
|
|
});
|
|
</script>
|
|
<script type="text/x-mathjax-config">
|
|
MathJax.Hub.signal.Interest(function(message) {
|
|
if(message[0] === "End Math") {
|
|
set_mathjax_display_div_settings();
|
|
}
|
|
});
|
|
function set_mathjax_display_div_settings() {
|
|
$('.MathJax_Display').each(function( index ) {
|
|
this.setAttribute('tabindex', '0');
|
|
this.setAttribute('aria-live', 'off');
|
|
this.removeAttribute('role');
|
|
this.removeAttribute('aria-readonly');
|
|
});
|
|
}
|
|
</script>
|
|
<script type="text/javascript">
|
|
// Activating Mathjax accessibility files
|
|
window.MathJax = {
|
|
menuSettings: {
|
|
collapsible: true,
|
|
autocollapse: false,
|
|
explorer: true
|
|
}
|
|
};
|
|
</script>
|
|
<!-- This must appear after all mathjax-config blocks, so it is after the imports from the other templates.
|
|
It can't be run through static.url because MathJax uses crazy url introspection to do lazy loading of
|
|
MathJax extension libraries -->
|
|
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/mathjax@2.7.5/MathJax.js?config=TeX-MML-AM_SVG"></script>
|
|
<!-- fragment head -->
|
|
{{ fragment.head_html | safe }}
|
|
</head>
|
|
<body>
|
|
<!-- fragment body -->
|
|
{{ fragment.body_html | safe }}
|
|
<!-- fragment foot -->
|
|
{{ fragment.foot_html | safe }}
|
|
<script>
|
|
/**
|
|
* Map of all URL handlers for this block and its children, keyed by usage
|
|
* key.
|
|
*/
|
|
{% comment %}
|
|
This variable is expected to be a valid JSON, which will be translated
|
|
directly into a javascript object.
|
|
{% endcomment %}
|
|
|
|
HANDLER_URL_MAP = {{ handler_urls_json | safe }};
|
|
|
|
/**
|
|
* The JavaScript code which runs inside our IFrame and is responsible
|
|
* for communicating with the parent window.
|
|
*
|
|
* This cannot use any imported functions because it runs in the IFrame,
|
|
* not in our app webpack bundle.
|
|
*/
|
|
function blockFrameJS() {
|
|
const CHILDREN_KEY = '_jsrt_xb_children'; // JavaScript RunTime XBlock children
|
|
const USAGE_ID_KEY = '_jsrt_xb_usage_id';
|
|
const HANDLER_URL = '_jsrt_xb_handler_url';
|
|
|
|
const uniqueKeyPrefix = `k${+Date.now()}-${Math.floor(Math.random() * 1e10)}-`;
|
|
let messageCount = 0;
|
|
|
|
/**
|
|
* The JavaScript runtime for any XBlock in the IFrame
|
|
*/
|
|
const runtime = {
|
|
/**
|
|
* An obscure and little-used API that retrieves a particular
|
|
* XBlock child using its 'data-name' attribute
|
|
* @param block The root DIV element of the XBlock calling this method
|
|
* @param childName The value of the 'data-name' attribute of the root
|
|
* DIV element of the XBlock child in question.
|
|
*/
|
|
childMap: (block, childName) => runtime.children(block).find((child) => child.element.getAttribute('data-name') === childName),
|
|
children: (block) => block[CHILDREN_KEY],
|
|
/**
|
|
* Get the URL for the specified handler. This method must be synchronous, so
|
|
* cannot make HTTP requests.
|
|
*/
|
|
handlerUrl: (block, handlerName, suffix, query) => {
|
|
let url = block[HANDLER_URL].replace('handler_name', handlerName);
|
|
if (suffix) {
|
|
url += `${suffix}/`;
|
|
}
|
|
if (query) {
|
|
url += `?${query}`;
|
|
}
|
|
return url;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Initialize an XBlock. This function should only be called by initializeXBlockAndChildren
|
|
* because it assumes that function has already run.
|
|
*/
|
|
function initializeXBlock(element, callback) {
|
|
const usageId = element[USAGE_ID_KEY];
|
|
// Check if the XBlock has an initialization function:
|
|
const initFunctionName = element.getAttribute('data-init');
|
|
if (initFunctionName !== null) {
|
|
// Since this block has an init function, it may need to call handlers:
|
|
element[HANDLER_URL] = HANDLER_URL_MAP[usageId];
|
|
// Now proceed with initializing the block's JavaScript:
|
|
const InitFunction = (window)[initFunctionName];
|
|
// Does the XBlock HTML contain arguments to pass to the InitFunction?
|
|
let data = {};
|
|
[].forEach.call(element.children, (childNode) => {
|
|
// The newer/pure/Blockstore runtime uses 'xblock_json_init_args'
|
|
// while the LMS runtime uses 'xblock-json-init-args'.
|
|
if (
|
|
childNode.matches('script.xblock_json_init_args')
|
|
|| childNode.matches('script.xblock-json-init-args')
|
|
) {
|
|
data = JSON.parse(childNode.textContent);
|
|
}
|
|
});
|
|
// An unfortunate inconsistency is that the old Studio runtime used
|
|
// to pass 'element' as a jQuery-wrapped DOM element, whereas the LMS
|
|
// runtime used to pass 'element' as the pure DOM node. In order not to
|
|
// break backwards compatibility, we would need to maintain that.
|
|
// However, this is currently disabled as it causes issues (need to
|
|
// modify the runtime methods like handlerUrl too), and we decided not
|
|
// to maintain support for legacy studio_view in this runtime.
|
|
// const isStudioView = element.className.indexOf('studio_view') !== -1;
|
|
// const passElement = isStudioView && (window as any).$ ? (window as any).$(element) : element;
|
|
const blockJS = new InitFunction(runtime, element, data) || {};
|
|
blockJS.element = element;
|
|
callback(blockJS);
|
|
} else {
|
|
const blockJS = { element };
|
|
callback(blockJS);
|
|
}
|
|
}
|
|
|
|
// Recursively initialize the JavaScript code of each XBlock:
|
|
function initializeXBlockAndChildren(element, callback) {
|
|
// The newer/pure/Blockstore runtime uses the 'data-usage' attribute, while the LMS uses 'data-usage-id'
|
|
const usageId = element.getAttribute('data-usage') || element.getAttribute('data-usage-id');
|
|
if (usageId !== null) {
|
|
element[USAGE_ID_KEY] = usageId;
|
|
} else {
|
|
throw new Error('XBlock is missing a usage ID attribute on its root HTML node.');
|
|
}
|
|
|
|
const version = element.getAttribute('data-runtime-version');
|
|
if (version != null && version !== '1') {
|
|
throw new Error('Unsupported XBlock runtime version requirement.');
|
|
}
|
|
|
|
// Recursively initialize any children first:
|
|
// We need to find all div.xblock-v1 children, unless they're grandchilden
|
|
// So we build a list of all div.xblock-v1 descendants that aren't descendants
|
|
// of an already-found descendant:
|
|
const childNodesFound = [];
|
|
[].forEach.call(element.querySelectorAll('.xblock, .xblock-v1'), (childNode) => {
|
|
if (!childNodesFound.find((el) => el.contains(childNode))) {
|
|
childNodesFound.push(childNode);
|
|
}
|
|
});
|
|
|
|
// This code is awkward because we can't use promises (IE11 etc.)
|
|
let childrenInitialized = -1;
|
|
function initNextChild() {
|
|
childrenInitialized += 1;
|
|
if (childrenInitialized < childNodesFound.length) {
|
|
const childNode = childNodesFound[childrenInitialized];
|
|
initializeXBlockAndChildren(childNode, initNextChild);
|
|
} else {
|
|
// All children are initialized:
|
|
initializeXBlock(element, callback);
|
|
}
|
|
}
|
|
initNextChild();
|
|
}
|
|
|
|
// Find the root XBlock node.
|
|
// The newer/pure/Blockstore runtime uses '.xblock-v1' while the LMS runtime uses '.xblock'.
|
|
const rootNode = document.querySelector('.xblock, .xblock-v1'); // will always return the first matching element
|
|
initializeXBlockAndChildren(rootNode, () => {
|
|
});
|
|
|
|
let lastHeight = -1;
|
|
function checkFrameHeight() {
|
|
const newHeight = document.documentElement.scrollHeight;
|
|
if (newHeight !== lastHeight) {
|
|
lastHeight = newHeight;
|
|
}
|
|
}
|
|
// Check the size whenever the DOM changes:
|
|
new MutationObserver(checkFrameHeight).observe(document.body, { attributes: true, childList: true, subtree: true });
|
|
// And whenever the IFrame is resized
|
|
window.addEventListener('resize', checkFrameHeight);
|
|
}
|
|
|
|
window.addEventListener('load', blockFrameJS);
|
|
</script>
|
|
</body>
|
|
</html>
|