feat: course unit - edit iframe modal window (#35777)
Adds logic to support the functionality of editing xblocks via the legacy modal editing window.
This commit is contained in:
@@ -9,13 +9,14 @@ from django.core.exceptions import PermissionDenied
|
||||
from django.db import transaction
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.decorators.http import require_http_methods
|
||||
from opaque_keys.edx.keys import CourseKey
|
||||
from web_fragments.fragment import Fragment
|
||||
|
||||
from cms.djangoapps.contentstore.utils import load_services_for_studio
|
||||
from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_string
|
||||
from common.djangoapps.edxmako.shortcuts import render_to_response, render_to_string
|
||||
from common.djangoapps.student.auth import (
|
||||
has_studio_read_access,
|
||||
has_studio_write_access,
|
||||
@@ -44,6 +45,8 @@ from ..helpers import (
|
||||
is_unit,
|
||||
)
|
||||
from .preview import get_preview_fragment
|
||||
from .component import _get_item_in_course
|
||||
from ..utils import get_container_handler_context
|
||||
|
||||
from cms.djangoapps.contentstore.xblock_storage_handlers.view_handlers import (
|
||||
handle_xblock,
|
||||
@@ -302,6 +305,39 @@ def xblock_view_handler(request, usage_key_string, view_name):
|
||||
return HttpResponse(status=406)
|
||||
|
||||
|
||||
@xframe_options_exempt
|
||||
@require_http_methods(["GET"])
|
||||
@login_required
|
||||
def xblock_edit_view(request, usage_key_string):
|
||||
"""
|
||||
Return rendered xblock edit view.
|
||||
|
||||
Allows editing of an XBlock specified by the usage key.
|
||||
"""
|
||||
usage_key = usage_key_with_run(usage_key_string)
|
||||
if not has_studio_read_access(request.user, usage_key.course_key):
|
||||
raise PermissionDenied()
|
||||
|
||||
store = modulestore()
|
||||
|
||||
with store.bulk_operations(usage_key.course_key):
|
||||
course, xblock, _, __ = _get_item_in_course(request, usage_key)
|
||||
container_handler_context = get_container_handler_context(request, usage_key, course, xblock)
|
||||
|
||||
fragment = get_preview_fragment(request, xblock, {})
|
||||
|
||||
hashed_resources = {
|
||||
hash_resource(resource): resource._asdict() for resource in fragment.resources
|
||||
}
|
||||
|
||||
container_handler_context.update({
|
||||
"action_name": "edit",
|
||||
"resources": list(hashed_resources.items()),
|
||||
})
|
||||
|
||||
return render_to_response('container_editor.html', container_handler_context)
|
||||
|
||||
|
||||
@require_http_methods("GET")
|
||||
@login_required
|
||||
@expect_json
|
||||
|
||||
@@ -23,6 +23,7 @@ from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from opaque_keys.edx.locator import BlockUsageLocator, CourseLocator
|
||||
from pyquery import PyQuery
|
||||
from pytz import UTC
|
||||
from bs4 import BeautifulSoup
|
||||
from web_fragments.fragment import Fragment
|
||||
from webob import Response
|
||||
from xblock.core import XBlockAside
|
||||
@@ -4538,3 +4539,61 @@ class TestUpdateFromSource(ModuleStoreTestCase):
|
||||
user_id=user.id,
|
||||
)
|
||||
self.check_updated(source_block, destination_block.location)
|
||||
|
||||
|
||||
class TestXblockEditView(CourseTestCase):
|
||||
"""
|
||||
Test xblock_edit_view.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.chapter = self._create_block(self.course, "chapter", "Week 1")
|
||||
self.sequential = self._create_block(self.chapter, "sequential", "Lesson 1")
|
||||
self.vertical = self._create_block(self.sequential, "vertical", "Unit")
|
||||
self.html = self._create_block(self.vertical, "html", "HTML")
|
||||
self.child_container = self._create_block(
|
||||
self.vertical, "split_test", "Split Test"
|
||||
)
|
||||
self.child_vertical = self._create_block(
|
||||
self.child_container, "vertical", "Child Vertical"
|
||||
)
|
||||
self.video = self._create_block(self.child_vertical, "video", "My Video")
|
||||
self.store = modulestore()
|
||||
|
||||
self.store.publish(self.vertical.location, self.user.id)
|
||||
|
||||
def _create_block(self, parent, category, display_name, **kwargs):
|
||||
"""
|
||||
creates a block in the module store, without publishing it.
|
||||
"""
|
||||
return BlockFactory.create(
|
||||
parent=parent,
|
||||
category=category,
|
||||
display_name=display_name,
|
||||
publish_item=False,
|
||||
user_id=self.user.id,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def test_xblock_edit_view(self):
|
||||
url = reverse_usage_url("xblock_edit_handler", self.video.location)
|
||||
resp = self.client.get_html(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
html_content = resp.content.decode(resp.charset)
|
||||
self.assertIn("var decodedActionName = 'edit';", html_content)
|
||||
|
||||
def test_xblock_edit_view_contains_resources(self):
|
||||
url = reverse_usage_url("xblock_edit_handler", self.video.location)
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
|
||||
html_content = resp.content.decode(resp.charset)
|
||||
soup = BeautifulSoup(html_content, "html.parser")
|
||||
|
||||
resource_links = [link["href"] for link in soup.find_all("link", {"rel": "stylesheet"})]
|
||||
script_sources = [script["src"] for script in soup.find_all("script") if script.get("src")]
|
||||
|
||||
self.assertGreater(len(resource_links), 0, f"No CSS resources found in HTML. Found: {resource_links}")
|
||||
self.assertGreater(len(script_sources), 0, f"No JS resources found in HTML. Found: {script_sources}")
|
||||
|
||||
@@ -47,6 +47,17 @@ define([
|
||||
title: gettext("Studio's having trouble saving your work"),
|
||||
message: message
|
||||
});
|
||||
if (window.self !== window.top) {
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'studioAjaxError',
|
||||
message: 'Sends a message when an AJAX error occurs',
|
||||
payload: {}
|
||||
}, document.referrer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
console.log('Studio AJAX Error', { // eslint-disable-line no-console
|
||||
url: event.currentTarget.URL,
|
||||
response: jqXHR.responseText,
|
||||
|
||||
@@ -70,17 +70,6 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
|
||||
newParent = undefined;
|
||||
},
|
||||
update: function(event, ui) {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'refreshPositions',
|
||||
message: 'Refresh positions of all xblocks',
|
||||
payload: {}
|
||||
}, document.referrer
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// When dragging from one ol to another, this method
|
||||
// will be called twice (once for each list). ui.sender will
|
||||
// be null if the change is related to the list the element
|
||||
@@ -137,6 +126,17 @@ function($, _, XBlockView, ModuleUtils, gettext, StringUtils, NotificationView)
|
||||
if (successCallback) {
|
||||
successCallback();
|
||||
}
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'refreshPositions',
|
||||
message: 'Refresh positions of all xblocks',
|
||||
payload: {}
|
||||
}, document.referrer
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
// Update publish and last modified information from the server.
|
||||
xblockInfo.fetch();
|
||||
}
|
||||
|
||||
@@ -109,6 +109,18 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
try {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'hideXBlockEditorModal',
|
||||
message: 'Sends a message when the modal window is hided',
|
||||
payload: {}
|
||||
}, document.referrer
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
// Completely remove the modal from the DOM
|
||||
this.undelegateEvents();
|
||||
this.$el.html('');
|
||||
@@ -119,6 +131,15 @@ define(['jquery', 'underscore', 'gettext', 'js/views/baseview'],
|
||||
event.preventDefault();
|
||||
event.stopPropagation(); // Make sure parent modals don't see the click
|
||||
}
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'closeXBlockEditorModal',
|
||||
message: 'Sends a message when the modal window is closed',
|
||||
payload: {}
|
||||
}, document.referrer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
this.hide();
|
||||
},
|
||||
|
||||
|
||||
@@ -83,6 +83,11 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
|
||||
},
|
||||
|
||||
getXBlockUpstreamLink: function() {
|
||||
if (!this.xblockElement || !this.xblockElement.length) {
|
||||
console.error('xblockElement is empty or not defined');
|
||||
return;
|
||||
}
|
||||
|
||||
const usageKey = this.xblockElement.data('locator');
|
||||
$.ajax({
|
||||
url: '/api/contentstore/v2/downstreams/' + usageKey,
|
||||
@@ -219,6 +224,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
|
||||
},
|
||||
|
||||
onSave: function() {
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'saveEditedXBlockData',
|
||||
message: 'Sends a message when the xblock data is saved',
|
||||
payload: {}
|
||||
}, document.referrer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
var refresh = this.editOptions.refresh;
|
||||
this.hide();
|
||||
if (refresh) {
|
||||
@@ -230,6 +245,16 @@ function($, _, Backbone, gettext, BaseModal, ViewUtils, XBlockViewUtils, XBlockE
|
||||
// Notify child views to stop listening events
|
||||
Backbone.trigger('xblock:editorModalHidden');
|
||||
|
||||
try {
|
||||
window.parent.postMessage({
|
||||
type: 'closeXBlockEditorModal',
|
||||
message: 'Sends a message when the modal window is closed',
|
||||
payload: {}
|
||||
}, document.referrer);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
BaseModal.prototype.hide.call(this);
|
||||
|
||||
// Notify the runtime that the modal has been hidden
|
||||
|
||||
@@ -149,6 +149,9 @@ function($, _, Backbone, gettext, BasePage,
|
||||
case 'refreshXBlock':
|
||||
this.render();
|
||||
break;
|
||||
case 'completeXBlockEditing':
|
||||
this.refreshXBlock(xblockElement, false);
|
||||
break;
|
||||
case 'completeManageXBlockAccess':
|
||||
this.refreshXBlock(xblockElement, false);
|
||||
break;
|
||||
@@ -507,6 +510,18 @@ function($, _, Backbone, gettext, BasePage,
|
||||
window.location.href = destinationUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.options.isIframeEmbed) {
|
||||
return window.parent.postMessage(
|
||||
{
|
||||
type: 'editXBlock',
|
||||
message: 'Sends a message when the legacy modal window is shown',
|
||||
payload: {
|
||||
id: this.findXBlockElement(event.target).data('locator')
|
||||
}
|
||||
}, document.referrer
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var xblockElement = this.findXBlockElement(event.target),
|
||||
@@ -1050,23 +1065,20 @@ function($, _, Backbone, gettext, BasePage,
|
||||
},
|
||||
|
||||
viewXBlockContent: function(event) {
|
||||
try {
|
||||
if (this.options.isIframeEmbed) {
|
||||
event.preventDefault();
|
||||
var usageId = event.currentTarget.href.split('/').pop() || '';
|
||||
window.parent.postMessage(
|
||||
{
|
||||
type: 'handleViewXBlockContent',
|
||||
payload: {
|
||||
usageId: usageId,
|
||||
},
|
||||
}, document.referrer
|
||||
);
|
||||
return true;
|
||||
try {
|
||||
if (this.options.isIframeEmbed) {
|
||||
event.preventDefault();
|
||||
var usageId = event.currentTarget.href.split('/').pop() || '';
|
||||
window.parent.postMessage({
|
||||
type: 'handleViewXBlockContent',
|
||||
message: 'View the content of the XBlock',
|
||||
payload: { usageId },
|
||||
}, document.referrer);
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
toggleSaveButton: function() {
|
||||
|
||||
@@ -527,7 +527,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
|
||||
tagValueElement.className = 'tagging-label-value';
|
||||
|
||||
tagContentElement.appendChild(tagValueElement);
|
||||
parentElement.appendChild(tagContentElement);
|
||||
if (parentElement) {
|
||||
parentElement.appendChild(tagContentElement);
|
||||
}
|
||||
|
||||
if (tag.children.length > 0) {
|
||||
var tagIconElement = document.createElement('span'),
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
@import 'cms/theme/variables-v1';
|
||||
@import 'elements/course-unit-mfe-iframe';
|
||||
|
||||
body {
|
||||
min-width: 800px;
|
||||
html {
|
||||
body {
|
||||
min-width: 800px;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
[class*="view-"] .wrapper {
|
||||
.inner-wrapper {
|
||||
max-width: 100%;
|
||||
}
|
||||
@@ -114,7 +117,6 @@ body {
|
||||
background-color: $primary;
|
||||
color: $white;
|
||||
border-color: $transparent;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
@@ -171,6 +173,11 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
.edit-xblock-modal select {
|
||||
background-color: $white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.wrapper-modal-window .modal-window .modal-actions a {
|
||||
color: $text-color;
|
||||
background-color: $transparent;
|
||||
@@ -441,6 +448,10 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.studio-xblock-wrapper::marker {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
.view-container .content-primary {
|
||||
@@ -457,7 +468,7 @@ body {
|
||||
@extend %button-styles;
|
||||
|
||||
position: relative;
|
||||
top: 7px;
|
||||
top: -7px;
|
||||
|
||||
.fa-pencil {
|
||||
display: none;
|
||||
@@ -561,19 +572,40 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
[class*="view-"] .modal-lg.modal-editor .modal-header .editor-modes .action-item {
|
||||
.editor-button,
|
||||
.settings-button {
|
||||
@extend %light-button;
|
||||
body [class*="view-"] .openassessment_editor_buttons.xblock-actions {
|
||||
padding: 15px 2% 3px 2%;
|
||||
}
|
||||
|
||||
[class*="view-"] {
|
||||
.modal-lg {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
.modal-lg.modal-editor .modal-header .editor-modes .action-item {
|
||||
.editor-button,
|
||||
.settings-button {
|
||||
@extend %light-button;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper.wrapper-modal-window .modal-window .modal-actions .action-primary {
|
||||
@extend %primary-button;
|
||||
}
|
||||
|
||||
#openassessment-editor {
|
||||
#oa_basic_settings_editor #openassessment_title_editor_wrapper input, input[type=number] {
|
||||
width: 48%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[class*="view-"] .wrapper.wrapper-modal-window .modal-window .modal-actions .action-primary {
|
||||
@extend %primary-button;
|
||||
}
|
||||
|
||||
.wrapper-comp-settings {
|
||||
[class*="view-"] div.wrapper-comp-settings {
|
||||
.list-input.settings-list {
|
||||
input:not([type="file"]):not([type="number"]),
|
||||
select {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.metadata-list-enum .create-setting {
|
||||
@extend %modal-actions-button;
|
||||
|
||||
@@ -597,6 +629,7 @@ body {
|
||||
.list-input.settings-list {
|
||||
.field.comp-setting-entry.is-set .setting-input {
|
||||
color: $text-color;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
select {
|
||||
@@ -784,3 +817,39 @@ select {
|
||||
.wrapper-xblock .xblock-header-primary .header-actions .wrapper-nav-sub {
|
||||
z-index: $zindex-dropdown;
|
||||
}
|
||||
|
||||
.xblock-studio_view-drag-and-drop-v2 .xblock--drag-and-drop--editor {
|
||||
.zone-align-select,
|
||||
.item-styles-form input,
|
||||
.drag-builder textarea,
|
||||
.target-image-form textarea {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.target-image-form input[type="text"] {
|
||||
width: 100%;
|
||||
|
||||
&.background-url {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.autozone-layout {
|
||||
&.autozone-layout-cols,
|
||||
&.autozone-layout-rows {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.autozone-size {
|
||||
&.autozone-size-width,
|
||||
&.autozone-size-height {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.feedback-tab input:not([type=checkbox]),
|
||||
.xblock--drag-and-drop--editor .feedback-tab select {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
133
cms/templates/container_editor.html
Normal file
133
cms/templates/container_editor.html
Normal file
@@ -0,0 +1,133 @@
|
||||
## coding=utf-8
|
||||
## mako
|
||||
|
||||
## Pages currently use v1 styling by default. Once the Pattern Library
|
||||
## rollout has been completed, this default can be switched to v2.
|
||||
<%! main_css = "style-main-v1" %>
|
||||
|
||||
<%! course_unit_mfe_iframe_css = "course-unit-mfe-iframe-bundle" %>
|
||||
|
||||
## Standard imports
|
||||
<%namespace name='static' file='static_content.html'/>
|
||||
<%!
|
||||
from django.utils.translation import gettext as _
|
||||
from cms.djangoapps.contentstore.config.waffle import CUSTOM_RELATIVE_DATES
|
||||
from lms.djangoapps.branding import api as branding_api
|
||||
from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_string
|
||||
from cms.djangoapps.contentstore.helpers import xblock_type_display_name
|
||||
from openedx.core.release import RELEASE_LINE
|
||||
%>
|
||||
<%def name="online_help_token()">
|
||||
<%
|
||||
return "container"
|
||||
%>
|
||||
</%def>
|
||||
|
||||
<%page expression_filter="h"/>
|
||||
<!doctype html>
|
||||
<html lang="${LANGUAGE_CODE}">
|
||||
<head dir="${static.dir_rtl()}">
|
||||
<%
|
||||
jsi18n_path = "js/i18n/{language}/djangojs.js".format(language=LANGUAGE_CODE)
|
||||
%>
|
||||
|
||||
% if getattr(settings, 'CAPTURE_CONSOLE_LOG', False):
|
||||
<script type="text/javascript">
|
||||
var oldOnError = window.onerror;
|
||||
window.localStorage.setItem('console_log_capture', JSON.stringify([]));
|
||||
|
||||
window.onerror = function (message, url, lineno, colno, error) {
|
||||
if (oldOnError) {
|
||||
oldOnError.apply(this, arguments);
|
||||
}
|
||||
|
||||
var messages = JSON.parse(window.localStorage.getItem('console_log_capture'));
|
||||
messages.push([message, url, lineno, colno, (error || {}).stack]);
|
||||
window.localStorage.setItem('console_log_capture', JSON.stringify(messages));
|
||||
}
|
||||
</script>
|
||||
% endif
|
||||
|
||||
<script type="text/javascript" src="${static.url(jsi18n_path)}"></script>
|
||||
|
||||
% if settings.DEBUG:
|
||||
<script type="text/javascript" src="${static.url('js/src/gettext_fallback.js')}"></script>
|
||||
% endif
|
||||
|
||||
<%static:css group='style-vendor'/>
|
||||
<%static:css group='style-vendor-tinymce-content'/>
|
||||
<%static:css group='style-vendor-tinymce-skin'/>
|
||||
<%static:css group='${self.attr.course_unit_mfe_iframe_css}'/>
|
||||
|
||||
% if uses_bootstrap:
|
||||
<link rel="stylesheet" href="${static.url(self.attr.main_css)}" type="text/css" media="all" />
|
||||
% else:
|
||||
<%static:css group='${self.attr.main_css}'/>
|
||||
% endif
|
||||
|
||||
<%include file="widgets/segment-io.html" />
|
||||
|
||||
<%block name="header_extras">
|
||||
% for template_name in templates:
|
||||
<script type="text/template" id="${template_name}-tpl">
|
||||
<%static:include path="js/${template_name}.underscore" />
|
||||
</script>
|
||||
% endfor
|
||||
<script type="text/template" id="image-modal-tpl">
|
||||
<%static:include path="common/templates/image-modal.underscore" />
|
||||
</script>
|
||||
|
||||
% if not settings.STUDIO_FRONTEND_CONTAINER_URL:
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/common.min.css')}" />
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('common/css/vendor/editImageModal.min.css')}" />
|
||||
% endif
|
||||
<link rel="stylesheet" type="text/css" href="${static.url('js/vendor/timepicker/jquery.timepicker.css')}" />
|
||||
% for _, resource in resources:
|
||||
% if resource['kind'] == 'url' and resource['mimetype'] == 'text/css':
|
||||
<link rel="stylesheet" href="${resource['data']}" type="text/css" />
|
||||
% endif
|
||||
% endfor
|
||||
</%block>
|
||||
|
||||
<!-- Hotjar Tracking Code -->
|
||||
<script>
|
||||
(function(h, o, t, j, a, r){
|
||||
h.hj = h.hj || function() { (h.hj.q = h.hj.q || []).push(arguments) };
|
||||
h._hjSettings={ hjid: Number('${settings.HOTJAR_ID |n, js_escaped_string}'), hjsv: 6 };
|
||||
a = o.getElementsByTagName('head')[0];
|
||||
r = o.createElement('script');
|
||||
r.async = 1;
|
||||
r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
|
||||
a.appendChild(r);
|
||||
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body class="${static.dir_rtl()} <%block name='bodyclass'></%block> lang_${LANGUAGE_CODE} view-container">
|
||||
<%static:js group='base_vendor' />
|
||||
<%static:webpack entry='commons' />
|
||||
|
||||
<script type="text/javascript">
|
||||
window.baseUrl = '${settings.STATIC_URL | n, js_escaped_string}';
|
||||
require.config({ baseUrl: window.baseUrl });
|
||||
</script>
|
||||
<script type="text/javascript" src="${static.url("cms/js/require-config.js")}"></script>
|
||||
|
||||
<%block name='page_bundle'>
|
||||
<%static:webpack entry="js/factories/container">
|
||||
require(['js/models/xblock_info', 'js/views/modals/edit_xblock'],
|
||||
function (XBlockInfo, EditXBlockModal) {
|
||||
var decodedActionName = '${action_name|n, decode.utf8}';
|
||||
var encodedXBlockDetails = ${xblock_info | n, dump_js_escaped_json};
|
||||
|
||||
if (decodedActionName === 'edit') {
|
||||
var editXBlockModal = new EditXBlockModal();
|
||||
var xblockInfoInstance = new XBlockInfo(encodedXBlockDetails);
|
||||
|
||||
editXBlockModal.edit([], xblockInfoInstance, {});
|
||||
}
|
||||
});
|
||||
</%static:webpack>
|
||||
</%block>
|
||||
</body>
|
||||
</html>
|
||||
@@ -18,6 +18,7 @@ import openedx.core.djangoapps.debug.views
|
||||
import openedx.core.djangoapps.lang_pref.views
|
||||
from cms.djangoapps.contentstore import toggles
|
||||
from cms.djangoapps.contentstore import views as contentstore_views
|
||||
from cms.djangoapps.contentstore.views.block import xblock_edit_view
|
||||
from cms.djangoapps.contentstore.views.organization import OrganizationListView
|
||||
from openedx.core.apidocs import api_info
|
||||
from openedx.core.djangoapps.password_policy import compliance as password_policy_compliance
|
||||
@@ -150,6 +151,8 @@ urlpatterns = oauth2_urlpatterns + [
|
||||
name='xblock_outline_handler'),
|
||||
re_path(fr'^xblock/container/{settings.USAGE_KEY_PATTERN}$', contentstore_views.xblock_container_handler,
|
||||
name='xblock_container_handler'),
|
||||
re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}/action/edit$', xblock_edit_view,
|
||||
name='xblock_edit_handler'),
|
||||
re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}/(?P<view_name>[^/]+)$', contentstore_views.xblock_view_handler,
|
||||
name='xblock_view_handler'),
|
||||
re_path(fr'^xblock/{settings.USAGE_KEY_PATTERN}?$', contentstore_views.xblock_handler,
|
||||
|
||||
Reference in New Issue
Block a user