From 4a95e8dbd6e6c36560fc87716bfc593ed135d5a1 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Wed, 1 Nov 2023 17:44:59 -0500
Subject: [PATCH 01/18] feat: Tag List component structure
---
.../contentstore/views/component.py | 8 +++--
cms/static/js/views/pages/container.js | 6 ++++
.../js/views/pages/container_subviews.js | 33 ++++++++++++++++++-
cms/templates/container.html | 5 ++-
cms/templates/js/tag-list.underscore | 3 ++
5 files changed, 51 insertions(+), 4 deletions(-)
create mode 100644 cms/templates/js/tag-list.underscore
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 3c31637d4c78..196e039b042b 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -25,7 +25,10 @@
from common.djangoapps.student.auth import has_course_author_access
from common.djangoapps.xblock_django.api import authorable_xblocks, disabled_xblocks
from common.djangoapps.xblock_django.models import XBlockStudioConfigurationFlag
-from cms.djangoapps.contentstore.toggles import use_new_problem_editor
+from cms.djangoapps.contentstore.toggles import (
+ use_new_problem_editor,
+ use_tagging_taxonomy_list_page,
+)
from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration
from openedx.core.djangoapps.content_staging import api as content_staging_api
@@ -61,7 +64,7 @@
"editor-mode-button", "upload-dialog",
"add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
"add-xblock-component-support-legend", "add-xblock-component-support-level", "add-xblock-component-menu-problem",
- "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history",
+ "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history", "tag-list",
"unit-outline", "container-message", "container-access", "license-selector", "copy-clipboard-button",
"edit-title-button",
]
@@ -216,6 +219,7 @@ def container_handler(request, usage_key_string):
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
'templates': CONTAINER_TEMPLATES,
+ 'show_unit_tags': use_tagging_taxonomy_list_page(),
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
})
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index 8f296ebb6b58..ecff5aeec682 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -91,6 +91,12 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
});
this.publishHistory.render();
+ this.tagList = new ContainerSubviews.TagList({
+ el: this.$('#tag-list'),
+ model: this.model
+ });
+ this.tagList.render();
+
this.viewLiveActions = new ContainerSubviews.ViewLiveButtonController({
el: this.$('.nav-actions'),
model: this.model
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index fc7f807257ca..951a1da347c4 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -295,11 +295,42 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
});
+ /**
+ * TagList displays a tree-shaped list of tags.
+ */
+ var TagList = BaseView.extend({
+ // takes XBlockInfo as a model
+
+ initialize: function() {
+ BaseView.prototype.initialize.call(this);
+ this.template = this.loadTemplate('tag-list');
+ this.model.on('sync', this.onSync, this);
+ },
+
+ onSync: function(model) {
+
+ },
+
+ render: function() {
+ HtmlUtils.setHtml(
+ this.$el,
+ HtmlUtils.HTML(
+ this.template({
+
+ })
+ )
+ );
+
+ return this;
+ }
+ });
+
return {
MessageView: MessageView,
ViewLiveButtonController: ViewLiveButtonController,
Publisher: Publisher,
PublishHistory: PublishHistory,
- ContainerAccess: ContainerAccess
+ ContainerAccess: ContainerAccess,
+ TagList: TagList
};
}); // end define();
diff --git a/cms/templates/container.html b/cms/templates/container.html
index 2f83c0c6d5e1..915dbc97d755 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -213,7 +213,10 @@ ${_("Location ID")}
)}
-
+
+ % if show_unit_tags:
+
+ % endif
% endif
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
new file mode 100644
index 000000000000..575d61543d98
--- /dev/null
+++ b/cms/templates/js/tag-list.underscore
@@ -0,0 +1,3 @@
+
+ This is the Tag List component
+
From d8e327ca626f95ce3751645059cee74718256615 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Thu, 2 Nov 2023 15:59:40 -0500
Subject: [PATCH 02/18] feat: Build view to pass tags to UI
---
.../contentstore/views/component.py | 69 ++++++++++++++++++-
cms/templates/container.html | 3 +
2 files changed, 71 insertions(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 196e039b042b..79b92b7ac578 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -32,6 +32,7 @@
from openedx.core.lib.xblock_utils import get_aside_from_xblock, is_xblock_aside
from openedx.core.djangoapps.discussions.models import DiscussionsConfiguration
from openedx.core.djangoapps.content_staging import api as content_staging_api
+from openedx.core.djangoapps.content_tagging.api import get_content_tags
from xmodule.modulestore.django import modulestore # lint-amnesty, pylint: disable=wrong-import-order
from xmodule.modulestore.exceptions import ItemNotFoundError # lint-amnesty, pylint: disable=wrong-import-order
from ..toggles import use_new_unit_page
@@ -196,6 +197,11 @@ def container_handler(request, usage_key_string):
break
index += 1
+ show_unit_tags = use_tagging_taxonomy_list_page()
+ unit_tags = {}
+ if show_unit_tags and is_unit_page:
+ unit_tags = get_unit_tags(usage_key)
+
# Get the status of the user's clipboard so they can paste components if they have something to paste
user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request)
return render_to_response('container.html', {
@@ -219,7 +225,8 @@ def container_handler(request, usage_key_string):
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
'templates': CONTAINER_TEMPLATES,
- 'show_unit_tags': use_tagging_taxonomy_list_page(),
+ 'show_unit_tags': show_unit_tags,
+ 'unit_tags': unit_tags,
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
})
@@ -602,3 +609,63 @@ def component_handler(request, usage_key_string, handler, suffix=''):
)
return webob_to_django_response(resp)
+
+
+def get_unit_tags(usage_key):
+ """
+ Get the tags of a Unit and build a json to be read by the UI
+ """
+ # Get content tags from content tagging API
+ content_tags = get_content_tags(usage_key)
+
+ # Group content tags by taxonomy
+ taxonomy_dict = {}
+ for content_tag in content_tags:
+ taxonomy_name = content_tag.name
+ if taxonomy_name not in taxonomy_dict:
+ taxonomy_dict[taxonomy_name] = []
+ taxonomy_dict[taxonomy_name].append(content_tag)
+
+ taxonomy_list = []
+ total_count = 0
+
+ # Build a tag tree for each taxonomy
+ for taxonomy_name, content_tag_list in taxonomy_dict.items():
+ tags = {}
+ root_ids = []
+
+ def handle_tag(tag, child_tag_id=None):
+ # Group each tag by parent to build a tree
+ if tag.id not in tags:
+ tags[tag.id] = {
+ 'value': tag.value,
+ 'children': [],
+ }
+ if child_tag_id:
+ # Add a child into the children list
+ tags[tag.id].get('children').append(tags[child_tag_id])
+ if tag.parent_id is None:
+ if tag.id not in root_ids:
+ root_ids.append(tag.id)
+ else:
+ # Group all the lineage of this tag
+ handle_tag(tag.parent, tag.id)
+
+ for content_tag in content_tag_list:
+ handle_tag(content_tag.tag)
+
+ count = len(tags)
+ # Add the tree to the taxonomy list
+ taxonomy_list.append({
+ 'value': taxonomy_name,
+ 'tags': [tags[tag_id] for tag_id in root_ids],
+ 'count': count,
+ })
+ total_count += count
+
+ unit_tags = {
+ 'count': total_count,
+ 'taxonomies': taxonomy_list,
+ }
+
+ return unit_tags
diff --git a/cms/templates/container.html b/cms/templates/container.html
index 915dbc97d755..ecb59f286a88 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -216,6 +216,9 @@ ${_("Location ID")}
% if show_unit_tags:
+
+ ${unit_tags}
+
% endif
% endif
From e36cb84be4bced5f701904acae4dcc65eb50a61b Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Fri, 3 Nov 2023 14:44:18 -0500
Subject: [PATCH 03/18] feat: Building Tag list component on unit page
---
.../contentstore/views/component.py | 18 +++++---
cms/static/js/views/pages/container.js | 41 ++++++++++++++---
.../js/views/pages/container_subviews.js | 33 +-------------
cms/static/sass/elements/_controls.scss | 28 ++++++++++++
cms/static/sass/views/_container.scss | 45 +++++++++++++++++++
cms/templates/container.html | 35 +++++++++++++--
cms/templates/js/tag-list.underscore | 3 --
7 files changed, 152 insertions(+), 51 deletions(-)
delete mode 100644 cms/templates/js/tag-list.underscore
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 79b92b7ac578..5f0368a98bfb 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -65,7 +65,7 @@
"editor-mode-button", "upload-dialog",
"add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
"add-xblock-component-support-legend", "add-xblock-component-support-level", "add-xblock-component-menu-problem",
- "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history", "tag-list",
+ "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history",
"unit-outline", "container-message", "container-access", "license-selector", "copy-clipboard-button",
"edit-title-button",
]
@@ -621,16 +621,17 @@ def get_unit_tags(usage_key):
# Group content tags by taxonomy
taxonomy_dict = {}
for content_tag in content_tags:
- taxonomy_name = content_tag.name
- if taxonomy_name not in taxonomy_dict:
- taxonomy_dict[taxonomy_name] = []
- taxonomy_dict[taxonomy_name].append(content_tag)
+ taxonomy_id = content_tag.taxonomy_id
+ if taxonomy_id:
+ if taxonomy_id not in taxonomy_dict:
+ taxonomy_dict[taxonomy_id] = []
+ taxonomy_dict[taxonomy_id].append(content_tag)
taxonomy_list = []
total_count = 0
# Build a tag tree for each taxonomy
- for taxonomy_name, content_tag_list in taxonomy_dict.items():
+ for content_tag_list in taxonomy_dict.values():
tags = {}
root_ids = []
@@ -654,10 +655,13 @@ def handle_tag(tag, child_tag_id=None):
for content_tag in content_tag_list:
handle_tag(content_tag.tag)
+ taxonomy = content_tag_list[0].taxonomy
+
count = len(tags)
# Add the tree to the taxonomy list
taxonomy_list.append({
- 'value': taxonomy_name,
+ 'id': taxonomy.id,
+ 'value': taxonomy.name,
'tags': [tags[tag_id] for tag_id in root_ids],
'count': count,
})
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index ecff5aeec682..68bcc06d323a 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -27,6 +27,8 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
'click .show-actions-menu-button': 'showXBlockActionsMenu',
'click .new-component-button': 'scrollToNewComponentButtons',
'click .paste-component-button': 'pasteComponent',
+ 'click .wrapper-tag-header': 'expandTagContainer',
+ 'click .taxonomy-label': 'expandContentTag',
},
options: {
@@ -91,12 +93,6 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
});
this.publishHistory.render();
- this.tagList = new ContainerSubviews.TagList({
- el: this.$('#tag-list'),
- model: this.model
- });
- this.tagList.render();
-
this.viewLiveActions = new ContainerSubviews.ViewLiveButtonController({
el: this.$('.nav-actions'),
model: this.model
@@ -114,6 +110,37 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel");
},
+ expandTagContainer: function() {
+ var $content = this.$('.wrapper-tags .wrapper-tag-content'),
+ $icon = this.$('.wrapper-tags .wrapper-tag-header .icon');
+
+ if ($content.hasClass('is-hidden')) {
+ $content.removeClass('is-hidden');
+ $icon.addClass('fa-caret-up');
+ $icon.removeClass('fa-caret-down');
+ } else {
+ $content.addClass('is-hidden');
+ $icon.removeClass('fa-caret-up');
+ $icon.addClass('fa-caret-down');
+ }
+ },
+
+ expandContentTag: function(event) {
+ var taxonomyValue = event.target.id,
+ $content = this.$(`.wrapper-tags .content-tags-${taxonomyValue}`),
+ $icon = this.$(`.wrapper-tags .taxonomy-${taxonomyValue} .icon`);
+
+ if ($content.hasClass('is-hidden')) {
+ $content.removeClass('is-hidden');
+ $icon.addClass('fa-caret-up');
+ $icon.removeClass('fa-caret-down');
+ } else {
+ $content.addClass('is-hidden');
+ $icon.removeClass('fa-caret-up');
+ $icon.addClass('fa-caret-down');
+ }
+ },
+
getViewParameters: function() {
return {
el: this.$('.wrapper-xblock'),
@@ -131,6 +158,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
xblockView = this.xblockView,
loadingElement = this.$('.ui-loading'),
unitLocationTree = this.$('.unit-location'),
+ unitTags = this.$('.unit-tags'),
hiddenCss = 'is-hidden';
loadingElement.removeClass(hiddenCss);
@@ -156,6 +184,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
// Refresh the views now that the xblock is visible
self.onXBlockRefresh(xblockView);
unitLocationTree.removeClass(hiddenCss);
+ unitTags.removeClass(hiddenCss);
// Re-enable Backbone events for any updated DOM elements
self.delegateEvents();
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 951a1da347c4..fc7f807257ca 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -295,42 +295,11 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
});
- /**
- * TagList displays a tree-shaped list of tags.
- */
- var TagList = BaseView.extend({
- // takes XBlockInfo as a model
-
- initialize: function() {
- BaseView.prototype.initialize.call(this);
- this.template = this.loadTemplate('tag-list');
- this.model.on('sync', this.onSync, this);
- },
-
- onSync: function(model) {
-
- },
-
- render: function() {
- HtmlUtils.setHtml(
- this.$el,
- HtmlUtils.HTML(
- this.template({
-
- })
- )
- );
-
- return this;
- }
- });
-
return {
MessageView: MessageView,
ViewLiveButtonController: ViewLiveButtonController,
Publisher: Publisher,
PublishHistory: PublishHistory,
- ContainerAccess: ContainerAccess,
- TagList: TagList
+ ContainerAccess: ContainerAccess
};
}); // end define();
diff --git a/cms/static/sass/elements/_controls.scss b/cms/static/sass/elements/_controls.scss
index 8420961ab4ab..3a9c3af3db89 100644
--- a/cms/static/sass/elements/_controls.scss
+++ b/cms/static/sass/elements/_controls.scss
@@ -119,6 +119,34 @@
}
}
+// inverse primary button
+%btn-primary-inverse {
+ @extend %ui-btn-primary;
+
+ background: theme-color("inverse");
+ border-color: theme-color("primary");
+ color: theme-color("primary");
+
+ &:hover,
+ &:active {
+ background: theme-color("primary");
+ border-color: $uxpl-blue-hover-active;
+ color: theme-color("inverse");
+ }
+
+ &.current,
+ &.active {
+ background: theme-color("primary");
+ border-color: $uxpl-blue-hover-active;
+ color: theme-color("inverse");
+
+ &:hover,
+ &:active {
+ background: theme-color("primary");
+ }
+ }
+}
+
// +Secondary Button - Extends
// ====================
// gray secondary button
diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss
index cf8824ca5110..7fc905cf98a6 100644
--- a/cms/static/sass/views/_container.scss
+++ b/cms/static/sass/views/_container.scss
@@ -242,6 +242,51 @@
}
}
+ .unit-tags {
+ .wrapper-tags {
+ margin-bottom: $baseline;
+ padding: ($baseline*0.75);
+ background-color: $white;
+
+ .wrapper-tag-header {
+ display: flex;
+ justify-content: space-between;
+
+ .tag-title {
+ font-weight: bold;
+ }
+
+ .count-badge {
+ background-color: $gray-l5;
+ border-radius: 50%;
+ display: inline-block;
+ padding: 0px 8px;
+ }
+ }
+
+ .action-primary {
+ @extend %btn-primary-inverse;
+
+ width: 100%;
+ margin: 16px 2px 8px 2px;
+ }
+
+ .wrapper-tag-content {
+ background-color: $white;
+
+ .content-taxonomies {
+ display: flex;
+ flex-direction: column;
+ padding-top: 10px;
+
+ .taxonomy-label {
+ padding: 8px 0px;
+ }
+ }
+ }
+ }
+ }
+
// versioning widget
.unit-publish-history {
.wrapper-last-publish {
diff --git a/cms/templates/container.html b/cms/templates/container.html
index ecb59f286a88..5a31d0fb564a 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -215,9 +215,38 @@ ${_("Location ID")}
% if show_unit_tags:
-
-
- ${unit_tags}
+
% endif
% endif
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
deleted file mode 100644
index 575d61543d98..000000000000
--- a/cms/templates/js/tag-list.underscore
+++ /dev/null
@@ -1,3 +0,0 @@
-
- This is the Tag List component
-
From b383efe3424b81cfe45e1c94205ad2302562d7f2 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Mon, 6 Nov 2023 15:08:38 -0500
Subject: [PATCH 04/18] refactor: Extract tag list as a separated component
---
.../contentstore/views/component.py | 17 +++--
.../xblock_storage_handlers/view_handlers.py | 3 +
cms/static/js/models/xblock_info.js | 4 ++
cms/static/js/views/pages/container.js | 39 ++---------
.../js/views/pages/container_subviews.js | 69 ++++++++++++++++++-
cms/templates/container.html | 34 +--------
cms/templates/js/tag-list.underscore | 35 ++++++++++
7 files changed, 125 insertions(+), 76 deletions(-)
create mode 100644 cms/templates/js/tag-list.underscore
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 5f0368a98bfb..3a906d48ceed 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -65,7 +65,7 @@
"editor-mode-button", "upload-dialog",
"add-xblock-component", "add-xblock-component-button", "add-xblock-component-menu",
"add-xblock-component-support-legend", "add-xblock-component-support-level", "add-xblock-component-menu-problem",
- "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history",
+ "xblock-string-field-editor", "xblock-access-editor", "publish-xblock", "publish-history", "tag-list",
"unit-outline", "container-message", "container-access", "license-selector", "copy-clipboard-button",
"edit-title-button",
]
@@ -182,9 +182,14 @@ def container_handler(request, usage_key_string):
prev_url = quote_plus(prev_url) if prev_url else None
next_url = quote_plus(next_url) if next_url else None
+ show_unit_tags = use_tagging_taxonomy_list_page()
+ unit_tags = None
+ if show_unit_tags and is_unit_page:
+ unit_tags = get_unit_tags(usage_key)
+
# Fetch the XBlock info for use by the container page. Note that it includes information
# about the block's ancestors and siblings for use by the Unit Outline.
- xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page)
+ xblock_info = create_xblock_info(xblock, include_ancestor_info=is_unit_page, tags=unit_tags)
if is_unit_page:
add_container_page_publishing_info(xblock, xblock_info)
@@ -197,11 +202,6 @@ def container_handler(request, usage_key_string):
break
index += 1
- show_unit_tags = use_tagging_taxonomy_list_page()
- unit_tags = {}
- if show_unit_tags and is_unit_page:
- unit_tags = get_unit_tags(usage_key)
-
# Get the status of the user's clipboard so they can paste components if they have something to paste
user_clipboard = content_staging_api.get_user_clipboard_json(request.user.id, request)
return render_to_response('container.html', {
@@ -225,8 +225,7 @@ def container_handler(request, usage_key_string):
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
'templates': CONTAINER_TEMPLATES,
- 'show_unit_tags': show_unit_tags,
- 'unit_tags': unit_tags,
+ 'show_unit_tags': show_unit_tags,
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
})
diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
index 425f97e3751f..0a800ade7ba0 100644
--- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
+++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
@@ -1091,6 +1091,7 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
course=None,
is_concise=False,
summary_configuration=None,
+ tags=None,
):
"""
Creates the information needed for client-side XBlockInfo.
@@ -1383,6 +1384,8 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
)
else:
xblock_info["ancestor_has_staff_lock"] = False
+ if tags is not None:
+ xblock_info["tags"] = tags
if course_outline:
if xblock_info["has_explicit_staff_lock"]:
diff --git a/cms/static/js/models/xblock_info.js b/cms/static/js/models/xblock_info.js
index d7d9ef278b01..5e470c986fbd 100644
--- a/cms/static/js/models/xblock_info.js
+++ b/cms/static/js/models/xblock_info.js
@@ -173,6 +173,10 @@ define(
* True if summary configuration is enabled.
*/
summary_configuration_enabled: null,
+ /**
+ * List of tags of the unit. This list is managed by the content_tagging module.
+ */
+ tags: null,
},
initialize: function() {
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index 68bcc06d323a..b757c7f1b07c 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -27,8 +27,6 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
'click .show-actions-menu-button': 'showXBlockActionsMenu',
'click .new-component-button': 'scrollToNewComponentButtons',
'click .paste-component-button': 'pasteComponent',
- 'click .wrapper-tag-header': 'expandTagContainer',
- 'click .taxonomy-label': 'expandContentTag',
},
options: {
@@ -99,6 +97,12 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
});
this.viewLiveActions.render();
+ this.tagListView = new ContainerSubviews.TagList({
+ el: this.$('.unit-tags'),
+ model: this.model
+ });
+ this.tagListView.render();
+
this.unitOutlineView = new UnitOutlineView({
el: this.$('.wrapper-unit-overview'),
model: this.model
@@ -110,37 +114,6 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
this.clipboardBroadcastChannel = new BroadcastChannel("studio_clipboard_channel");
},
- expandTagContainer: function() {
- var $content = this.$('.wrapper-tags .wrapper-tag-content'),
- $icon = this.$('.wrapper-tags .wrapper-tag-header .icon');
-
- if ($content.hasClass('is-hidden')) {
- $content.removeClass('is-hidden');
- $icon.addClass('fa-caret-up');
- $icon.removeClass('fa-caret-down');
- } else {
- $content.addClass('is-hidden');
- $icon.removeClass('fa-caret-up');
- $icon.addClass('fa-caret-down');
- }
- },
-
- expandContentTag: function(event) {
- var taxonomyValue = event.target.id,
- $content = this.$(`.wrapper-tags .content-tags-${taxonomyValue}`),
- $icon = this.$(`.wrapper-tags .taxonomy-${taxonomyValue} .icon`);
-
- if ($content.hasClass('is-hidden')) {
- $content.removeClass('is-hidden');
- $icon.addClass('fa-caret-up');
- $icon.removeClass('fa-caret-down');
- } else {
- $content.addClass('is-hidden');
- $icon.removeClass('fa-caret-up');
- $icon.addClass('fa-caret-down');
- }
- },
-
getViewParameters: function() {
return {
el: this.$('.wrapper-xblock'),
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index fc7f807257ca..a90674cc08ba 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -295,11 +295,78 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
});
+ /**
+ * PublishHistory displays the tags of a unit.
+ */
+ var TagList = BaseView.extend({
+ // takes XBlockInfo as a model
+
+ events: {
+ 'click .wrapper-tag-header': 'expandTagContainer',
+ 'click .tagging-label': 'expandContentTag',
+ },
+
+ initialize: function() {
+ BaseView.prototype.initialize.call(this);
+ this.template = this.loadTemplate('tag-list');
+ this.model.on('sync', this.onSync, this);
+ },
+
+ onSync: function(model) {
+
+ },
+
+ expandTagContainer: function() {
+ var $content = this.$('.wrapper-tags .wrapper-tag-content'),
+ $icon = this.$('.wrapper-tags .wrapper-tag-header .icon');
+
+ if ($content.hasClass('is-hidden')) {
+ $content.removeClass('is-hidden');
+ $icon.addClass('fa-caret-up');
+ $icon.removeClass('fa-caret-down');
+ } else {
+ $content.addClass('is-hidden');
+ $icon.removeClass('fa-caret-up');
+ $icon.addClass('fa-caret-down');
+ }
+ },
+
+ expandContentTag: function(event) {
+ var taxonomyValue = event.target.id,
+ $content = this.$(`.wrapper-tags .content-tags-${taxonomyValue}`),
+ $icon = this.$(`.wrapper-tags .label-${taxonomyValue} .icon`);
+
+ if ($content.hasClass('is-hidden')) {
+ $content.removeClass('is-hidden');
+ $icon.addClass('fa-caret-up');
+ $icon.removeClass('fa-caret-down');
+ } else {
+ $content.addClass('is-hidden');
+ $icon.removeClass('fa-caret-up');
+ $icon.addClass('fa-caret-down');
+ }
+ },
+
+ render: function() {
+ HtmlUtils.setHtml(
+ this.$el,
+ HtmlUtils.HTML(
+ this.template({
+ tags: this.model.get('tags'),
+ })
+ )
+ );
+
+ return this;
+ }
+ });
+
return {
MessageView: MessageView,
ViewLiveButtonController: ViewLiveButtonController,
Publisher: Publisher,
PublishHistory: PublishHistory,
- ContainerAccess: ContainerAccess
+ ContainerAccess: ContainerAccess,
+ TagList: TagList
};
}); // end define();
diff --git a/cms/templates/container.html b/cms/templates/container.html
index 5a31d0fb564a..4c702670b3b7 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -215,39 +215,7 @@ ${_("Location ID")}
% if show_unit_tags:
-
+
% endif
% endif
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
new file mode 100644
index 000000000000..e014227bf6bc
--- /dev/null
+++ b/cms/templates/js/tag-list.underscore
@@ -0,0 +1,35 @@
+<% if (tags !== null) { %>
+
+<% } %>
From a9f2afcdb89d67b15ab1633801adf4528d75feba Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Tue, 7 Nov 2023 11:16:30 -0500
Subject: [PATCH 05/18] feat: Render tag children
---
.../contentstore/views/component.py | 1 +
.../js/views/pages/container_subviews.js | 61 +++++++++++++++++--
cms/static/sass/views/_container.scss | 8 ++-
cms/templates/js/tag-list.underscore | 6 +-
4 files changed, 65 insertions(+), 11 deletions(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 3a906d48ceed..c26369773c34 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -638,6 +638,7 @@ def handle_tag(tag, child_tag_id=None):
# Group each tag by parent to build a tree
if tag.id not in tags:
tags[tag.id] = {
+ 'id': tag.id,
'value': tag.value,
'children': [],
}
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index a90674cc08ba..f83807bd90e8 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -296,7 +296,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
});
/**
- * PublishHistory displays the tags of a unit.
+ * TagList displays the tags of a unit.
*/
var TagList = BaseView.extend({
// takes XBlockInfo as a model
@@ -313,7 +313,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
},
onSync: function(model) {
-
+ if (ViewUtils.hasChangedAttributes(model, ['tags'])) {
+ this.render();
+ }
},
expandTagContainer: function() {
@@ -332,9 +334,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
},
expandContentTag: function(event) {
- var taxonomyValue = event.target.id,
- $content = this.$(`.wrapper-tags .content-tags-${taxonomyValue}`),
- $icon = this.$(`.wrapper-tags .label-${taxonomyValue} .icon`);
+ var contentId = event.target.id,
+ $content = this.$(`.wrapper-tags .content-tags-${contentId}`),
+ $icon = this.$(`.wrapper-tags .tagging-label-${contentId} .icon`);
if ($content.hasClass('is-hidden')) {
$content.removeClass('is-hidden');
@@ -347,6 +349,53 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
},
+ renderTagElements: function(tags, depth, parentId) {
+ const tagListElement = this;
+ tags.forEach(function(tag) {
+ const parentElement = document.querySelector(`.content-tags-${parentId}`);
+ var tagContentElement = document.createElement('div'),
+ tagValueElement = document.createElement('span');
+
+ // Element that contains the tag value and the arrow icon
+ tagContentElement.style.marginLeft = `${depth}em`;
+ tagContentElement.className = 'tagging-label';
+
+ // Element that contains the tag value
+ tagValueElement.textContent = tag.value;
+ tagValueElement.id = `tag-${tag.id}`;
+
+ tagContentElement.appendChild(tagValueElement);
+ parentElement.appendChild(tagContentElement);
+
+ if (tag.children.length > 0) {
+ var tagIconElement = document.createElement('span'),
+ tagChildrenElement = document.createElement('div');
+
+ // Arrow icon
+ tagIconElement.className = 'icon fa fa-caret-down';
+ tagIconElement.ariaHidden = 'true';
+ tagIconElement.id = `tag-${tag.id}`;
+
+ // Element that contains the children of this tag
+ tagChildrenElement.className = `content-tags-tag-${tag.id} is-hidden`;
+
+ tagContentElement.appendChild(tagIconElement);
+ parentElement.appendChild(tagChildrenElement);
+
+ // Render children
+ tagListElement.renderTagElements(tag.children, depth + 1, `tag-${tag.id}`);
+ }
+ });
+ },
+
+ renderTags: function() {
+ const taxonomies = this.model.get('tags').taxonomies;
+ const tagListElement = this;
+ taxonomies.forEach(function(taxonomy) {
+ tagListElement.renderTagElements(taxonomy.tags, 1, `tax-${taxonomy.id}`);
+ });
+ },
+
render: function() {
HtmlUtils.setHtml(
this.$el,
@@ -357,6 +406,8 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
)
);
+ this.renderTags();
+
return this;
}
});
diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss
index 7fc905cf98a6..521b8cf6d3c1 100644
--- a/cms/static/sass/views/_container.scss
+++ b/cms/static/sass/views/_container.scss
@@ -279,8 +279,12 @@
flex-direction: column;
padding-top: 10px;
- .taxonomy-label {
- padding: 8px 0px;
+ .tagging-label {
+ padding: 4px 0px;
+ }
+
+ .icon {
+ margin-left: 5px;
}
}
}
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
index e014227bf6bc..b44b9f4eeb3b 100644
--- a/cms/templates/js/tag-list.underscore
+++ b/cms/templates/js/tag-list.underscore
@@ -16,15 +16,13 @@
<% for (var i = 0; i < tags.taxonomies.length; i++) {
var taxonomy = tags.taxonomies[i];
%>
-
+
-
- <%- taxonomy.tags %>
-
+
<% } %>
From e19ff5538cf7b3d7cebf8e2fd1ff9ef3163bb0b8 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Tue, 7 Nov 2023 12:55:24 -0500
Subject: [PATCH 06/18] feat: Manage tags button added on component menu
---
cms/static/js/views/pages/container.js | 5 +++++
cms/static/js/views/pages/container_subviews.js | 12 +++++++-----
cms/templates/studio_xblock_wrapper.html | 8 +++++++-
3 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index b757c7f1b07c..f750e01102a8 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -27,6 +27,7 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
'click .show-actions-menu-button': 'showXBlockActionsMenu',
'click .new-component-button': 'scrollToNewComponentButtons',
'click .paste-component-button': 'pasteComponent',
+ 'click .tags-button': 'openManageTags',
},
options: {
@@ -417,6 +418,10 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
this.duplicateComponent(this.findXBlockElement(event.target));
},
+ openManageTags: function() {
+ // TODO open manage tags drawer
+ },
+
showMoveXBlockModal: function(event) {
var xblockElement = this.findXBlockElement(event.target),
parentXBlockElement = xblockElement.parents('.studio-xblock-wrapper'),
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index f83807bd90e8..8af46f8fd19b 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -389,11 +389,13 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
},
renderTags: function() {
- const taxonomies = this.model.get('tags').taxonomies;
- const tagListElement = this;
- taxonomies.forEach(function(taxonomy) {
- tagListElement.renderTagElements(taxonomy.tags, 1, `tax-${taxonomy.id}`);
- });
+ if (this.model.get('tags') !== null) {
+ const taxonomies = this.model.get('tags').taxonomies;
+ const tagListElement = this;
+ taxonomies.forEach(function(taxonomy) {
+ tagListElement.renderTagElements(taxonomy.tags, 1, `tax-${taxonomy.id}`);
+ });
+ }
},
render: function() {
diff --git a/cms/templates/studio_xblock_wrapper.html b/cms/templates/studio_xblock_wrapper.html
index a027cc764bd7..38a61ff49106 100644
--- a/cms/templates/studio_xblock_wrapper.html
+++ b/cms/templates/studio_xblock_wrapper.html
@@ -7,13 +7,14 @@
from openedx.core.djangolib.js_utils import (
dump_js_escaped_json, js_escaped_string
)
-from cms.djangoapps.contentstore.toggles import use_new_text_editor, use_new_problem_editor, use_new_video_editor, use_video_gallery_flow
+from cms.djangoapps.contentstore.toggles import use_new_text_editor, use_new_problem_editor, use_new_video_editor, use_video_gallery_flow, use_tagging_taxonomy_list_page
%>
<%
use_new_editor_text = use_new_text_editor()
use_new_editor_video = use_new_video_editor()
use_new_editor_problem = use_new_problem_editor()
use_new_video_gallery_flow = use_video_gallery_flow()
+use_tagging = use_tagging_taxonomy_list_page()
xblock_url = xblock_studio_url(xblock)
show_inline = xblock.has_children and not xblock_url
section_class = "level-nesting" if show_inline else "level-element"
@@ -118,6 +119,11 @@
${_("Duplicate")}
% endif
+ % if use_tagging:
+
+ ${_("Manage tags")}
+
+ % endif
% if can_move:
${_("Move")}
From 7305ed7c2825b3c780a2ae65fe3f906e2edc0204 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Tue, 7 Nov 2023 13:37:36 -0500
Subject: [PATCH 07/18] chore: Lint
---
.../contentstore/views/component.py | 44 +++++++++----------
1 file changed, 22 insertions(+), 22 deletions(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index c26369773c34..79c8d4c1c6eb 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -225,7 +225,7 @@ def container_handler(request, usage_key_string):
'draft_preview_link': preview_lms_link,
'published_preview_link': lms_link,
'templates': CONTAINER_TEMPLATES,
- 'show_unit_tags': show_unit_tags,
+ 'show_unit_tags': show_unit_tags,
# Status of the user's clipboard, exactly as would be returned from the "GET clipboard" REST API.
'user_clipboard': user_clipboard,
})
@@ -616,7 +616,7 @@ def get_unit_tags(usage_key):
"""
# Get content tags from content tagging API
content_tags = get_content_tags(usage_key)
-
+
# Group content tags by taxonomy
taxonomy_dict = {}
for content_tag in content_tags:
@@ -625,35 +625,35 @@ def get_unit_tags(usage_key):
if taxonomy_id not in taxonomy_dict:
taxonomy_dict[taxonomy_id] = []
taxonomy_dict[taxonomy_id].append(content_tag)
-
+
taxonomy_list = []
total_count = 0
+ def handle_tag(tags, root_ids, tag, child_tag_id=None):
+ # Group each tag by parent to build a tree
+ if tag.id not in tags:
+ tags[tag.id] = {
+ 'id': tag.id,
+ 'value': tag.value,
+ 'children': [],
+ }
+ if child_tag_id:
+ # Add a child into the children list
+ tags[tag.id].get('children').append(tags[child_tag_id])
+ if tag.parent_id is None:
+ if tag.id not in root_ids:
+ root_ids.append(tag.id)
+ else:
+ # Group all the lineage of this tag
+ handle_tag(tags, root_ids, tag.parent, tag.id)
+
# Build a tag tree for each taxonomy
for content_tag_list in taxonomy_dict.values():
tags = {}
root_ids = []
- def handle_tag(tag, child_tag_id=None):
- # Group each tag by parent to build a tree
- if tag.id not in tags:
- tags[tag.id] = {
- 'id': tag.id,
- 'value': tag.value,
- 'children': [],
- }
- if child_tag_id:
- # Add a child into the children list
- tags[tag.id].get('children').append(tags[child_tag_id])
- if tag.parent_id is None:
- if tag.id not in root_ids:
- root_ids.append(tag.id)
- else:
- # Group all the lineage of this tag
- handle_tag(tag.parent, tag.id)
-
for content_tag in content_tag_list:
- handle_tag(content_tag.tag)
+ handle_tag(tags, root_ids, content_tag.tag)
taxonomy = content_tag_list[0].taxonomy
From d79cf9728b86a14c31f12ce699e4c9abf1e5629a Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Tue, 7 Nov 2023 14:37:20 -0500
Subject: [PATCH 08/18] style: Nits
---
.../contentstore/views/component.py | 22 ++++++++++---------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 79c8d4c1c6eb..5b2ad16b778a 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -653,19 +653,21 @@ def handle_tag(tags, root_ids, tag, child_tag_id=None):
root_ids = []
for content_tag in content_tag_list:
- handle_tag(tags, root_ids, content_tag.tag)
+ if content_tag.tag:
+ handle_tag(tags, root_ids, content_tag.tag)
taxonomy = content_tag_list[0].taxonomy
- count = len(tags)
- # Add the tree to the taxonomy list
- taxonomy_list.append({
- 'id': taxonomy.id,
- 'value': taxonomy.name,
- 'tags': [tags[tag_id] for tag_id in root_ids],
- 'count': count,
- })
- total_count += count
+ if tags:
+ count = len(tags)
+ # Add the tree to the taxonomy list
+ taxonomy_list.append({
+ 'id': taxonomy.id,
+ 'value': taxonomy.name,
+ 'tags': [tags[tag_id] for tag_id in root_ids],
+ 'count': count,
+ })
+ total_count += count
unit_tags = {
'count': total_count,
From b4235073855a5528d8ec34c4480aa8ef1c6d258a Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Wed, 8 Nov 2023 15:51:25 -0500
Subject: [PATCH 09/18] style: Adding comments in get_unit_tags
---
cms/djangoapps/contentstore/views/component.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 5b2ad16b778a..b00215be4243 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -613,6 +613,10 @@ def component_handler(request, usage_key_string, handler, suffix=''):
def get_unit_tags(usage_key):
"""
Get the tags of a Unit and build a json to be read by the UI
+
+ Note: When migrating the `TagList` subview from `container_subview.js` to the course-authoring MFE,
+ this function can be simplified to use the REST API of openedx-learning,
+ which already provides this grouping + sorting logic.
"""
# Get content tags from content tagging API
content_tags = get_content_tags(usage_key)
From b96fd33f1893b970fa519c742bd6c274d7b0751a Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Thu, 9 Nov 2023 16:29:54 -0500
Subject: [PATCH 10/18] feat: Extract open and close tagging drawer to utility
functions
---
cms/static/js/views/course_outline.js | 39 +++------------
.../js/views/utils/tagging_drawer_utils.js | 48 +++++++++++++++++++
2 files changed, 54 insertions(+), 33 deletions(-)
create mode 100644 cms/static/js/views/utils/tagging_drawer_utils.js
diff --git a/cms/static/js/views/course_outline.js b/cms/static/js/views/course_outline.js
index 2faccde97e36..4b7107cc4ddc 100644
--- a/cms/static/js/views/course_outline.js
+++ b/cms/static/js/views/course_outline.js
@@ -11,10 +11,12 @@
define(['jquery', 'underscore', 'js/views/xblock_outline', 'edx-ui-toolkit/js/utils/string-utils',
'common/js/components/utils/view_utils', 'js/views/utils/xblock_utils',
'js/models/xblock_outline_info', 'js/views/modals/course_outline_modals', 'js/utils/drag_and_drop',
- 'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt',],
+ 'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt',
+ 'js/views/utils/tagging_drawer_utils',],
function(
$, _, XBlockOutlineView, StringUtils, ViewUtils, XBlockViewUtils,
- XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger, NotificationView, PromptView
+ XBlockOutlineInfo, CourseOutlineModalsFactory, ContentDragger, NotificationView, PromptView,
+ TaggingDrawerUtils
) {
var CourseOutlineView = XBlockOutlineView.extend({
// takes XBlockOutlineInfo as a model
@@ -458,41 +460,12 @@ function(
event.stopPropagation();
},
- closeManageTagsDrawer(drawer, drawerCover) {
- $(drawerCover).css('display', 'none');
- $(drawer).empty();
- $(drawer).css('display', 'none');
- $('body').removeClass('drawer-open');
- },
-
- openManageTagsDrawer(event) {
- const drawer = document.querySelector("#manage-tags-drawer");
- const drawerCover = document.querySelector(".drawer-cover")
+ openManageTagsDrawer() {
const article = document.querySelector('[data-taxonomy-tags-widget-url]');
const taxonomyTagsWidgetUrl = $(article).attr('data-taxonomy-tags-widget-url');
const contentId = this.model.get('id');
- // Add handler to close drawer when dark background is clicked
- $(drawerCover).click(function() {
- this.closeManageTagsDrawer(drawer, drawerCover);
- }.bind(this));
-
- // Add event listen to close drawer when close button is clicked from within the Iframe
- window.addEventListener("message", function (event) {
- if (event.data === 'closeManageTagsDrawer') {
- this.closeManageTagsDrawer(drawer, drawerCover)
- }
- }.bind(this));
-
- $(drawerCover).css('display', 'block');
- // xss-lint: disable=javascript-jquery-html
- $(drawer).html(
- ``
- );
- $(drawer).css('display', 'block');
-
- // Prevent background from being scrollable when drawer is open
- $('body').addClass('drawer-open');
+ TaggingDrawerUtils.openDrawer(taxonomyTagsWidgetUrl, contentId);
},
addButtonActions: function(element) {
diff --git a/cms/static/js/views/utils/tagging_drawer_utils.js b/cms/static/js/views/utils/tagging_drawer_utils.js
new file mode 100644
index 000000000000..4733a4078335
--- /dev/null
+++ b/cms/static/js/views/utils/tagging_drawer_utils.js
@@ -0,0 +1,48 @@
+/**
+ * Provides utilities for open and close the tagging drawer to manage tags.
+ */
+define(['jquery'],
+function($,) {
+ 'use strict';
+
+ var closeDrawer, openDrawer;
+
+ closeDrawer = function(drawer, drawerCover) {
+ $(drawerCover).css('display', 'none');
+ $(drawer).empty();
+ $(drawer).css('display', 'none');
+ $('body').removeClass('drawer-open');
+ };
+
+ openDrawer = function(taxonomyTagsWidgetUrl, contentId) {
+ const drawer = document.querySelector("#manage-tags-drawer");
+ const drawerCover = document.querySelector(".drawer-cover");
+
+ // Add handler to close drawer when dark background is clicked
+ $(drawerCover).click(function() {
+ closeDrawer(drawer, drawerCover);
+ }.bind(this));
+
+ // Add event listen to close drawer when close button is clicked from within the Iframe
+ window.addEventListener("message", function (event) {
+ if (event.data === 'closeManageTagsDrawer') {
+ closeDrawer(drawer, drawerCover)
+ }
+ }.bind(this));
+
+ $(drawerCover).css('display', 'block');
+ // xss-lint: disable=javascript-jquery-html
+ $(drawer).html(
+ ``
+ );
+ $(drawer).css('display', 'block');
+
+ // Prevent background from being scrollable when drawer is open
+ $('body').addClass('drawer-open');
+ };
+
+ return {
+ openDrawer: openDrawer,
+ closeDrawer: closeDrawer
+ };
+});
From a005b34f1577bf1d71431610a50686d25544d4cb Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Thu, 9 Nov 2023 17:47:41 -0500
Subject: [PATCH 11/18] feat: Open manage tag drawer
---
cms/djangoapps/contentstore/utils.py | 6 ++++--
.../xblock_storage_handlers/view_handlers.py | 3 +++
cms/static/js/views/pages/container.js | 10 +++++++---
cms/static/js/views/pages/container_subviews.js | 13 +++++++++++--
cms/static/js/views/utils/tagging_drawer_utils.js | 9 ++++++++-
cms/templates/container.html | 3 +++
cms/templates/js/tag-list.underscore | 2 +-
7 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index ba227ecbec0e..4d8e18228832 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -442,7 +442,7 @@ def get_taxonomy_list_url():
return taxonomy_list_url
-def get_taxonomy_tags_widget_url(course_locator) -> str:
+def get_taxonomy_tags_widget_url(course_locator=None) -> str:
"""
Gets course authoring microfrontend URL for taxonomy tags drawer widget view.
@@ -451,7 +451,9 @@ def get_taxonomy_tags_widget_url(course_locator) -> str:
taxonomy_tags_widget_url = None
# Uses the same waffle flag as taxonomy list page
if use_tagging_taxonomy_list_page():
- mfe_base_url = get_course_authoring_url(course_locator)
+ mfe_base_url = settings.COURSE_AUTHORING_MICROFRONTEND_URL
+ if course_locator:
+ mfe_base_url = get_course_authoring_url(course_locator)
if mfe_base_url:
taxonomy_tags_widget_url = f'{mfe_base_url}/tagging/components/widget/'
return taxonomy_tags_widget_url
diff --git a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
index e491c783a88c..2e7853a9e165 100644
--- a/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
+++ b/cms/djangoapps/contentstore/xblock_storage_handlers/view_handlers.py
@@ -97,6 +97,7 @@
has_children_visible_to_specific_partition_groups,
is_currently_visible_to_students,
is_self_paced,
+ get_taxonomy_tags_widget_url,
)
from .create_xblock import create_xblock
@@ -1386,6 +1387,8 @@ def create_xblock_info( # lint-amnesty, pylint: disable=too-many-statements
xblock_info["ancestor_has_staff_lock"] = False
if tags is not None:
xblock_info["tags"] = tags
+ if use_tagging_taxonomy_list_page():
+ xblock_info["taxonomy_tags_widget_url"] = get_taxonomy_tags_widget_url()
if course_outline:
if xblock_info["has_explicit_staff_lock"]:
diff --git a/cms/static/js/views/pages/container.js b/cms/static/js/views/pages/container.js
index f750e01102a8..71cbb9b26d5f 100644
--- a/cms/static/js/views/pages/container.js
+++ b/cms/static/js/views/pages/container.js
@@ -8,10 +8,11 @@ define(['jquery', 'underscore', 'backbone', 'gettext', 'js/views/pages/base_page
'js/models/xblock_info', 'js/views/xblock_string_field_editor', 'js/views/xblock_access_editor',
'js/views/pages/container_subviews', 'js/views/unit_outline', 'js/views/utils/xblock_utils',
'common/js/components/views/feedback_notification', 'common/js/components/views/feedback_prompt',
+ 'js/views/utils/tagging_drawer_utils',
],
function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView, AddXBlockComponent,
EditXBlockModal, MoveXBlockModal, XBlockInfo, XBlockStringFieldEditor, XBlockAccessEditor,
- ContainerSubviews, UnitOutlineView, XBlockUtils, NotificationView, PromptView) {
+ ContainerSubviews, UnitOutlineView, XBlockUtils, NotificationView, PromptView, TaggingDrawerUtils) {
'use strict';
var XBlockContainerPage = BasePage.extend({
@@ -418,8 +419,11 @@ function($, _, Backbone, gettext, BasePage, ViewUtils, ContainerView, XBlockView
this.duplicateComponent(this.findXBlockElement(event.target));
},
- openManageTags: function() {
- // TODO open manage tags drawer
+ openManageTags: function(event) {
+ const taxonomyTagsWidgetUrl = this.model.get('taxonomy_tags_widget_url');
+ const contentId = this.findXBlockElement(event.target).data('locator');
+
+ TaggingDrawerUtils.openDrawer(taxonomyTagsWidgetUrl, contentId);
},
showMoveXBlockModal: function(event) {
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 8af46f8fd19b..792ed592774f 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -2,8 +2,9 @@
* Subviews (usually small side panels) for XBlockContainerPage.
*/
define(['jquery', 'underscore', 'gettext', 'js/views/baseview', 'common/js/components/utils/view_utils',
- 'js/views/utils/xblock_utils', 'js/views/utils/move_xblock_utils', 'edx-ui-toolkit/js/utils/html-utils'],
-function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, HtmlUtils) {
+ 'js/views/utils/xblock_utils', 'js/views/utils/move_xblock_utils', 'edx-ui-toolkit/js/utils/html-utils',
+ 'js/views/utils/tagging_drawer_utils'],
+function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, HtmlUtils, TaggingDrawerUtils) {
'use strict';
var disabledCss = 'is-disabled';
@@ -304,6 +305,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
events: {
'click .wrapper-tag-header': 'expandTagContainer',
'click .tagging-label': 'expandContentTag',
+ 'click .manage-tag-button': 'openManageTagDrawer',
},
initialize: function() {
@@ -398,6 +400,13 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
},
+ openManageTagDrawer: function() {
+ const taxonomyTagsWidgetUrl = this.model.get('taxonomy_tags_widget_url');
+ const contentId = this.model.get('id');
+
+ TaggingDrawerUtils.openDrawer(taxonomyTagsWidgetUrl, contentId);
+ },
+
render: function() {
HtmlUtils.setHtml(
this.$el,
diff --git a/cms/static/js/views/utils/tagging_drawer_utils.js b/cms/static/js/views/utils/tagging_drawer_utils.js
index 4733a4078335..107f79f04470 100644
--- a/cms/static/js/views/utils/tagging_drawer_utils.js
+++ b/cms/static/js/views/utils/tagging_drawer_utils.js
@@ -1,5 +1,12 @@
/**
- * Provides utilities for open and close the tagging drawer to manage tags.
+ * Provides utilities to open and close the tagging drawer to manage tags.
+ *
+ * To use this drawer you need to add the following code into your template:
+ *
+ * ```
+ *
+ *
+ * ```
*/
define(['jquery'],
function($,) {
diff --git a/cms/templates/container.html b/cms/templates/container.html
index 4c702670b3b7..ee2b56860187 100644
--- a/cms/templates/container.html
+++ b/cms/templates/container.html
@@ -241,4 +241,7 @@ ${_("Location ID")}
+
+
+
%block>
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
index b44b9f4eeb3b..e38bc90ac6c6 100644
--- a/cms/templates/js/tag-list.underscore
+++ b/cms/templates/js/tag-list.underscore
@@ -25,7 +25,7 @@
<% } %>
-
+
<%- gettext("Manage tags") %>
From 6d70b80b9f354a0aeda89c05fc1c7150ce9c96ff Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Mon, 13 Nov 2023 11:15:09 -0500
Subject: [PATCH 12/18] fix: Bug with multiple children on the same parent tag
---
cms/djangoapps/contentstore/views/component.py | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index b00215be4243..0d98c336bb81 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -625,6 +625,8 @@ def get_unit_tags(usage_key):
taxonomy_dict = {}
for content_tag in content_tags:
taxonomy_id = content_tag.taxonomy_id
+ # When a taxonomy is deleted, the id here is None.
+ # In that case the tag is not shown in the UI.
if taxonomy_id:
if taxonomy_id not in taxonomy_dict:
taxonomy_dict[taxonomy_id] = []
@@ -634,8 +636,11 @@ def get_unit_tags(usage_key):
total_count = 0
def handle_tag(tags, root_ids, tag, child_tag_id=None):
- # Group each tag by parent to build a tree
- if tag.id not in tags:
+ """
+ Group each tag by parent to build a tree.
+ """
+ tag_processed_before = tag.id in tags
+ if not tag_processed_before:
tags[tag.id] = {
'id': tag.id,
'value': tag.value,
@@ -647,8 +652,11 @@ def handle_tag(tags, root_ids, tag, child_tag_id=None):
if tag.parent_id is None:
if tag.id not in root_ids:
root_ids.append(tag.id)
- else:
- # Group all the lineage of this tag
+ elif not tag_processed_before:
+ # Group all the lineage of this tag.
+ #
+ # Skip this if the tag has been processed before,
+ # we don't need to process lineage again to aboid duplicates.
handle_tag(tags, root_ids, tag.parent, tag.id)
# Build a tag tree for each taxonomy
@@ -657,6 +665,8 @@ def handle_tag(tags, root_ids, tag, child_tag_id=None):
root_ids = []
for content_tag in content_tag_list:
+ # When a tag is deleted from the taxonomy, the `tag` here is None.
+ # In that case the tag is not shown in the UI.
if content_tag.tag:
handle_tag(tags, root_ids, content_tag.tag)
From 91c03c116ced831a8c18fedcd4599cd8bee15fe6 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Mon, 13 Nov 2023 11:30:32 -0500
Subject: [PATCH 13/18] fix: caret up/down for children tags
---
cms/static/js/views/pages/container_subviews.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 792ed592774f..977077733f33 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -360,7 +360,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
// Element that contains the tag value and the arrow icon
tagContentElement.style.marginLeft = `${depth}em`;
- tagContentElement.className = 'tagging-label';
+ tagContentElement.className = `tagging-label tagging-label-tag-${tag.id}`;
// Element that contains the tag value
tagValueElement.textContent = tag.value;
From 76232ba0fce372818dabc65246d8d8fc8f207264 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Tue, 14 Nov 2023 11:29:51 -0500
Subject: [PATCH 14/18] style: Typo on comments
---
cms/djangoapps/contentstore/views/component.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cms/djangoapps/contentstore/views/component.py b/cms/djangoapps/contentstore/views/component.py
index 0d98c336bb81..3cd3ffff90e2 100644
--- a/cms/djangoapps/contentstore/views/component.py
+++ b/cms/djangoapps/contentstore/views/component.py
@@ -656,7 +656,7 @@ def handle_tag(tags, root_ids, tag, child_tag_id=None):
# Group all the lineage of this tag.
#
# Skip this if the tag has been processed before,
- # we don't need to process lineage again to aboid duplicates.
+ # we don't need to process lineage again to avoid duplicates.
handle_tag(tags, root_ids, tag.parent, tag.id)
# Build a tag tree for each taxonomy
From 7aa790d88e6b4a0fbe0e00c9550d8e5862e767b1 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Wed, 15 Nov 2023 10:31:35 -0500
Subject: [PATCH 15/18] style: Nits on UI
---
cms/djangoapps/contentstore/utils.py | 3 ++-
cms/static/js/models/xblock_info.js | 8 ++++----
cms/static/js/views/pages/container_subviews.js | 5 +++--
cms/static/sass/views/_container.scss | 17 +++++++++++++++++
cms/templates/js/tag-list.underscore | 5 ++---
5 files changed, 28 insertions(+), 10 deletions(-)
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index 4d8e18228832..cb045aad3c8d 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -7,6 +7,7 @@
from contextlib import contextmanager
from datetime import datetime, timezone
from uuid import uuid4
+from typing import Optional
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -442,7 +443,7 @@ def get_taxonomy_list_url():
return taxonomy_list_url
-def get_taxonomy_tags_widget_url(course_locator=None) -> str:
+def get_taxonomy_tags_widget_url(course_locator=None) -> Optional[str]:
"""
Gets course authoring microfrontend URL for taxonomy tags drawer widget view.
diff --git a/cms/static/js/models/xblock_info.js b/cms/static/js/models/xblock_info.js
index 5e470c986fbd..49812a6c9d78 100644
--- a/cms/static/js/models/xblock_info.js
+++ b/cms/static/js/models/xblock_info.js
@@ -173,10 +173,10 @@ define(
* True if summary configuration is enabled.
*/
summary_configuration_enabled: null,
- /**
- * List of tags of the unit. This list is managed by the content_tagging module.
- */
- tags: null,
+ /**
+ * List of tags of the unit. This list is managed by the content_tagging module.
+ */
+ tags: null,
},
initialize: function() {
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 977077733f33..1f9416465693 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -297,8 +297,8 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
});
/**
- * TagList displays the tags of a unit.
- */
+ * TagList displays the tags of a unit.
+ */
var TagList = BaseView.extend({
// takes XBlockInfo as a model
@@ -365,6 +365,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
// Element that contains the tag value
tagValueElement.textContent = tag.value;
tagValueElement.id = `tag-${tag.id}`;
+ tagValueElement.className = 'tagging-label-value';
tagContentElement.appendChild(tagValueElement);
parentElement.appendChild(tagContentElement);
diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss
index 521b8cf6d3c1..a680d9136173 100644
--- a/cms/static/sass/views/_container.scss
+++ b/cms/static/sass/views/_container.scss
@@ -280,7 +280,24 @@
padding-top: 10px;
.tagging-label {
+ display: flex;
padding: 4px 0px;
+
+ .tagging-label-value {
+ display: inline-block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .tagging-label-count {
+ display: inline-block;
+ margin: 0 0.5em;
+ }
+ }
+
+ .tagging-label:hover {
+ color: $blue;
}
.icon {
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
index e38bc90ac6c6..e7a2b001cc47 100644
--- a/cms/templates/js/tag-list.underscore
+++ b/cms/templates/js/tag-list.underscore
@@ -17,9 +17,8 @@
var taxonomy = tags.taxonomies[i];
%>
From aa12f5c459440cf919df8856a04fe4e0b85514e5 Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Wed, 15 Nov 2023 13:32:36 -0500
Subject: [PATCH 16/18] style: Nit on open taxonomy and tag
---
cms/djangoapps/contentstore/utils.py | 4 ++--
cms/static/js/views/pages/container_subviews.js | 1 +
cms/templates/js/tag-list.underscore | 2 +-
3 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py
index cb045aad3c8d..8e3f97eee98e 100644
--- a/cms/djangoapps/contentstore/utils.py
+++ b/cms/djangoapps/contentstore/utils.py
@@ -1,13 +1,13 @@
"""
Common utility functions useful throughout the contentstore
"""
+from __future__ import annotations
import configparser
import logging
from collections import defaultdict
from contextlib import contextmanager
from datetime import datetime, timezone
from uuid import uuid4
-from typing import Optional
from django.conf import settings
from django.core.exceptions import ValidationError
@@ -443,7 +443,7 @@ def get_taxonomy_list_url():
return taxonomy_list_url
-def get_taxonomy_tags_widget_url(course_locator=None) -> Optional[str]:
+def get_taxonomy_tags_widget_url(course_locator=None) -> str | None:
"""
Gets course authoring microfrontend URL for taxonomy tags drawer widget view.
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index 1f9416465693..b950dbfcb3a6 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -361,6 +361,7 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
// Element that contains the tag value and the arrow icon
tagContentElement.style.marginLeft = `${depth}em`;
tagContentElement.className = `tagging-label tagging-label-tag-${tag.id}`;
+ tagContentElement.id = `tag-${tag.id}`;
// Element that contains the tag value
tagValueElement.textContent = tag.value;
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
index e7a2b001cc47..976c74d2c2e1 100644
--- a/cms/templates/js/tag-list.underscore
+++ b/cms/templates/js/tag-list.underscore
@@ -16,7 +16,7 @@
<% for (var i = 0; i < tags.taxonomies.length; i++) {
var taxonomy = tags.taxonomies[i];
%>
-
+
<%- taxonomy.value %>
(<%- taxonomy.count %>)
From 29f3b9c6b28139deee5c08574035470ff09ed9bf Mon Sep 17 00:00:00 2001
From: XnpioChV
Date: Thu, 16 Nov 2023 13:49:10 -0500
Subject: [PATCH 17/18] feat: Add a11y support to tag list component
---
.../js/views/pages/container_subviews.js | 34 +++++++++++++++++++
cms/static/sass/views/_container.scss | 8 +++++
cms/templates/js/tag-list.underscore | 10 +++---
3 files changed, 47 insertions(+), 5 deletions(-)
diff --git a/cms/static/js/views/pages/container_subviews.js b/cms/static/js/views/pages/container_subviews.js
index b950dbfcb3a6..b4ee286ae897 100644
--- a/cms/static/js/views/pages/container_subviews.js
+++ b/cms/static/js/views/pages/container_subviews.js
@@ -306,6 +306,9 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
'click .wrapper-tag-header': 'expandTagContainer',
'click .tagging-label': 'expandContentTag',
'click .manage-tag-button': 'openManageTagDrawer',
+ 'keydown .wrapper-tag-header': 'handleKeyDownOnHeader',
+ 'keydown .tagging-label': 'handleKeyDownOnContentTag',
+ 'keydown .manage-tag-button': 'handleKeyDownOnTagDrawer',
},
initialize: function() {
@@ -320,34 +323,61 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
}
},
+ handleKeyDownOnHeader: function(event) {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ this.expandTagContainer();
+ }
+ },
+
+ handleKeyDownOnContentTag: function(event) {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ this.expandContentTag(event);
+ }
+ },
+
+ handleKeyDownOnTagDrawer: function(event) {
+ if (event.key === 'Enter' || event.key === ' ') {
+ event.preventDefault();
+ this.openManageTagDrawer();
+ }
+ },
+
expandTagContainer: function() {
var $content = this.$('.wrapper-tags .wrapper-tag-content'),
+ $header = this.$('.wrapper-tags .wrapper-tag-header'),
$icon = this.$('.wrapper-tags .wrapper-tag-header .icon');
if ($content.hasClass('is-hidden')) {
$content.removeClass('is-hidden');
$icon.addClass('fa-caret-up');
$icon.removeClass('fa-caret-down');
+ $header.attr('aria-expanded', 'true');
} else {
$content.addClass('is-hidden');
$icon.removeClass('fa-caret-up');
$icon.addClass('fa-caret-down');
+ $header.attr('aria-expanded', 'false');
}
},
expandContentTag: function(event) {
var contentId = event.target.id,
$content = this.$(`.wrapper-tags .content-tags-${contentId}`),
+ $header = this.$(`.wrapper-tags .tagging-label-${contentId}`),
$icon = this.$(`.wrapper-tags .tagging-label-${contentId} .icon`);
if ($content.hasClass('is-hidden')) {
$content.removeClass('is-hidden');
$icon.addClass('fa-caret-up');
$icon.removeClass('fa-caret-down');
+ $header.attr('aria-expanded', 'true');
} else {
$content.addClass('is-hidden');
$icon.removeClass('fa-caret-up');
$icon.addClass('fa-caret-down');
+ $header.attr('aria-expanded', 'false');
}
},
@@ -383,6 +413,10 @@ function($, _, gettext, BaseView, ViewUtils, XBlockViewUtils, MoveXBlockUtils, H
// Element that contains the children of this tag
tagChildrenElement.className = `content-tags-tag-${tag.id} is-hidden`;
+ tagContentElement.tabIndex = 0;
+ tagContentElement.role = "button";
+ tagContentElement.ariaExpanded = "false";
+ tagContentElement.setAttribute('aria-controls', `content-tags-tag-${tag.id}`);
tagContentElement.appendChild(tagIconElement);
parentElement.appendChild(tagChildrenElement);
diff --git a/cms/static/sass/views/_container.scss b/cms/static/sass/views/_container.scss
index a680d9136173..1f44ad05b3e2 100644
--- a/cms/static/sass/views/_container.scss
+++ b/cms/static/sass/views/_container.scss
@@ -264,6 +264,10 @@
}
}
+ .wrapper-tag-header:focus {
+ border: 1px dotted gray;
+ }
+
.action-primary {
@extend %btn-primary-inverse;
@@ -300,6 +304,10 @@
color: $blue;
}
+ .tagging-label:focus {
+ color: $blue;
+ }
+
.icon {
margin-left: 5px;
}
diff --git a/cms/templates/js/tag-list.underscore b/cms/templates/js/tag-list.underscore
index 976c74d2c2e1..a006eb111b5b 100644
--- a/cms/templates/js/tag-list.underscore
+++ b/cms/templates/js/tag-list.underscore
@@ -1,6 +1,6 @@
<% if (tags !== null) { %>
-