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) { %> +
+
+ + + <%- gettext("Tags") %> + + + <%- tags.count %> + + + +
+ +
+<% } %> 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]; %> -
+ - + <% } %>
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: + + % endif % if can_move: