diff --git a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py index 31cf9c36f0..5214b8efd4 100644 --- a/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py +++ b/cms/djangoapps/contentstore/rest_api/v1/serializers/course_waffle_flags.py @@ -148,10 +148,15 @@ class CourseWaffleFlagsSerializer(serializers.Serializer): def get_use_new_textbooks_page(self, obj): """ - Method to get the use_new_textbooks_page switch + Method to indicate whether we should use_new_textbooks_page or not. + + This used to be based on a waffle flag but the flag is being removed so we + default it to true for now until we can remove the need for it from the consumers + of this serializer and the related APIs. + + See https://github.com/openedx/edx-platform/issues/37497 """ - course_key = self.get_course_key() - return toggles.use_new_textbooks_page(course_key) + return True def get_use_new_group_configurations_page(self, obj): """ diff --git a/cms/djangoapps/contentstore/tests/test_contentstore.py b/cms/djangoapps/contentstore/tests/test_contentstore.py index a8721d629c..0c213f2d4f 100644 --- a/cms/djangoapps/contentstore/tests/test_contentstore.py +++ b/cms/djangoapps/contentstore/tests/test_contentstore.py @@ -1467,6 +1467,15 @@ class ContentStoreTest(ContentStoreTestCase): ) self.assertEqual(resp.status_code, 200) + def test_get_json(handler): + # Helper function for getting HTML for a page in Studio and + # checking that it does not error. + resp = self.client.get( + get_url(handler, course_key, 'course_key_string'), + HTTP_ACCEPT="application/json", + ) + self.assertEqual(resp.status_code, 200) + course_items = import_course_from_xml( self.store, self.user.id, TEST_DATA_DIR, ['simple'], create_if_not_present=True ) @@ -1499,8 +1508,7 @@ class ContentStoreTest(ContentStoreTestCase): test_get_html('grading_handler') with override_waffle_flag(toggles.LEGACY_STUDIO_ADVANCED_SETTINGS, True): test_get_html('advanced_settings_handler') - with override_waffle_flag(toggles.LEGACY_STUDIO_TEXTBOOKS, True): - test_get_html('textbooks_list_handler') + test_get_json('textbooks_list_handler') # go look at the Edit page unit_key = course_key.make_usage_key('vertical', 'test_vertical') diff --git a/cms/djangoapps/contentstore/tests/test_course_settings.py b/cms/djangoapps/contentstore/tests/test_course_settings.py index 9afba26b32..bf8007d41b 100644 --- a/cms/djangoapps/contentstore/tests/test_course_settings.py +++ b/cms/djangoapps/contentstore/tests/test_course_settings.py @@ -171,7 +171,6 @@ class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin): @override_waffle_flag(toggles.LEGACY_STUDIO_CUSTOM_PAGES, True) @override_waffle_flag(toggles.LEGACY_STUDIO_SCHEDULE_DETAILS, True) @override_waffle_flag(toggles.LEGACY_STUDIO_GRADING, True) - @override_waffle_flag(toggles.LEGACY_STUDIO_TEXTBOOKS, True) def test_disable_advanced_settings_feature(self, disable_advanced_settings): """ If this feature is enabled, only Django Staff/Superuser should be able to access the "Advanced Settings" page. @@ -190,7 +189,6 @@ class CourseAdvanceSettingViewTest(CourseTestCase, MilestonesTestCaseMixin): 'tabs_handler', 'settings_handler', 'grading_handler', - 'textbooks_list_handler', ): # Test that non-staff users don't see the "Advanced Settings" tab link. response = self.non_staff_client.get_html( diff --git a/cms/djangoapps/contentstore/toggles.py b/cms/djangoapps/contentstore/toggles.py index 96a646bff2..0d0101b6c0 100644 --- a/cms/djangoapps/contentstore/toggles.py +++ b/cms/djangoapps/contentstore/toggles.py @@ -402,25 +402,6 @@ def use_new_certificates_page(course_key): return not LEGACY_STUDIO_CERTIFICATES.is_enabled(course_key) -# .. toggle_name: legacy_studio.textbooks -# .. toggle_implementation: WaffleFlag -# .. toggle_default: False -# .. toggle_description: Temporarily fall back to the old Studio Textbooks page. -# .. toggle_use_cases: temporary -# .. toggle_creation_date: 2025-03-14 -# .. toggle_target_removal_date: 2025-09-14 -# .. toggle_tickets: https://github.com/openedx/edx-platform/issues/36275 -# .. toggle_warning: In Ulmo, this toggle will be removed. Only the new (React-based) experience will be available. -LEGACY_STUDIO_TEXTBOOKS = CourseWaffleFlag('legacy_studio.textbooks', __name__) - - -def use_new_textbooks_page(course_key): - """ - Returns a boolean if new studio textbooks mfe is enabled - """ - return not LEGACY_STUDIO_TEXTBOOKS.is_enabled(course_key) - - # .. toggle_name: legacy_studio.configurations # .. toggle_implementation: WaffleFlag # .. toggle_default: False diff --git a/cms/djangoapps/contentstore/utils.py b/cms/djangoapps/contentstore/utils.py index 3b263570f6..8a89bba637 100644 --- a/cms/djangoapps/contentstore/utils.py +++ b/cms/djangoapps/contentstore/utils.py @@ -50,7 +50,6 @@ from cms.djangoapps.contentstore.toggles import ( use_new_group_configurations_page, use_new_import_page, use_new_schedule_details_page, - use_new_textbooks_page, use_new_unit_page, use_new_updates_page, use_new_video_uploads_page, @@ -492,11 +491,10 @@ def get_textbooks_url(course_locator) -> str: Gets course authoring microfrontend URL for textbooks page view. """ textbooks_url = None - if use_new_textbooks_page(course_locator): - mfe_base_url = get_course_authoring_url(course_locator) - course_mfe_url = f'{mfe_base_url}/course/{course_locator}/textbooks' - if mfe_base_url: - textbooks_url = course_mfe_url + mfe_base_url = get_course_authoring_url(course_locator) + course_mfe_url = f'{mfe_base_url}/course/{course_locator}/textbooks' + if mfe_base_url: + textbooks_url = course_mfe_url return textbooks_url diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 453e30e0aa..f585c4b4af 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -93,7 +93,6 @@ from ..toggles import ( use_new_updates_page, use_new_advanced_settings_page, use_new_grading_page, - use_new_textbooks_page, use_new_group_configurations_page, use_new_schedule_details_page ) @@ -112,7 +111,6 @@ from ..utils import ( get_schedule_details_url, get_studio_home_url, get_updates_url, - get_textbooks_context, get_textbooks_url, initialize_permissions, remove_all_instructors, @@ -1457,17 +1455,18 @@ def textbooks_list_handler(request, course_key_string): json: overwrite all textbooks in the course with the given list """ course_key = CourseKey.from_string(course_key_string) + if "application/json" not in request.META.get('HTTP_ACCEPT', 'text/html'): + # return HTML page + # We don't need to do an access check here because + # that is done when the endpoint for the actual content of the page. + # This is just to handle redirecting anyone that has bookmarked the old + # textbooks page. + return redirect(get_textbooks_url(course_key)) + store = modulestore() with store.bulk_operations(course_key): course = get_course_and_check_access(course_key, request.user) - if "application/json" not in request.META.get('HTTP_ACCEPT', 'text/html'): - # return HTML page - if use_new_textbooks_page(course_key): - return redirect(get_textbooks_url(course_key)) - textbooks_context = get_textbooks_context(course) - return render_to_response('textbooks.html', textbooks_context) - # from here on down, we know the client has requested JSON if request.method == 'GET': return JsonResponse(course.pdf_textbooks) diff --git a/cms/djangoapps/contentstore/views/tests/test_textbooks.py b/cms/djangoapps/contentstore/views/tests/test_textbooks.py index e80c5d8379..0756d236ed 100644 --- a/cms/djangoapps/contentstore/views/tests/test_textbooks.py +++ b/cms/djangoapps/contentstore/views/tests/test_textbooks.py @@ -4,9 +4,7 @@ import json from unittest import TestCase -from edx_toggles.toggles.testutils import override_waffle_flag -from cms.djangoapps.contentstore import toggles from cms.djangoapps.contentstore.tests.utils import CourseTestCase from cms.djangoapps.contentstore.utils import reverse_course_url @@ -20,15 +18,10 @@ class TextbookIndexTestCase(CourseTestCase): super().setUp() self.url = reverse_course_url('textbooks_list_handler', self.course.id) - @override_waffle_flag(toggles.LEGACY_STUDIO_TEXTBOOKS, True) def test_view_index(self): "Basic check that the textbook index page responds correctly" resp = self.client.get(self.url) - self.assertEqual(resp.status_code, 200) - # we don't have resp.context right now, - # due to bugs in our testing harness :( - if resp.context and resp.context.get('course'): - self.assertEqual(resp.context['course'], self.course) + self.assertEqual(resp.status_code, 302) def test_view_index_xhr(self): "Check that we get a JSON response when requested via AJAX" diff --git a/cms/static/cms/js/spec/main.js b/cms/static/cms/js/spec/main.js index 4e093ee573..3fb9ca8319 100644 --- a/cms/static/cms/js/spec/main.js +++ b/cms/static/cms/js/spec/main.js @@ -234,11 +234,9 @@ 'js/spec/models/section_spec', 'js/spec/models/settings_course_grader_spec', 'js/spec/models/settings_grading_spec', - 'js/spec/models/textbook_spec', 'js/spec/models/upload_spec', 'js/spec/views/course_info_spec', 'js/spec/views/metadata_edit_spec', - 'js/spec/views/textbook_spec', 'js/spec/views/upload_spec', 'js/spec/video/transcripts/message_manager_spec', 'js/spec/video/transcripts/utils_spec', diff --git a/cms/static/js/collections/chapter.js b/cms/static/js/collections/chapter.js deleted file mode 100644 index 2af2a85bd4..0000000000 --- a/cms/static/js/collections/chapter.js +++ /dev/null @@ -1,14 +0,0 @@ -define(['backbone', 'js/models/chapter'], function(Backbone, ChapterModel) { - var ChapterCollection = Backbone.Collection.extend({ - model: ChapterModel, - comparator: 'order', - nextOrder: function() { - if (!this.length) { return 1; } - return this.last().get('order') + 1; - }, - isEmpty: function() { - return this.length === 0 || this.every(function(m) { return m.isEmpty(); }); - } - }); - return ChapterCollection; -}); diff --git a/cms/static/js/collections/textbook.js b/cms/static/js/collections/textbook.js deleted file mode 100644 index d388ca9800..0000000000 --- a/cms/static/js/collections/textbook.js +++ /dev/null @@ -1,8 +0,0 @@ -define(['backbone', 'js/models/textbook'], - function(Backbone, TextbookModel) { - var TextbookCollection = Backbone.Collection.extend({ - model: TextbookModel, - url: function() { return CMS.URL.TEXTBOOKS; } - }); - return TextbookCollection; - }); diff --git a/cms/static/js/factories/textbooks.js b/cms/static/js/factories/textbooks.js deleted file mode 100644 index c3ba710ebf..0000000000 --- a/cms/static/js/factories/textbooks.js +++ /dev/null @@ -1,25 +0,0 @@ -import * as gettext from 'gettext'; -import * as Section from 'js/models/section'; -import * as TextbookCollection from 'js/collections/textbook'; -import * as ListTextbooksView from 'js/views/list_textbooks'; -import './base'; - -// eslint-disable-next-line no-unused-expressions -'use strict'; -export default function TextbooksFactory(textbooksJson) { - var textbooks = new TextbookCollection(textbooksJson, {parse: true}), - tbView = new ListTextbooksView({collection: textbooks}); - - $('.content-primary').append(tbView.render().el); - $('.nav-actions .new-button').click(function(event) { - tbView.addOne(event); - }); - $(window).on('beforeunload', function() { - var dirty = textbooks.find(function(textbook) { return textbook.isDirty(); }); - if (dirty) { - return gettext('You have unsaved changes. Do you really want to leave this page?'); - } - }); -} - -export {TextbooksFactory}; diff --git a/cms/static/js/models/chapter.js b/cms/static/js/models/chapter.js deleted file mode 100644 index e241217c38..0000000000 --- a/cms/static/js/models/chapter.js +++ /dev/null @@ -1,52 +0,0 @@ -define(['backbone', 'gettext', 'backbone.associations'], function(Backbone, gettext) { - var Chapter = Backbone.AssociatedModel.extend({ - defaults: function() { - return { - name: '', - asset_path: '', - order: this.collection ? this.collection.nextOrder() : 1 - }; - }, - isEmpty: function() { - return !this.get('name') && !this.get('asset_path'); - }, - parse: function(response) { - if ('title' in response && !('name' in response)) { - response.name = response.title; - delete response.title; - } - if ('url' in response && !('asset_path' in response)) { - response.asset_path = response.url; - delete response.url; - } - return response; - }, - toJSON: function() { - return { - title: this.get('name'), - url: this.get('asset_path') - }; - }, - // NOTE: validation functions should return non-internationalized error - // messages. The messages will be passed through gettext in the template. - validate: function(attrs, options) { - if (!attrs.name && !attrs.asset_path) { - return { - message: gettext('Chapter name and asset_path are both required'), - attributes: {name: true, asset_path: true} - }; - } else if (!attrs.name) { - return { - message: gettext('Chapter name is required'), - attributes: {name: true} - }; - } else if (!attrs.asset_path) { - return { - message: gettext('asset_path is required'), - attributes: {asset_path: true} - }; - } - } - }); - return Chapter; -}); diff --git a/cms/static/js/models/textbook.js b/cms/static/js/models/textbook.js deleted file mode 100644 index cc14824048..0000000000 --- a/cms/static/js/models/textbook.js +++ /dev/null @@ -1,89 +0,0 @@ -define(['backbone', 'underscore', 'gettext', 'js/models/chapter', 'js/collections/chapter', - 'backbone.associations', 'cms/js/main'], -function(Backbone, _, gettext, ChapterModel, ChapterCollection) { - var Textbook = Backbone.AssociatedModel.extend({ - defaults: function() { - return { - name: '', - chapters: new ChapterCollection([{}]), - showChapters: false, - editing: false - }; - }, - relations: [{ - type: Backbone.Many, - key: 'chapters', - relatedModel: ChapterModel, - collectionType: ChapterCollection - }], - initialize: function() { - this.setOriginalAttributes(); - return this; - }, - setOriginalAttributes: function() { - this._originalAttributes = this.parse(this.toJSON()); - }, - reset: function() { - this.set(this._originalAttributes, {parse: true}); - }, - isDirty: function() { - return !_.isEqual(this._originalAttributes, this.parse(this.toJSON())); - }, - isEmpty: function() { - return !this.get('name') && this.get('chapters').isEmpty(); - }, - urlRoot: function() { return CMS.URL.TEXTBOOKS; }, - parse: function(response) { - var ret = $.extend(true, {}, response); - if ('tab_title' in ret && !('name' in ret)) { - ret.name = ret.tab_title; - delete ret.tab_title; - } - if ('url' in ret && !('chapters' in ret)) { - ret.chapters = {url: ret.url}; - delete ret.url; - } - _.each(ret.chapters, function(chapter, i) { - chapter.order = chapter.order || i + 1; - }); - return ret; - }, - toJSON: function() { - return { - tab_title: this.get('name'), - chapters: this.get('chapters').toJSON() - }; - }, - // NOTE: validation functions should return non-internationalized error - // messages. The messages will be passed through gettext in the template. - validate: function(attrs, options) { - if (!attrs.name) { - return { - message: gettext('Textbook name is required'), - attributes: {name: true} - }; - } - if (attrs.chapters.length === 0) { - return { - message: gettext('Please add at least one chapter'), - attributes: {chapters: true} - }; - } else { - // validate all chapters - var invalidChapters = []; - attrs.chapters.each(function(chapter) { - if (!chapter.isValid()) { - invalidChapters.push(chapter); - } - }); - if (!_.isEmpty(invalidChapters)) { - return { - message: gettext('All chapters must have a name and asset'), - attributes: {chapters: invalidChapters} - }; - } - } - } - }); - return Textbook; -}); diff --git a/cms/static/js/spec/models/textbook_spec.js b/cms/static/js/spec/models/textbook_spec.js deleted file mode 100644 index 9be93eb624..0000000000 --- a/cms/static/js/spec/models/textbook_spec.js +++ /dev/null @@ -1,240 +0,0 @@ -define(["backbone", "js/models/textbook", "js/collections/textbook", "js/models/chapter", "js/collections/chapter", "cms/js/main"], -function(Backbone, Textbook, TextbookSet, Chapter, ChapterSet, main) { - - describe("Textbook model", function() { - beforeEach(function() { - main(); - this.model = new Textbook(); - CMS.URL.TEXTBOOKS = "/textbooks"; - }); - - afterEach(() => delete CMS.URL.TEXTBOOKS); - - describe("Basic", function() { - it("should have an empty name by default", function() { - expect(this.model.get("name")).toEqual(""); - }); - - it("should not show chapters by default", function() { - expect(this.model.get("showChapters")).toBeFalsy(); - }); - - it("should have a ChapterSet with one chapter by default", function() { - const chapters = this.model.get("chapters"); - expect(chapters).toBeInstanceOf(ChapterSet); - expect(chapters.length).toEqual(1); - expect(chapters.at(0).isEmpty()).toBeTruthy(); - }); - - it("should be empty by default", function() { - expect(this.model.isEmpty()).toBeTruthy(); - }); - - it("should have a URL root", function() { - const urlRoot = _.result(this.model, 'urlRoot'); - expect(urlRoot).toBeTruthy(); - }); - - it("should be able to reset itself", function() { - this.model.set("name", "foobar"); - this.model.reset(); - expect(this.model.get("name")).toEqual(""); - }); - - it("should not be dirty by default", function() { - expect(this.model.isDirty()).toBeFalsy(); - }); - - it("should be dirty after it's been changed", function() { - this.model.set("name", "foobar"); - expect(this.model.isDirty()).toBeTruthy(); - }); - - it("should not be dirty after calling setOriginalAttributes", function() { - this.model.set("name", "foobar"); - this.model.setOriginalAttributes(); - expect(this.model.isDirty()).toBeFalsy(); - }); - }); - - describe("Input/Output", function() { - var deepAttributes = function(obj) { - if (obj instanceof Backbone.Model) { - return deepAttributes(obj.attributes); - } else if (obj instanceof Backbone.Collection) { - return obj.map(deepAttributes); - } else if (_.isArray(obj)) { - return _.map(obj, deepAttributes); - } else if (_.isObject(obj)) { - const attributes = {}; - for (let prop of Object.keys(obj)) { - const val = obj[prop]; - attributes[prop] = deepAttributes(val); - } - return attributes; - } else { - return obj; - } - }; - - it("should match server model to client model", function() { - const serverModelSpec = { - "tab_title": "My Textbook", - "chapters": [ - {"title": "Chapter 1", "url": "/ch1.pdf"}, - {"title": "Chapter 2", "url": "/ch2.pdf"}, - ] - }; - const clientModelSpec = { - "name": "My Textbook", - "showChapters": false, - "editing": false, - "chapters": [{ - "name": "Chapter 1", - "asset_path": "/ch1.pdf", - "order": 1 - }, { - "name": "Chapter 2", - "asset_path": "/ch2.pdf", - "order": 2 - } - ] - }; - - const model = new Textbook(serverModelSpec, {parse: true}); - expect(deepAttributes(model)).toEqual(clientModelSpec); - expect(model.toJSON()).toEqual(serverModelSpec); - }); - }); - - describe("Validation", function() { - it("requires a name", function() { - const model = new Textbook({name: ""}); - expect(model.isValid()).toBeFalsy(); - }); - - it("requires at least one chapter", function() { - const model = new Textbook({name: "foo"}); - model.get("chapters").reset(); - expect(model.isValid()).toBeFalsy(); - }); - - it("requires a valid chapter", function() { - const chapter = new Chapter(); - chapter.isValid = () => false; - const model = new Textbook({name: "foo"}); - model.get("chapters").reset([chapter]); - expect(model.isValid()).toBeFalsy(); - }); - - it("requires all chapters to be valid", function() { - const chapter1 = new Chapter(); - chapter1.isValid = () => true; - const chapter2 = new Chapter(); - chapter2.isValid = () => false; - const model = new Textbook({name: "foo"}); - model.get("chapters").reset([chapter1, chapter2]); - expect(model.isValid()).toBeFalsy(); - }); - - it("can pass validation", function() { - const chapter = new Chapter(); - chapter.isValid = () => true; - const model = new Textbook({name: "foo"}); - model.get("chapters").reset([chapter]); - expect(model.isValid()).toBeTruthy(); - }); - }); - }); - - - describe("Textbook collection", function() { - beforeEach(function() { - CMS.URL.TEXTBOOKS = "/textbooks"; - this.collection = new TextbookSet(); - }); - - afterEach(() => delete CMS.URL.TEXTBOOKS); - - it("should have a url set", function() { - const url = _.result(this.collection, 'url'); - expect(url).toEqual("/textbooks"); - }); - }); - - - describe("Chapter model", function() { - beforeEach(function() { - this.model = new Chapter(); - }); - - describe("Basic", function() { - it("should have a name by default", function() { - expect(this.model.get("name")).toEqual(""); - }); - - it("should have an asset_path by default", function() { - expect(this.model.get("asset_path")).toEqual(""); - }); - - it("should have an order by default", function() { - expect(this.model.get("order")).toEqual(1); - }); - - it("should be empty by default", function() { - expect(this.model.isEmpty()).toBeTruthy(); - }); - }); - - describe("Validation", function() { - it("requires a name", function() { - const model = new Chapter({name: "", asset_path: "a.pdf"}); - expect(model.isValid()).toBeFalsy(); - }); - - it("requires an asset_path", function() { - const model = new Chapter({name: "a", asset_path: ""}); - expect(model.isValid()).toBeFalsy(); - }); - - it("can pass validation", function() { - const model = new Chapter({name: "a", asset_path: "a.pdf"}); - expect(model.isValid()).toBeTruthy(); - }); - }); - }); - - - describe("Chapter collection", function() { - beforeEach(function() { - this.collection = new ChapterSet(); - }); - - it("is empty by default", function() { - expect(this.collection.isEmpty()).toBeTruthy(); - }); - - it("is empty if all chapters are empty", function() { - this.collection.add([{}, {}, {}]); - expect(this.collection.isEmpty()).toBeTruthy(); - }); - - it("is not empty if a chapter is not empty", function() { - this.collection.add([{}, {name: "full"}, {}]); - expect(this.collection.isEmpty()).toBeFalsy(); - }); - - it("should have a nextOrder function", function() { - expect(this.collection.nextOrder()).toEqual(1); - this.collection.add([{}]); - expect(this.collection.nextOrder()).toEqual(2); - this.collection.add([{}]); - expect(this.collection.nextOrder()).toEqual(3); - // verify that it doesn't just return an incrementing value each time - expect(this.collection.nextOrder()).toEqual(3); - // try going back one - this.collection.remove(this.collection.last()); - expect(this.collection.nextOrder()).toEqual(2); - }); - }); -}); diff --git a/cms/static/js/spec/views/textbook_spec.js b/cms/static/js/spec/views/textbook_spec.js deleted file mode 100644 index 1a0332f90f..0000000000 --- a/cms/static/js/spec/views/textbook_spec.js +++ /dev/null @@ -1,401 +0,0 @@ -define(["js/models/textbook", "js/models/chapter", "js/collections/chapter", "js/models/course", - "js/collections/textbook", "js/views/show_textbook", "js/views/edit_textbook", "js/views/list_textbooks", - "js/views/edit_chapter", "common/js/components/views/feedback_prompt", - "common/js/components/views/feedback_notification", "common/js/components/utils/view_utils", - "edx-ui-toolkit/js/utils/spec-helpers/ajax-helpers", - "js/spec_helpers/modal_helpers"], - function(Textbook, Chapter, ChapterSet, Course, TextbookSet, ShowTextbook, EditTextbook, ListTextbooks, EditChapter, - Prompt, Notification, ViewUtils, AjaxHelpers, modal_helpers) { - - describe("ShowTextbook", function() { - const tpl = readFixtures('show-textbook.underscore'); - - beforeEach(function() { - setFixtures($(" -% endfor - - -<%block name="jsextra"> - - - -<%block name="page_bundle"> - <%static:webpack entry="js/factories/textbooks"> - TextbooksFactory(${textbooks | n, dump_js_escaped_json}); - - - -<%block name="content"> -
- % if context_course: - <% - pages_and_resources_mfe_url = get_pages_and_resources_url(context_course.id) - pages_and_resources_mfe_enabled = bool(pages_and_resources_mfe_url) - %> - % endif - - % if pages_and_resources_mfe_enabled: -
-
-
-
    -
  1. - ${_("Content")} - › -
  2. -
  3. - ${_("Pages & Resources")} - › -
  4. -
