-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1ae58cb
commit 98a068e
Showing
16 changed files
with
968 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[main] | ||
host = https://www.transifex.com | ||
|
||
[o:open-edx:p:p:xblocks:r:annotatable] | ||
file_filter = annotatable/translations/<lang>/LC_MESSAGES/text.po | ||
source_file = annotatable/translations/en/LC_MESSAGES/text.po | ||
source_lang = en | ||
type = PO |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
""" | ||
Init for the AnnotatableXBlock package. | ||
""" | ||
|
||
from .annotatable import AnnotatableXBlock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,207 @@ | ||
import textwrap | ||
from importlib.resources import files | ||
|
||
import markupsafe | ||
from lxml import etree | ||
from web_fragments.fragment import Fragment | ||
from xblock.core import XBlock | ||
from xblock.fields import Scope, String | ||
from xblock.utils.resources import ResourceLoader | ||
from xblock.utils.studio_editable import StudioEditableXBlockMixin | ||
|
||
resource_loader = ResourceLoader(__name__) | ||
|
||
|
||
def _(text): | ||
"""Make '_' a no-op, so we can scrape strings""" | ||
return text | ||
|
||
|
||
class AnnotatableXBlock(StudioEditableXBlockMixin, XBlock): | ||
""" | ||
Annotatable XBlock. | ||
""" | ||
|
||
display_name = String( | ||
display_name=_("Display Name"), | ||
help=_("The display name for this component."), | ||
scope=Scope.settings, | ||
default=_("Annotation"), | ||
) | ||
|
||
data = String( | ||
help=_("XML data for the annotation"), | ||
scope=Scope.content, | ||
default=textwrap.dedent( | ||
markupsafe.Markup( | ||
""" | ||
<annotatable> | ||
<instructions> | ||
<p>Enter your (optional) instructions for the exercise in HTML format.</p> | ||
<p>Annotations are specified by an <code>{}annotation{}</code> tag which may may have the following attributes:</p> | ||
<ul class="instructions-template"> | ||
<li><code>title</code> (optional). Title of the annotation. Defaults to <i>Commentary</i> if omitted.</li> | ||
<li><code>body</code> (<b>required</b>). Text of the annotation.</li> | ||
<li><code>problem</code> (optional). Numeric index of the problem associated with this annotation. This is a zero-based index, so the first problem on the page would have <code>problem="0"</code>.</li> | ||
<li><code>highlight</code> (optional). Possible values: yellow, red, orange, green, blue, or purple. Defaults to yellow if this attribute is omitted.</li> | ||
</ul> | ||
</instructions> | ||
<p>Add your HTML with annotation spans here.</p> | ||
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. <annotation title="My title" body="My comment" highlight="yellow" problem="0">Ut sodales laoreet est, egestas gravida felis egestas nec.</annotation> Aenean at volutpat erat. Cras commodo viverra nibh in aliquam.</p> | ||
<p>Nulla facilisi. <annotation body="Basic annotation example." problem="1">Pellentesque id vestibulum libero.</annotation> Suspendisse potenti. Morbi scelerisque nisi vitae felis dictum mattis. Nam sit amet magna elit. Nullam volutpat cursus est, sit amet sagittis odio vulputate et. Curabitur euismod, orci in vulputate imperdiet, augue lorem tempor purus, id aliquet augue turpis a est. Aenean a sagittis libero. Praesent fringilla pretium magna, non condimentum risus elementum nec. Pellentesque faucibus elementum pharetra. Pellentesque vitae metus eros.</p> | ||
</annotatable> | ||
""" | ||
).format(markupsafe.escape("<"), markupsafe.escape(">")) | ||
), | ||
) | ||
|
||
instructions_html = String( | ||
help=_("Instructions HTML"), | ||
scope=Scope.user_state, | ||
default="", | ||
enforce_type=True | ||
) | ||
|
||
content_html = String( | ||
help=_("Content HTML"), | ||
scope=Scope.user_state, | ||
default="", | ||
enforce_type=True | ||
) | ||
|
||
editable_fields = ["display_name"] | ||
HIGHLIGHT_COLORS = ["yellow", "orange", "purple", "blue", "green"] | ||
|
||
def resource_string(self, path): | ||
"""Handy helper for getting resources from our kit.""" | ||
return files(__package__).joinpath(path).read_text() | ||
|
||
def _get_annotation_class_attr(self, index, el): | ||
"""Returns a dict with the CSS class attribute to set on the annotation | ||
and an XML key to delete from the element. | ||
""" | ||
|
||
attr = {} | ||
cls = ["annotatable-span", "highlight"] | ||
highlight_key = "highlight" | ||
color = el.get(highlight_key) | ||
|
||
if color is not None: | ||
if color in self.HIGHLIGHT_COLORS: | ||
cls.append("highlight-" + color) | ||
attr["_delete"] = highlight_key | ||
attr["value"] = " ".join(cls) | ||
|
||
return {"class": attr} | ||
|
||
def _get_annotation_data_attr(self, index, el): | ||
"""Returns a dict in which the keys are the HTML data attributes | ||
to set on the annotation element. Each data attribute has a | ||
corresponding 'value' and (optional) '_delete' key to specify | ||
an XML attribute to delete. | ||
""" | ||
|
||
data_attrs = {} | ||
attrs_map = { | ||
"body": "data-comment-body", | ||
"title": "data-comment-title", | ||
"problem": "data-problem-id", | ||
} | ||
|
||
for xml_key, html_key in attrs_map.items(): | ||
if xml_key in el.attrib: | ||
value = el.get(xml_key, "") | ||
data_attrs[html_key] = {"value": value, "_delete": xml_key} | ||
|
||
return data_attrs | ||
|
||
def _render_annotation(self, index, el): | ||
"""Renders an annotation element for HTML output.""" | ||
attr = {} | ||
attr.update(self._get_annotation_class_attr(index, el)) | ||
attr.update(self._get_annotation_data_attr(index, el)) | ||
|
||
el.tag = "span" | ||
|
||
for key, value_dict in attr.items(): | ||
el.set(key, value_dict["value"]) | ||
if "_delete" in value_dict and value_dict["_delete"] is not None: | ||
delete_key = value_dict["_delete"] | ||
del el.attrib[delete_key] | ||
|
||
def _render_content(self): | ||
"""Renders annotatable content with annotation spans and returns HTML.""" | ||
|
||
xmltree = etree.fromstring(self.data) | ||
content = etree.tostring(xmltree, encoding="unicode") | ||
|
||
xmltree = etree.fromstring(content) | ||
xmltree.tag = "div" | ||
if "display_name" in xmltree.attrib: | ||
del xmltree.attrib["display_name"] | ||
|
||
index = 0 | ||
for el in xmltree.findall(".//annotation"): | ||
self._render_annotation(index, el) | ||
index += 1 | ||
|
||
return etree.tostring(xmltree, encoding="unicode") | ||
|
||
def _extract_instructions(self, xmltree): | ||
"""Removes <instructions> from the xmltree and returns them as a string, otherwise None.""" | ||
instructions = xmltree.find("instructions") | ||
if instructions is not None: | ||
instructions.tag = "div" | ||
xmltree.remove(instructions) | ||
return etree.tostring(instructions, encoding="unicode") | ||
return None | ||
|
||
def student_view(self, context=None): | ||
""" | ||
Create primary view of the AnnotatableXBlock, shown to students when viewing courses. | ||
""" | ||
if context: | ||
pass # TO-DO: do something based on the context. | ||
xmltree = etree.fromstring(self.data) | ||
self.instructions_html = self._extract_instructions(xmltree) | ||
self.content_html = self._render_content() | ||
|
||
html = self.resource_string("static/html/annotatable.html") | ||
frag = Fragment(html.format(self=self)) | ||
frag.add_css(self.resource_string("static/css/annotatable.css")) | ||
|
||
frag.add_javascript(self.resource_string("static/js/src/annotatable.js")) | ||
frag.initialize_js("AnnotatableXBlock") | ||
return frag | ||
|
||
def studio_view(self, _context=None): | ||
""" | ||
Return the studio view. | ||
""" | ||
html = self.resource_string("static/html/annotatable_editor.html") | ||
frag = Fragment(html.format(self=self)) | ||
frag.add_css(self.resource_string("static/css/annotatable_editor.css")) | ||
|
||
frag.add_javascript(self.resource_string("static/js/src/annotatable_editor.js")) | ||
frag.initialize_js("XMLEditor") | ||
return frag | ||
|
||
@staticmethod | ||
def workbench_scenarios(): | ||
"""Create canned scenario for display in the workbench.""" | ||
return [ | ||
( | ||
"AnnotatableXBlock", | ||
"""<annotatable_xblock/> | ||
""", | ||
), | ||
( | ||
"Multiple AnnotatableXBlock", | ||
"""<vertical_demo> | ||
<annotatable_xblock/> | ||
<annotatable_xblock/> | ||
<annotatable_xblock/> | ||
</vertical_demo> | ||
""", | ||
), | ||
] |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
# Configuration for i18n workflow. | ||
|
||
locales: | ||
- en # English - Source Language | ||
# - am # Amharic | ||
- ar # Arabic | ||
# - az # Azerbaijani | ||
# - bg_BG # Bulgarian (Bulgaria) | ||
# - bn_BD # Bengali (Bangladesh) | ||
# - bn_IN # Bengali (India) | ||
# - bs # Bosnian | ||
# - ca # Catalan | ||
# - ca@valencia # Catalan (Valencia) | ||
# - cs # Czech | ||
# - cy # Welsh | ||
# - da # Danish | ||
# - de_DE # German (Germany) | ||
# - el # Greek | ||
# - en_GB # English (United Kingdom) | ||
# # Don't pull these until we figure out why pages randomly display in these locales, | ||
# # when the user's browser is in English and the user is not logged in. | ||
# #- en@lolcat # LOLCAT English | ||
# #- en@pirate # Pirate English | ||
- es_419 # Spanish (Latin America) | ||
# - es_AR # Spanish (Argentina) | ||
# - es_EC # Spanish (Ecuador) | ||
# - es_ES # Spanish (Spain) | ||
# - es_MX # Spanish (Mexico) | ||
# - es_PE # Spanish (Peru) | ||
# - et_EE # Estonian (Estonia) | ||
# - eu_ES # Basque (Spain) | ||
# - fa # Persian | ||
# - fa_IR # Persian (Iran) | ||
# - fi_FI # Finnish (Finland) | ||
# - fil # Filipino | ||
- fr # French | ||
# - gl # Galician | ||
# - gu # Gujarati | ||
- he # Hebrew | ||
- hi # Hindi | ||
# - hr # Croatian | ||
# - hu # Hungarian | ||
# - hy_AM # Armenian (Armenia) | ||
# - id # Indonesian | ||
# - it_IT # Italian (Italy) | ||
# - ja_JP # Japanese (Japan) | ||
# - kk_KZ # Kazakh (Kazakhstan) | ||
# - km_KH # Khmer (Cambodia) | ||
# - kn # Kannada | ||
- ko_KR # Korean (Korea) | ||
# - lt_LT # Lithuanian (Lithuania) | ||
# - ml # Malayalam | ||
# - mn # Mongolian | ||
# - mr # Marathi | ||
# - ms # Malay | ||
# - nb # Norwegian Bokmål | ||
# - ne # Nepali | ||
# - nl_NL # Dutch (Netherlands) | ||
# - or # Oriya | ||
# - pl # Polish | ||
- pt_BR # Portuguese (Brazil) | ||
# - pt_PT # Portuguese (Portugal) | ||
# - ro # Romanian | ||
- ru # Russian | ||
# - si # Sinhala | ||
# - sk # Slovak | ||
# - sl # Slovenian | ||
# - sq # Albanian | ||
# - sr # Serbian | ||
# - sv # Swedish | ||
# - sw # Swahili | ||
# - ta # Tamil | ||
# - te # Telugu | ||
# - th # Thai | ||
# - tr_TR # Turkish (Turkey) | ||
# - uk # Ukranian | ||
# - ur # Urdu | ||
# - uz # Uzbek | ||
# - vi # Vietnamese | ||
- zh_CN # Chinese (China) | ||
# - zh_HK # Chinese (Hong Kong) | ||
# - zh_TW # Chinese (Taiwan) | ||
|
||
|
||
# The locales used for fake-accented English, for testing. | ||
dummy_locales: | ||
- eo | ||
- rtl # Fake testing language for Arabic | ||
|
||
# Directories we don't search for strings. | ||
ignore_dirs: | ||
- '*/css' | ||
- 'public/js/translations' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
This static directory is for files that should be included in your kit as plain | ||
static files. | ||
|
||
You can ask the runtime for a URL that will retrieve these files with: | ||
|
||
url = self.runtime.local_resource_url(self, "static/js/lib.js") | ||
|
||
The default implementation is very strict though, and will not serve files from | ||
the static directory. It will serve files from a directory named "public". | ||
Create a directory alongside this one named "public", and put files there. | ||
Then you can get a url with code like this: | ||
|
||
url = self.runtime.local_resource_url(self, "public/js/lib.js") | ||
|
||
The sample code includes a function you can use to read the content of files | ||
in the static directory, like this: | ||
|
||
frag.add_javascript(self.resource_string("static/js/my_block.js")) | ||
|
Oops, something went wrong.