fix: Broken CSS styles of ora2 block editor [FC-0076] (#36220)

* It was this error 'Uncaught TypeError: el.timepicker is not a function' while rendering the editor. It's fixed adding the timepicker pluging in xblock_v2/xblock_iframe.html
* Added '.openassessment_cancel_button' and '.openassessment_save_button' as action buttons.
* Use openassessment manifest.json to load css from dist
This commit is contained in:
Chris Chávez
2025-04-22 16:08:02 -05:00
committed by GitHub
parent 2eb0adb440
commit 54ec998bee
9 changed files with 386 additions and 32 deletions

View File

@@ -2910,6 +2910,7 @@ LIBRARY_ENABLED_BLOCKS = [
'video',
'html',
'drag-and-drop-v2',
'openassessment',
'conditional',
'done',
'freetextresponse',

View File

@@ -1,3 +1,8 @@
@import '_builtin-block-variables';
@import 'bourbon/bourbon'; // lib - bourbon
@import 'neat/neat'; // lib - Neat
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'build-v1'; // shared app style assets/rendering
@import 'cms/theme/variables-v1';
@import 'elements/course-unit-mfe-iframe';
@@ -5,6 +10,9 @@ html {
body {
min-width: 800px;
background: transparent;
&.openassessment_full_height.view-container {
overflow-y: hidden;
}
}
}
@@ -478,6 +486,38 @@ body,
}
}
// openassessment xblock mods for mfe iframe
.openassessment_editor_buttons.xblock-actions {
padding: ($baseline*0.75) 2% ($baseline/2) 2%;
position: absolute;
bottom: 0;
z-index: 11;
right: 0;
.action-item {
@extend %t-action3;
display: inline-block;
margin-right: ($baseline*0.75);
&:last-child {
margin-right: 0;
}
}
}
#openassessment-editor .oa_editor_content_wrapper {
// make room for action buttons
bottom: 70px;
// make room for tabs above after shifting everything up due to action buttons
height: calc(100% - 112px);
}
#openassessment-editor #openassessment_editor_header {
padding: 10px;
padding-right: 50px; // make space for close button
}
.xblock-v1-studio_view {
height: 100%;
@@ -486,10 +526,6 @@ body,
flex-direction: column;
justify-content: space-between;
height: 100%;
.list-input {
height: 90vh;
}
}
&.xmodule_DoneXBlock {

283
common/static/css/vendor/hint.css vendored Normal file
View File

@@ -0,0 +1,283 @@
/*! Hint.css - v1.3.1 - 2013-11-23
* http://kushagragour.in/lab/hint/
* Copyright (c) 2013 Kushagra Gour; Licensed MIT */
/*-------------------------------------*\
HINT.css - A CSS tooltip library
\*-------------------------------------*/
/**
* HINT.css is a tooltip library made in pure CSS.
*
* Source: https://github.com/chinchang/hint.css
* Demo: http://kushagragour.in/lab/hint/
*
* Release under The MIT License
*
*/
/**
* source: hint-core.scss
*
* Defines the basic styling for the tooltip.
* Each tooltip is made of 2 parts:
* 1) body (:after)
* 2) arrow (:before)
*
* Classes added:
* 1) hint
*/
.hint, [data-hint] {
position: relative;
display: inline-block;
/**
* tooltip arrow
*/
/**
* tooltip body
*/ }
.hint:before, .hint:after, [data-hint]:before, [data-hint]:after {
position: absolute;
-webkit-transform: translate3d(0, 0, 0);
-moz-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: hidden;
opacity: 0;
z-index: 1000000;
pointer-events: none;
-webkit-transition: 0.3s ease;
-moz-transition: 0.3s ease;
transition: 0.3s ease; }
.hint:hover:before, .hint:hover:after, .hint:focus:before, .hint:focus:after, [data-hint]:hover:before, [data-hint]:hover:after, [data-hint]:focus:before, [data-hint]:focus:after {
visibility: visible;
opacity: 1; }
.hint:before, [data-hint]:before {
content: '';
position: absolute;
background: transparent;
border: 6px solid transparent;
z-index: 1000001; }
.hint:after, [data-hint]:after {
content: attr(data-hint);
background: #383838;
color: white;
text-shadow: 0 -1px 0px black;
padding: 8px 10px;
font-size: 12px;
line-height: 12px;
white-space: nowrap;
box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.3); }
/**
* source: hint-position.scss
*
* Defines the positoning logic for the tooltips.
*
* Classes added:
* 1) hint--top
* 2) hint--bottom
* 3) hint--left
* 4) hint--right
*/
/**
* set default color for tooltip arrows
*/
.hint--top:before {
border-top-color: #383838; }
.hint--bottom:before {
border-bottom-color: #383838; }
.hint--left:before {
border-left-color: #383838; }
.hint--right:before {
border-right-color: #383838; }
/**
* top tooltip
*/
.hint--top:before {
margin-bottom: -12px; }
.hint--top:after {
margin-left: -18px; }
.hint--top:before, .hint--top:after {
bottom: 100%;
left: 50%; }
.hint--top:hover:after, .hint--top:hover:before, .hint--top:focus:after, .hint--top:focus:before {
-webkit-transform: translateY(-8px);
-moz-transform: translateY(-8px);
transform: translateY(-8px); }
/**
* bottom tooltip
*/
.hint--bottom:before {
margin-top: -12px; }
.hint--bottom:after {
margin-left: -18px; }
.hint--bottom:before, .hint--bottom:after {
top: 100%;
left: 50%; }
.hint--bottom:hover:after, .hint--bottom:hover:before, .hint--bottom:focus:after, .hint--bottom:focus:before {
-webkit-transform: translateY(8px);
-moz-transform: translateY(8px);
transform: translateY(8px); }
/**
* right tooltip
*/
.hint--right:before {
margin-left: -12px;
margin-bottom: -6px; }
.hint--right:after {
margin-bottom: -14px; }
.hint--right:before, .hint--right:after {
left: 100%;
bottom: 50%; }
.hint--right:hover:after, .hint--right:hover:before, .hint--right:focus:after, .hint--right:focus:before {
-webkit-transform: translateX(8px);
-moz-transform: translateX(8px);
transform: translateX(8px); }
/**
* left tooltip
*/
.hint--left:before {
margin-right: -12px;
margin-bottom: -6px; }
.hint--left:after {
margin-bottom: -14px; }
.hint--left:before, .hint--left:after {
right: 100%;
bottom: 50%; }
.hint--left:hover:after, .hint--left:hover:before, .hint--left:focus:after, .hint--left:focus:before {
-webkit-transform: translateX(-8px);
-moz-transform: translateX(-8px);
transform: translateX(-8px); }
/**
* source: hint-color-types.scss
*
* Contains tooltips of various types based on color differences.
*
* Classes added:
* 1) hint--error
* 2) hint--warning
* 3) hint--info
* 4) hint--success
*
*/
/**
* Error
*/
.hint--error:after {
background-color: #b34e4d;
text-shadow: 0 -1px 0px #592726; }
.hint--error.hint--top:before {
border-top-color: #b34e4d; }
.hint--error.hint--bottom:before {
border-bottom-color: #b34e4d; }
.hint--error.hint--left:before {
border-left-color: #b34e4d; }
.hint--error.hint--right:before {
border-right-color: #b34e4d; }
/**
* Warning
*/
.hint--warning:after {
background-color: #c09854;
text-shadow: 0 -1px 0px #6c5328; }
.hint--warning.hint--top:before {
border-top-color: #c09854; }
.hint--warning.hint--bottom:before {
border-bottom-color: #c09854; }
.hint--warning.hint--left:before {
border-left-color: #c09854; }
.hint--warning.hint--right:before {
border-right-color: #c09854; }
/**
* Info
*/
.hint--info:after {
background-color: #3986ac;
text-shadow: 0 -1px 0px #193b4d; }
.hint--info.hint--top:before {
border-top-color: #3986ac; }
.hint--info.hint--bottom:before {
border-bottom-color: #3986ac; }
.hint--info.hint--left:before {
border-left-color: #3986ac; }
.hint--info.hint--right:before {
border-right-color: #3986ac; }
/**
* Success
*/
.hint--success:after {
background-color: #458746;
text-shadow: 0 -1px 0px #1a321a; }
.hint--success.hint--top:before {
border-top-color: #458746; }
.hint--success.hint--bottom:before {
border-bottom-color: #458746; }
.hint--success.hint--left:before {
border-left-color: #458746; }
.hint--success.hint--right:before {
border-right-color: #458746; }
/**
* source: hint-always.scss
*
* Defines a persisted tooltip which shows always.
*
* Classes added:
* 1) hint--always
*
*/
.hint--always:after, .hint--always:before {
opacity: 1;
visibility: visible; }
.hint--always.hint--top:after, .hint--always.hint--top:before {
-webkit-transform: translateY(-8px);
-moz-transform: translateY(-8px);
transform: translateY(-8px); }
.hint--always.hint--bottom:after, .hint--always.hint--bottom:before {
-webkit-transform: translateY(8px);
-moz-transform: translateY(8px);
transform: translateY(8px); }
.hint--always.hint--left:after, .hint--always.hint--left:before {
-webkit-transform: translateX(-8px);
-moz-transform: translateX(-8px);
transform: translateX(-8px); }
.hint--always.hint--right:after, .hint--always.hint--right:before {
-webkit-transform: translateX(8px);
-moz-transform: translateX(8px);
transform: translateX(8px); }
/**
* source: hint-rounded.scss
*
* Defines rounded corner tooltips.
*
* Classes added:
* 1) hint--rounded
*
*/
.hint--rounded:after {
border-radius: 4px; }
/**
* source: hint-effects.scss
*
* Defines various transition effects for the tooltips.
*
* Classes added:
* 1) hint--bounce
*
*/
.hint--bounce:before, .hint--bounce:after {
-webkit-transition: opacity 0.3s ease, visibility 0.3s ease, -webkit-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
-moz-transition: opacity 0.3s ease, visibility 0.3s ease, -moz-transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24);
transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.3s cubic-bezier(0.71, 1.7, 0.77, 1.24); }

View File

@@ -15,6 +15,8 @@
{% endif %}
<!-- Most XBlocks require jQuery: -->
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<!-- ORA2 blocks require timepicker -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timepicker/1.13.18/jquery.timepicker.min.js"></script>
<!-- The Video XBlock requires "ajaxWithPrefix" -->
<script type="text/javascript">
$.postWithPrefix = $.post;
@@ -40,32 +42,38 @@
define = define || RequireJS.define;
(function (require, define) {
if ('{{ view_name | safe }}' === 'studio_view') {
// Call `require-config.js` of the CMS
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "{{ cms_root_url }}/static/studio/cms/js/require-config.js";
document.head.appendChild(script);
// Call `require-config.js` of the CMS
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = "{{ cms_root_url }}/static/studio/cms/js/require-config.js";
document.head.appendChild(script);
require.config({
baseUrl: "{{ cms_root_url }}/static/studio",
paths: {
accessibility: 'js/src/accessibility_tools',
draggabilly: 'js/vendor/draggabilly',
hls: 'common/js/vendor/hls',
moment: 'common/js/vendor/moment-with-locales',
},
});
require.config({
baseUrl: "{{ cms_root_url }}/static/studio",
paths: {
accessibility: 'js/src/accessibility_tools',
draggabilly: 'js/vendor/draggabilly',
hls: 'common/js/vendor/hls',
moment: 'common/js/vendor/moment-with-locales',
'tinymce': 'js/vendor/tinymce/js/tinymce/tinymce.full.min',
'jquery.tinymce': 'js/vendor/tinymce/js/tinymce/jquery.tinymce.min',
},
});
require([
"{{ lms_root_url }}/static/dist{{ oa_manifest.oa_editor_tinymce_js }}",
"{{ lms_root_url }}/static/dist{{ oa_manifest.oa_editor_textarea_js }}",
]);
} else {
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',
},
});
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; });
@@ -104,8 +112,11 @@
<link rel="stylesheet" href="{{ lms_root_url }}/static/js/vendor/CodeMirror/codemirror.css">
<!-- Additional CSS for the XBlock Editor on the Library Authoring -->
<link rel="stylesheet" href="{{ cms_root_url }}/static/studio/css/vendor/normalize.css">
<link rel="stylesheet" href="{{ cms_root_url }}/static/studio/css/vendor/hint.css">
<link rel="stylesheet" href="{{ cms_root_url }}/static/studio/css/studio-main-v1.css" />
<link rel="stylesheet" href="{{ cms_root_url }}/static/studio/css/course-unit-mfe-iframe-bundle.css" />
<link rel="stylesheet" href="{{ lms_root_url }}/static/dist{{ oa_manifest.oa_ltr_css }}">
<script type="text/javascript" src="{{ lms_root_url }}/static/dist{{ oa_manifest.oa_ltr_js }}"></script>
<!-- Configure and load MathJax -->
<script type="text/x-mathjax-config">
@@ -378,6 +389,8 @@
'.save-button',
'.action-cancel',
'.action-save',
'.openassessment_cancel_button',
'.openassessment_save_button',
];
for (const selector of selectors) {

View File

@@ -2,6 +2,7 @@
Views that implement a RESTful API for interacting with XBlocks.
"""
import json
from pathlib import Path
from common.djangoapps.util.json_request import JsonResponse
from corsheaders.signals import check_request_enabled
@@ -21,6 +22,7 @@ from rest_framework.views import APIView
from xblock.django.request import DjangoWebobRequest, webob_to_django_response
from xblock.exceptions import NoSuchUsage
from xblock.fields import Scope
import openassessment
import openedx.core.djangoapps.site_configuration.helpers as configuration_helpers
from openedx.core.djangoapps.xblock.learning_context.manager import get_learning_context_impl
@@ -119,6 +121,24 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str):
# }
lms_root_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL)
cms_root_url = configuration_helpers.get_value('CMS_ROOT_URL', settings.CMS_ROOT_URL)
openassessment_path = Path(openassessment.__path__[0])
oa_manifest_path = openassessment_path / "xblock" / "static" / "dist" / "manifest.json"
new_oa_manifest = {}
if oa_manifest_path.exists():
with open(oa_manifest_path, "r") as f:
oa_manifest = json.load(f)
new_oa_manifest = {
# When we add the RTL style, it automatically applies that style (right-to-left reading) regardless
# of the language.
# We weren't sure of where to place that conditional code, so we just defaulted to the LTR style for
# now, until we are more clear on how to handle rtl/ltr conditionally.
'oa_ltr_css': oa_manifest.get("openassessment-ltr.css", ""),
'oa_ltr_js': oa_manifest.get("openassessment-ltr.js", ""),
'oa_editor_textarea_js': oa_manifest.get("openassessment-editor-textarea.js", ""),
'oa_editor_tinymce_js': oa_manifest.get("openassessment-editor-tinymce.js", ""),
}
context = {
'fragment': fragment,
'handler_urls_json': json.dumps(handler_urls),
@@ -126,6 +146,7 @@ def embed_block_view(request, usage_key: UsageKeyV2, view_name: str):
'cms_root_url': cms_root_url,
'view_name': view_name,
'is_development': settings.DEBUG,
'oa_manifest': new_oa_manifest,
}
response = render(request, 'xblock_v2/xblock_iframe.html', context, content_type='text/html')

View File

@@ -828,7 +828,7 @@ openedx-mongodbproxy==0.2.2
# via -r requirements/edx/kernel.in
optimizely-sdk==5.2.0
# via -r requirements/edx/bundled.in
ora2==6.15.1
ora2==6.16.1
# via -r requirements/edx/bundled.in
packaging==24.2
# via

View File

@@ -1396,7 +1396,7 @@ optimizely-sdk==5.2.0
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt
ora2==6.15.1
ora2==6.16.1
# via
# -r requirements/edx/doc.txt
# -r requirements/edx/testing.txt

View File

@@ -1000,7 +1000,7 @@ openedx-mongodbproxy==0.2.2
# via -r requirements/edx/base.txt
optimizely-sdk==5.2.0
# via -r requirements/edx/base.txt
ora2==6.15.1
ora2==6.16.1
# via -r requirements/edx/base.txt
packaging==24.2
# via

View File

@@ -1058,7 +1058,7 @@ openedx-mongodbproxy==0.2.2
# via -r requirements/edx/base.txt
optimizely-sdk==5.2.0
# via -r requirements/edx/base.txt
ora2==6.15.1
ora2==6.16.1
# via -r requirements/edx/base.txt
packaging==24.2
# via