-
-
-

- > ${_("Textbooks")} -

- % else: -
-

- ${_("Content")} - > ${_("Textbooks")} -

- % endif - -
-

${_("Page Actions")}

-
    -
  • - ${_("New Textbook")} -
  • -
-
-
-
- -
-
-
-
-
-

${_("Why should I break my textbook into chapters?")}

-

${_("Breaking your textbook into multiple chapters reduces loading times for students, especially those with slow Internet connections. Breaking up textbooks into chapters can also help students more easily find topic-based information.")}

-
-
-

${_("What if my book isn't divided into chapters?")}

-

${_("If your textbook doesn't have individual chapters, you can upload the entire text as a single chapter and enter a name of your choice in the Chapter Name field.")}

-
- -
- ${_("Learn more about textbooks")} -
-
-
-
- diff --git a/webpack-config/file-lists.js b/webpack-config/file-lists.js index d9e818f912..1ca653ba3e 100644 --- a/webpack-config/file-lists.js +++ b/webpack-config/file-lists.js @@ -27,7 +27,6 @@ module.exports = { path.resolve(__dirname, '../cms/static/js/views/active_video_upload_list.js'), path.resolve(__dirname, '../cms/static/js/views/assets.js'), path.resolve(__dirname, '../cms/static/js/views/course_video_settings.js'), - path.resolve(__dirname, '../cms/static/js/views/edit_chapter.js'), path.resolve(__dirname, '../cms/static/js/views/experiment_group_edit.js'), path.resolve(__dirname, '../cms/static/js/views/license.js'), path.resolve(__dirname, '../cms/static/js/views/modals/move_xblock_modal.js'), diff --git a/webpack.common.config.js b/webpack.common.config.js index ac283c3d40..e52a79a1bf 100644 --- a/webpack.common.config.js +++ b/webpack.common.config.js @@ -100,7 +100,6 @@ module.exports = Merge.merge({ // Studio Import: './cms/static/js/features/import/factories/import.js', CourseOrLibraryListing: './cms/static/js/features_jsx/studio/CourseOrLibraryListing.jsx', - 'js/factories/textbooks': './cms/static/js/factories/textbooks.js', 'js/factories/container': './cms/static/js/factories/container.js', 'js/factories/context_course': './cms/static/js/factories/context_course.js', 'js/factories/library': './cms/static/js/factories/library.js',