diff --git a/RELEASE.rst b/RELEASE.rst index 1560a059c4..61fc8076ce 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,15 @@ Release Notes ============= +Version 0.51.1 +-------------- + +- Product Page Updates (#2839) +- feat: add course detail in resource card (#2834) +- memory optimizations for embedding process (#2841) +- fix: add delete button for image and media node (#2836) +- fix: editor component migration into main app (#2833) + Version 0.51.0 (Released January 08, 2026) -------------- diff --git a/frontends/api/src/generated/v1/api.ts b/frontends/api/src/generated/v1/api.ts index 774ba47f90..a3400684c0 100644 --- a/frontends/api/src/generated/v1/api.ts +++ b/frontends/api/src/generated/v1/api.ts @@ -11144,6 +11144,7 @@ export const CoursesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {CoursesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -11165,6 +11166,7 @@ export const CoursesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: CoursesListSortbyEnum, topic?: Array, @@ -11242,6 +11244,10 @@ export const CoursesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -11430,6 +11436,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {CoursesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -11451,6 +11458,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: CoursesListSortbyEnum, topic?: Array, @@ -11476,6 +11484,7 @@ export const CoursesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -11606,6 +11615,7 @@ export const CoursesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -11826,6 +11836,13 @@ export interface CoursesApiCoursesListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof CoursesApiCoursesList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -11946,6 +11963,7 @@ export class CoursesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -12520,6 +12538,7 @@ export const FeaturedApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {FeaturedListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -12541,6 +12560,7 @@ export const FeaturedApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: FeaturedListSortbyEnum, topic?: Array, @@ -12618,6 +12638,10 @@ export const FeaturedApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -12717,6 +12741,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {FeaturedListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -12738,6 +12763,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: FeaturedListSortbyEnum, topic?: Array, @@ -12763,6 +12789,7 @@ export const FeaturedApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -12849,6 +12876,7 @@ export const FeaturedApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -12978,6 +13006,13 @@ export interface FeaturedApiFeaturedListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof FeaturedApiFeaturedList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -13049,6 +13084,7 @@ export class FeaturedApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -13268,6 +13304,7 @@ export const LearningResourceDisplayInfoApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourceDisplayInfoListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -13289,6 +13326,7 @@ export const LearningResourceDisplayInfoApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourceDisplayInfoListSortbyEnum, topic?: Array, @@ -13366,6 +13404,10 @@ export const LearningResourceDisplayInfoApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -13469,6 +13511,7 @@ export const LearningResourceDisplayInfoApiFp = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourceDisplayInfoListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -13490,6 +13533,7 @@ export const LearningResourceDisplayInfoApiFp = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourceDisplayInfoListSortbyEnum, topic?: Array, @@ -13516,6 +13560,7 @@ export const LearningResourceDisplayInfoApiFp = function ( professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -13609,6 +13654,7 @@ export const LearningResourceDisplayInfoApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -13738,6 +13784,13 @@ export interface LearningResourceDisplayInfoApiLearningResourceDisplayInfoListRe */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof LearningResourceDisplayInfoApiLearningResourceDisplayInfoList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -13809,6 +13862,7 @@ export class LearningResourceDisplayInfoApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -14011,143 +14065,6 @@ export const LearningResourcesApiAxiosParamCreator = function ( configuration?: Configuration, ) { return { - /** - * Fetch multiple learning resources in a single request. - * @summary Retrieve multiple learning resources by IDs - * @param {string} ids Comma-separated list of IDs (e.g. 1,2,3) - * @param {boolean} [certification] - * @param {Array} [certification_type] The type of certification offered * `micromasters` - MicroMasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate - * @param {Array} [course_feature] Content feature for the resources. Load the \'api/v1/course_features\' endpoint for a list of course features - * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline - * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies - * @param {boolean} [free] The course/program is offered for free - * @param {Array} [level] The academic level of the resources * `undergraduate` - Undergraduate * `graduate` - Graduate * `high_school` - High School * `noncredit` - Non-Credit * `advanced` - Advanced * `intermediate` - Intermediate * `introductory` - Introductory - * @param {Array} [offered_by] The organization that offers a learning resource * `mitx` - MITx * `ocw` - MIT OpenCourseWare * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `climate` - MIT Climate - * @param {Array} [platform] The platform on which learning resources are offered * `edx` - edX * `ocw` - MIT OpenCourseWare * `oll` - Open Learning Library * `mitxonline` - MITx Online * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `csail` - CSAIL * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `scc` - Schwarzman College of Computing * `ctl` - Center for Transportation & Logistics * `whu` - WHU * `susskind` - Susskind * `globalalumni` - Global Alumni * `simplilearn` - Simplilearn * `emeritus` - Emeritus * `podcast` - Podcast * `youtube` - YouTube * `canvas` - Canvas * `climate` - MIT Climate - * @param {boolean} [professional] - * @param {Array} [readable_id] A unique text identifier for the resources - * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material - * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article - * @param {LearningResourcesBulkListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending - * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - learningResourcesBulkList: async ( - ids: string, - certification?: boolean, - certification_type?: Array, - course_feature?: Array, - delivery?: Array>, - department?: Array, - free?: boolean, - level?: Array, - offered_by?: Array, - platform?: Array, - professional?: boolean, - readable_id?: Array, - resource_category?: Array, - resource_type?: Array, - sortby?: LearningResourcesBulkListSortbyEnum, - topic?: Array, - options: RawAxiosRequestConfig = {}, - ): Promise => { - // verify required parameter 'ids' is not null or undefined - assertParamExists("learningResourcesBulkList", "ids", ids) - const localVarPath = `/api/v1/learning_resources/bulk/` - // use dummy base URL string because the URL constructor only accepts absolute URLs. - const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL) - let baseOptions - if (configuration) { - baseOptions = configuration.baseOptions - } - - const localVarRequestOptions = { - method: "GET", - ...baseOptions, - ...options, - } - const localVarHeaderParameter = {} as any - const localVarQueryParameter = {} as any - - if (certification !== undefined) { - localVarQueryParameter["certification"] = certification - } - - if (certification_type) { - localVarQueryParameter["certification_type"] = certification_type - } - - if (course_feature) { - localVarQueryParameter["course_feature"] = course_feature - } - - if (delivery) { - localVarQueryParameter["delivery"] = delivery - } - - if (department) { - localVarQueryParameter["department"] = department - } - - if (free !== undefined) { - localVarQueryParameter["free"] = free - } - - if (ids !== undefined) { - localVarQueryParameter["ids"] = ids - } - - if (level) { - localVarQueryParameter["level"] = level - } - - if (offered_by) { - localVarQueryParameter["offered_by"] = offered_by - } - - if (platform) { - localVarQueryParameter["platform"] = platform - } - - if (professional !== undefined) { - localVarQueryParameter["professional"] = professional - } - - if (readable_id) { - localVarQueryParameter["readable_id"] = readable_id - } - - if (resource_category) { - localVarQueryParameter["resource_category"] = resource_category - } - - if (resource_type) { - localVarQueryParameter["resource_type"] = resource_type - } - - if (sortby !== undefined) { - localVarQueryParameter["sortby"] = sortby - } - - if (topic) { - localVarQueryParameter["topic"] = topic - } - - setSearchParams(localVarUrlObj, localVarQueryParameter) - let headersFromBaseOptions = - baseOptions && baseOptions.headers ? baseOptions.headers : {} - localVarRequestOptions.headers = { - ...localVarHeaderParameter, - ...headersFromBaseOptions, - ...options.headers, - } - - return { - url: toPathString(localVarUrlObj), - options: localVarRequestOptions, - } - }, /** * Show content files for a learning resource * @summary Learning Resource Content File List @@ -14509,6 +14426,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -14530,6 +14448,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesListSortbyEnum, topic?: Array, @@ -14607,6 +14526,10 @@ export const LearningResourcesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -14696,6 +14619,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesSimilarListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -14717,6 +14641,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesSimilarListSortbyEnum, topic?: Array, @@ -14795,6 +14720,10 @@ export const LearningResourcesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -14838,6 +14767,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesSummaryListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -14859,6 +14789,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesSummaryListSortbyEnum, topic?: Array, @@ -14936,6 +14867,10 @@ export const LearningResourcesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -15040,6 +14975,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesVectorSimilarListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -15061,6 +14997,7 @@ export const LearningResourcesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesVectorSimilarListSortbyEnum, topic?: Array, @@ -15140,6 +15077,10 @@ export const LearningResourcesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -15177,85 +15118,6 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { const localVarAxiosParamCreator = LearningResourcesApiAxiosParamCreator(configuration) return { - /** - * Fetch multiple learning resources in a single request. - * @summary Retrieve multiple learning resources by IDs - * @param {string} ids Comma-separated list of IDs (e.g. 1,2,3) - * @param {boolean} [certification] - * @param {Array} [certification_type] The type of certification offered * `micromasters` - MicroMasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate - * @param {Array} [course_feature] Content feature for the resources. Load the \'api/v1/course_features\' endpoint for a list of course features - * @param {Array>} [delivery] The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline - * @param {Array} [department] The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies - * @param {boolean} [free] The course/program is offered for free - * @param {Array} [level] The academic level of the resources * `undergraduate` - Undergraduate * `graduate` - Graduate * `high_school` - High School * `noncredit` - Non-Credit * `advanced` - Advanced * `intermediate` - Intermediate * `introductory` - Introductory - * @param {Array} [offered_by] The organization that offers a learning resource * `mitx` - MITx * `ocw` - MIT OpenCourseWare * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `climate` - MIT Climate - * @param {Array} [platform] The platform on which learning resources are offered * `edx` - edX * `ocw` - MIT OpenCourseWare * `oll` - Open Learning Library * `mitxonline` - MITx Online * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `csail` - CSAIL * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `scc` - Schwarzman College of Computing * `ctl` - Center for Transportation & Logistics * `whu` - WHU * `susskind` - Susskind * `globalalumni` - Global Alumni * `simplilearn` - Simplilearn * `emeritus` - Emeritus * `podcast` - Podcast * `youtube` - YouTube * `canvas` - Canvas * `climate` - MIT Climate - * @param {boolean} [professional] - * @param {Array} [readable_id] A unique text identifier for the resources - * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material - * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article - * @param {LearningResourcesBulkListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending - * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - async learningResourcesBulkList( - ids: string, - certification?: boolean, - certification_type?: Array, - course_feature?: Array, - delivery?: Array>, - department?: Array, - free?: boolean, - level?: Array, - offered_by?: Array, - platform?: Array, - professional?: boolean, - readable_id?: Array, - resource_category?: Array, - resource_type?: Array, - sortby?: LearningResourcesBulkListSortbyEnum, - topic?: Array, - options?: RawAxiosRequestConfig, - ): Promise< - ( - axios?: AxiosInstance, - basePath?: string, - ) => AxiosPromise> - > { - const localVarAxiosArgs = - await localVarAxiosParamCreator.learningResourcesBulkList( - ids, - certification, - certification_type, - course_feature, - delivery, - department, - free, - level, - offered_by, - platform, - professional, - readable_id, - resource_category, - resource_type, - sortby, - topic, - options, - ) - const index = configuration?.serverIndex ?? 0 - const operationBasePath = - operationServerMap["LearningResourcesApi.learningResourcesBulkList"]?.[ - index - ]?.url - return (axios, basePath) => - createRequestFunction( - localVarAxiosArgs, - globalAxios, - BASE_PATH, - configuration, - )(axios, operationBasePath || basePath) - }, /** * Show content files for a learning resource * @summary Learning Resource Content File List @@ -15485,6 +15347,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -15506,6 +15369,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesListSortbyEnum, topic?: Array, @@ -15532,6 +15396,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -15598,6 +15463,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesSimilarListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -15619,6 +15485,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesSimilarListSortbyEnum, topic?: Array, @@ -15645,6 +15512,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -15680,6 +15548,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesSummaryListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -15701,6 +15570,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesSummaryListSortbyEnum, topic?: Array, @@ -15727,6 +15597,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -15802,6 +15673,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningResourcesVectorSimilarListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -15823,6 +15695,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningResourcesVectorSimilarListSortbyEnum, topic?: Array, @@ -15849,6 +15722,7 @@ export const LearningResourcesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -15881,39 +15755,6 @@ export const LearningResourcesApiFactory = function ( ) { const localVarFp = LearningResourcesApiFp(configuration) return { - /** - * Fetch multiple learning resources in a single request. - * @summary Retrieve multiple learning resources by IDs - * @param {LearningResourcesApiLearningResourcesBulkListRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - */ - learningResourcesBulkList( - requestParameters: LearningResourcesApiLearningResourcesBulkListRequest, - options?: RawAxiosRequestConfig, - ): AxiosPromise> { - return localVarFp - .learningResourcesBulkList( - requestParameters.ids, - requestParameters.certification, - requestParameters.certification_type, - requestParameters.course_feature, - requestParameters.delivery, - requestParameters.department, - requestParameters.free, - requestParameters.level, - requestParameters.offered_by, - requestParameters.platform, - requestParameters.professional, - requestParameters.readable_id, - requestParameters.resource_category, - requestParameters.resource_type, - requestParameters.sortby, - requestParameters.topic, - options, - ) - .then((request) => request(axios, basePath)) - }, /** * Show content files for a learning resource * @summary Learning Resource Content File List @@ -16046,6 +15887,7 @@ export const LearningResourcesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -16095,6 +15937,7 @@ export const LearningResourcesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -16129,6 +15972,7 @@ export const LearningResourcesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -16183,6 +16027,7 @@ export const LearningResourcesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -16193,125 +16038,6 @@ export const LearningResourcesApiFactory = function ( } } -/** - * Request parameters for learningResourcesBulkList operation in LearningResourcesApi. - * @export - * @interface LearningResourcesApiLearningResourcesBulkListRequest - */ -export interface LearningResourcesApiLearningResourcesBulkListRequest { - /** - * Comma-separated list of IDs (e.g. 1,2,3) - * @type {string} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly ids: string - - /** - * - * @type {boolean} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly certification?: boolean - - /** - * The type of certification offered * `micromasters` - MicroMasters Credential * `professional` - Professional Certificate * `completion` - Certificate of Completion * `none` - No Certificate - * @type {Array<'completion' | 'micromasters' | 'none' | 'professional'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly certification_type?: Array - - /** - * Content feature for the resources. Load the \'api/v1/course_features\' endpoint for a list of course features - * @type {Array} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly course_feature?: Array - - /** - * The delivery of course/program resources * `online` - Online * `hybrid` - Hybrid * `in_person` - In person * `offline` - Offline - * @type {Array>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly delivery?: Array> - - /** - * The department that offers learning resources * `1` - Civil and Environmental Engineering * `2` - Mechanical Engineering * `3` - Materials Science and Engineering * `4` - Architecture * `5` - Chemistry * `6` - Electrical Engineering and Computer Science * `7` - Biology * `8` - Physics * `9` - Brain and Cognitive Sciences * `10` - Chemical Engineering * `11` - Urban Studies and Planning * `12` - Earth, Atmospheric, and Planetary Sciences * `14` - Economics * `15` - Management * `16` - Aeronautics and Astronautics * `17` - Political Science * `18` - Mathematics * `20` - Biological Engineering * `21A` - Anthropology * `21G` - Global Languages * `21H` - History * `21L` - Literature * `21M` - Music and Theater Arts * `22` - Nuclear Science and Engineering * `24` - Linguistics and Philosophy * `CC` - Concourse * `CMS-W` - Comparative Media Studies/Writing * `EC` - Edgerton Center * `ES` - Experimental Study Group * `ESD` - Engineering Systems Division * `HST` - Medical Engineering and Science * `IDS` - Data, Systems, and Society * `MAS` - Media Arts and Sciences * `PE` - Athletics, Physical Education and Recreation * `SP` - Special Programs * `STS` - Science, Technology, and Society * `WGS` - Women\'s and Gender Studies - * @type {Array<'1' | '10' | '11' | '12' | '14' | '15' | '16' | '17' | '18' | '2' | '20' | '21A' | '21G' | '21H' | '21L' | '21M' | '22' | '24' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'CC' | 'CMS-W' | 'EC' | 'ES' | 'ESD' | 'HST' | 'IDS' | 'MAS' | 'PE' | 'SP' | 'STS' | 'WGS'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly department?: Array - - /** - * The course/program is offered for free - * @type {boolean} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly free?: boolean - - /** - * The academic level of the resources * `undergraduate` - Undergraduate * `graduate` - Graduate * `high_school` - High School * `noncredit` - Non-Credit * `advanced` - Advanced * `intermediate` - Intermediate * `introductory` - Introductory - * @type {Array<'advanced' | 'graduate' | 'high_school' | 'intermediate' | 'introductory' | 'noncredit' | 'undergraduate'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly level?: Array - - /** - * The organization that offers a learning resource * `mitx` - MITx * `ocw` - MIT OpenCourseWare * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `climate` - MIT Climate - * @type {Array<'bootcamps' | 'climate' | 'mitpe' | 'mitx' | 'ocw' | 'see' | 'xpro'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly offered_by?: Array - - /** - * The platform on which learning resources are offered * `edx` - edX * `ocw` - MIT OpenCourseWare * `oll` - Open Learning Library * `mitxonline` - MITx Online * `bootcamps` - Bootcamps * `xpro` - MIT xPRO * `csail` - CSAIL * `mitpe` - MIT Professional Education * `see` - MIT Sloan Executive Education * `scc` - Schwarzman College of Computing * `ctl` - Center for Transportation & Logistics * `whu` - WHU * `susskind` - Susskind * `globalalumni` - Global Alumni * `simplilearn` - Simplilearn * `emeritus` - Emeritus * `podcast` - Podcast * `youtube` - YouTube * `canvas` - Canvas * `climate` - MIT Climate - * @type {Array<'bootcamps' | 'canvas' | 'climate' | 'csail' | 'ctl' | 'edx' | 'emeritus' | 'globalalumni' | 'mitpe' | 'mitxonline' | 'ocw' | 'oll' | 'podcast' | 'scc' | 'see' | 'simplilearn' | 'susskind' | 'whu' | 'xpro' | 'youtube'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly platform?: Array - - /** - * - * @type {boolean} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly professional?: boolean - - /** - * A unique text identifier for the resources - * @type {Array} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly readable_id?: Array - - /** - * The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material - * @type {Array<'course' | 'learning_material' | 'program'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly resource_category?: Array - - /** - * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article - * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly resource_type?: Array - - /** - * Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending - * @type {'-id' | '-last_modified' | '-mitcoursenumber' | '-readable_id' | '-start_date' | '-views' | 'id' | 'last_modified' | 'mitcoursenumber' | 'new' | 'readable_id' | 'start_date' | 'upcoming' | 'views'} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly sortby?: LearningResourcesBulkListSortbyEnum - - /** - * Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics - * @type {Array} - * @memberof LearningResourcesApiLearningResourcesBulkList - */ - readonly topic?: Array -} - /** * Request parameters for learningResourcesContentfilesList operation in LearningResourcesApi. * @export @@ -16591,6 +16317,13 @@ export interface LearningResourcesApiLearningResourcesListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof LearningResourcesApiLearningResourcesList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -16731,6 +16464,13 @@ export interface LearningResourcesApiLearningResourcesSimilarListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof LearningResourcesApiLearningResourcesSimilarList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -16857,6 +16597,13 @@ export interface LearningResourcesApiLearningResourcesSummaryListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof LearningResourcesApiLearningResourcesSummaryList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -17014,69 +16761,41 @@ export interface LearningResourcesApiLearningResourcesVectorSimilarListRequest { readonly resource_category?: Array /** - * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article - * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} - * @memberof LearningResourcesApiLearningResourcesVectorSimilarList - */ - readonly resource_type?: Array - - /** - * Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending - * @type {'-id' | '-last_modified' | '-mitcoursenumber' | '-readable_id' | '-start_date' | '-views' | 'id' | 'last_modified' | 'mitcoursenumber' | 'new' | 'readable_id' | 'start_date' | 'upcoming' | 'views'} - * @memberof LearningResourcesApiLearningResourcesVectorSimilarList - */ - readonly sortby?: LearningResourcesVectorSimilarListSortbyEnum - - /** - * Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics - * @type {Array} + * Comma-separated list of learning resource IDs + * @type {Array} * @memberof LearningResourcesApiLearningResourcesVectorSimilarList */ - readonly topic?: Array -} + readonly resource_id?: Array -/** - * LearningResourcesApi - object-oriented interface - * @export - * @class LearningResourcesApi - * @extends {BaseAPI} - */ -export class LearningResourcesApi extends BaseAPI { /** - * Fetch multiple learning resources in a single request. - * @summary Retrieve multiple learning resources by IDs - * @param {LearningResourcesApiLearningResourcesBulkListRequest} requestParameters Request parameters. - * @param {*} [options] Override http request option. - * @throws {RequiredError} - * @memberof LearningResourcesApi - */ - public learningResourcesBulkList( - requestParameters: LearningResourcesApiLearningResourcesBulkListRequest, - options?: RawAxiosRequestConfig, - ) { - return LearningResourcesApiFp(this.configuration) - .learningResourcesBulkList( - requestParameters.ids, - requestParameters.certification, - requestParameters.certification_type, - requestParameters.course_feature, - requestParameters.delivery, - requestParameters.department, - requestParameters.free, - requestParameters.level, - requestParameters.offered_by, - requestParameters.platform, - requestParameters.professional, - requestParameters.readable_id, - requestParameters.resource_category, - requestParameters.resource_type, - requestParameters.sortby, - requestParameters.topic, - options, - ) - .then((request) => request(this.axios, this.basePath)) - } + * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article + * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} + * @memberof LearningResourcesApiLearningResourcesVectorSimilarList + */ + readonly resource_type?: Array + + /** + * Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending + * @type {'-id' | '-last_modified' | '-mitcoursenumber' | '-readable_id' | '-start_date' | '-views' | 'id' | 'last_modified' | 'mitcoursenumber' | 'new' | 'readable_id' | 'start_date' | 'upcoming' | 'views'} + * @memberof LearningResourcesApiLearningResourcesVectorSimilarList + */ + readonly sortby?: LearningResourcesVectorSimilarListSortbyEnum + + /** + * Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics + * @type {Array} + * @memberof LearningResourcesApiLearningResourcesVectorSimilarList + */ + readonly topic?: Array +} +/** + * LearningResourcesApi - object-oriented interface + * @export + * @class LearningResourcesApi + * @extends {BaseAPI} + */ +export class LearningResourcesApi extends BaseAPI { /** * Show content files for a learning resource * @summary Learning Resource Content File List @@ -17220,6 +16939,7 @@ export class LearningResourcesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -17273,6 +16993,7 @@ export class LearningResourcesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -17309,6 +17030,7 @@ export class LearningResourcesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -17367,6 +17089,7 @@ export class LearningResourcesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -17376,173 +17099,6 @@ export class LearningResourcesApi extends BaseAPI { } } -/** - * @export - */ -export const LearningResourcesBulkListCertificationTypeEnum = { - Completion: "completion", - Micromasters: "micromasters", - None: "none", - Professional: "professional", -} as const -export type LearningResourcesBulkListCertificationTypeEnum = - (typeof LearningResourcesBulkListCertificationTypeEnum)[keyof typeof LearningResourcesBulkListCertificationTypeEnum] -/** - * @export - */ -export const LearningResourcesBulkListDeliveryEnum = { - Online: "online", - Hybrid: "hybrid", - InPerson: "in_person", - Offline: "offline", -} as const -export type LearningResourcesBulkListDeliveryEnum = - (typeof LearningResourcesBulkListDeliveryEnum)[keyof typeof LearningResourcesBulkListDeliveryEnum] -/** - * @export - */ -export const LearningResourcesBulkListDepartmentEnum = { - _1: "1", - _10: "10", - _11: "11", - _12: "12", - _14: "14", - _15: "15", - _16: "16", - _17: "17", - _18: "18", - _2: "2", - _20: "20", - _21A: "21A", - _21G: "21G", - _21H: "21H", - _21L: "21L", - _21M: "21M", - _22: "22", - _24: "24", - _3: "3", - _4: "4", - _5: "5", - _6: "6", - _7: "7", - _8: "8", - _9: "9", - Cc: "CC", - CmsW: "CMS-W", - Ec: "EC", - Es: "ES", - Esd: "ESD", - Hst: "HST", - Ids: "IDS", - Mas: "MAS", - Pe: "PE", - Sp: "SP", - Sts: "STS", - Wgs: "WGS", -} as const -export type LearningResourcesBulkListDepartmentEnum = - (typeof LearningResourcesBulkListDepartmentEnum)[keyof typeof LearningResourcesBulkListDepartmentEnum] -/** - * @export - */ -export const LearningResourcesBulkListLevelEnum = { - Advanced: "advanced", - Graduate: "graduate", - HighSchool: "high_school", - Intermediate: "intermediate", - Introductory: "introductory", - Noncredit: "noncredit", - Undergraduate: "undergraduate", -} as const -export type LearningResourcesBulkListLevelEnum = - (typeof LearningResourcesBulkListLevelEnum)[keyof typeof LearningResourcesBulkListLevelEnum] -/** - * @export - */ -export const LearningResourcesBulkListOfferedByEnum = { - Bootcamps: "bootcamps", - Climate: "climate", - Mitpe: "mitpe", - Mitx: "mitx", - Ocw: "ocw", - See: "see", - Xpro: "xpro", -} as const -export type LearningResourcesBulkListOfferedByEnum = - (typeof LearningResourcesBulkListOfferedByEnum)[keyof typeof LearningResourcesBulkListOfferedByEnum] -/** - * @export - */ -export const LearningResourcesBulkListPlatformEnum = { - Bootcamps: "bootcamps", - Canvas: "canvas", - Climate: "climate", - Csail: "csail", - Ctl: "ctl", - Edx: "edx", - Emeritus: "emeritus", - Globalalumni: "globalalumni", - Mitpe: "mitpe", - Mitxonline: "mitxonline", - Ocw: "ocw", - Oll: "oll", - Podcast: "podcast", - Scc: "scc", - See: "see", - Simplilearn: "simplilearn", - Susskind: "susskind", - Whu: "whu", - Xpro: "xpro", - Youtube: "youtube", -} as const -export type LearningResourcesBulkListPlatformEnum = - (typeof LearningResourcesBulkListPlatformEnum)[keyof typeof LearningResourcesBulkListPlatformEnum] -/** - * @export - */ -export const LearningResourcesBulkListResourceCategoryEnum = { - Course: "course", - LearningMaterial: "learning_material", - Program: "program", -} as const -export type LearningResourcesBulkListResourceCategoryEnum = - (typeof LearningResourcesBulkListResourceCategoryEnum)[keyof typeof LearningResourcesBulkListResourceCategoryEnum] -/** - * @export - */ -export const LearningResourcesBulkListResourceTypeEnum = { - Article: "article", - Course: "course", - LearningPath: "learning_path", - Podcast: "podcast", - PodcastEpisode: "podcast_episode", - Program: "program", - Video: "video", - VideoPlaylist: "video_playlist", -} as const -export type LearningResourcesBulkListResourceTypeEnum = - (typeof LearningResourcesBulkListResourceTypeEnum)[keyof typeof LearningResourcesBulkListResourceTypeEnum] -/** - * @export - */ -export const LearningResourcesBulkListSortbyEnum = { - Id: "-id", - LastModified: "-last_modified", - Mitcoursenumber: "-mitcoursenumber", - ReadableId: "-readable_id", - StartDate: "-start_date", - Views: "-views", - Id2: "id", - LastModified2: "last_modified", - Mitcoursenumber2: "mitcoursenumber", - New: "new", - ReadableId2: "readable_id", - StartDate2: "start_date", - Upcoming: "upcoming", - Views2: "views", -} as const -export type LearningResourcesBulkListSortbyEnum = - (typeof LearningResourcesBulkListSortbyEnum)[keyof typeof LearningResourcesBulkListSortbyEnum] /** * @export */ @@ -22196,6 +21752,7 @@ export const LearningpathsApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningpathsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -22217,6 +21774,7 @@ export const LearningpathsApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningpathsListSortbyEnum, topic?: Array, @@ -22294,6 +21852,10 @@ export const LearningpathsApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -22735,6 +22297,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {LearningpathsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -22756,6 +22319,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: LearningpathsListSortbyEnum, topic?: Array, @@ -22782,6 +22346,7 @@ export const LearningpathsApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -23065,6 +22630,7 @@ export const LearningpathsApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -23380,6 +22946,13 @@ export interface LearningpathsApiLearningpathsListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof LearningpathsApiLearningpathsList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -23616,6 +23189,7 @@ export class LearningpathsApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -24650,6 +24224,7 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {PodcastEpisodesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -24671,6 +24246,7 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: PodcastEpisodesListSortbyEnum, topic?: Array, @@ -24748,6 +24324,10 @@ export const PodcastEpisodesApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -24848,6 +24428,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {PodcastEpisodesListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -24869,6 +24450,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: PodcastEpisodesListSortbyEnum, topic?: Array, @@ -24895,6 +24477,7 @@ export const PodcastEpisodesApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -24984,6 +24567,7 @@ export const PodcastEpisodesApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -25113,6 +24697,13 @@ export interface PodcastEpisodesApiPodcastEpisodesListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof PodcastEpisodesApiPodcastEpisodesList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -25184,6 +24775,7 @@ export class PodcastEpisodesApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -25528,6 +25120,7 @@ export const PodcastsApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {PodcastsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -25549,6 +25142,7 @@ export const PodcastsApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: PodcastsListSortbyEnum, topic?: Array, @@ -25626,6 +25220,10 @@ export const PodcastsApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -25801,6 +25399,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {PodcastsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -25822,6 +25421,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: PodcastsListSortbyEnum, topic?: Array, @@ -25847,6 +25447,7 @@ export const PodcastsApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -25973,6 +25574,7 @@ export const PodcastsApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -26158,6 +25760,13 @@ export interface PodcastsApiPodcastsListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof PodcastsApiPodcastsList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -26273,6 +25882,7 @@ export class PodcastsApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -26651,6 +26261,7 @@ export const ProgramsApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {ProgramsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -26672,6 +26283,7 @@ export const ProgramsApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: ProgramsListSortbyEnum, topic?: Array, @@ -26749,6 +26361,10 @@ export const ProgramsApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -26848,6 +26464,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {ProgramsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -26869,6 +26486,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: ProgramsListSortbyEnum, topic?: Array, @@ -26894,6 +26512,7 @@ export const ProgramsApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -26980,6 +26599,7 @@ export const ProgramsApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -27109,6 +26729,13 @@ export interface ProgramsApiProgramsListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof ProgramsApiProgramsList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -27180,6 +26807,7 @@ export class ProgramsApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -29761,6 +29389,7 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {VideoPlaylistsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -29782,6 +29411,7 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: VideoPlaylistsListSortbyEnum, topic?: Array, @@ -29859,6 +29489,10 @@ export const VideoPlaylistsApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -30038,6 +29672,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {VideoPlaylistsListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -30059,6 +29694,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: VideoPlaylistsListSortbyEnum, topic?: Array, @@ -30085,6 +29721,7 @@ export const VideoPlaylistsApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -30212,6 +29849,7 @@ export const VideoPlaylistsApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -30397,6 +30035,13 @@ export interface VideoPlaylistsApiVideoPlaylistsListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof VideoPlaylistsApiVideoPlaylistsList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -30512,6 +30157,7 @@ export class VideoPlaylistsApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -30731,6 +30377,7 @@ export const VideosApiAxiosParamCreator = function ( * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {VideosListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -30752,6 +30399,7 @@ export const VideosApiAxiosParamCreator = function ( professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: VideosListSortbyEnum, topic?: Array, @@ -30829,6 +30477,10 @@ export const VideosApiAxiosParamCreator = function ( localVarQueryParameter["resource_category"] = resource_category } + if (resource_id) { + localVarQueryParameter["resource_id"] = resource_id + } + if (resource_type) { localVarQueryParameter["resource_type"] = resource_type } @@ -30928,6 +30580,7 @@ export const VideosApiFp = function (configuration?: Configuration) { * @param {boolean} [professional] * @param {Array} [readable_id] A unique text identifier for the resources * @param {Array} [resource_category] The resource category of the learning resources * `course` - Course * `program` - Program * `learning_material` - Learning Material + * @param {Array} [resource_id] Comma-separated list of learning resource IDs * @param {Array} [resource_type] The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @param {VideosListSortbyEnum} [sortby] Sort By * `id` - Object ID ascending * `-id` - Object ID descending * `readable_id` - Readable ID ascending * `-readable_id` - Readable ID descending * `last_modified` - Last Modified Date ascending * `-last_modified` - Last Modified Date descending * `new` - Newest resources first * `start_date` - Start Date ascending * `-start_date` - Start Date descending * `mitcoursenumber` - MIT course number ascending * `-mitcoursenumber` - MIT course number descending * `views` - Popularity ascending * `-views` - Popularity descending * `upcoming` - Next start date ascending * @param {Array} [topic] Topics covered by the resources. Load the \'/api/v1/topics\' endpoint for a list of topics @@ -30949,6 +30602,7 @@ export const VideosApiFp = function (configuration?: Configuration) { professional?: boolean, readable_id?: Array, resource_category?: Array, + resource_id?: Array, resource_type?: Array, sortby?: VideosListSortbyEnum, topic?: Array, @@ -30974,6 +30628,7 @@ export const VideosApiFp = function (configuration?: Configuration) { professional, readable_id, resource_category, + resource_id, resource_type, sortby, topic, @@ -31059,6 +30714,7 @@ export const VideosApiFactory = function ( requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, @@ -31188,6 +30844,13 @@ export interface VideosApiVideosListRequest { */ readonly resource_category?: Array + /** + * Comma-separated list of learning resource IDs + * @type {Array} + * @memberof VideosApiVideosList + */ + readonly resource_id?: Array + /** * The type of learning resource * `course` - Course * `program` - Program * `learning_path` - Learning Path * `podcast` - Podcast * `podcast_episode` - Podcast Episode * `video` - Video * `video_playlist` - Video Playlist * `article` - Article * @type {Array<'article' | 'course' | 'learning_path' | 'podcast' | 'podcast_episode' | 'program' | 'video' | 'video_playlist'>} @@ -31259,6 +30922,7 @@ export class VideosApi extends BaseAPI { requestParameters.professional, requestParameters.readable_id, requestParameters.resource_category, + requestParameters.resource_id, requestParameters.resource_type, requestParameters.sortby, requestParameters.topic, diff --git a/frontends/api/src/hooks/learningResources/index.ts b/frontends/api/src/hooks/learningResources/index.ts index ded87b7aa7..cf96fba96c 100644 --- a/frontends/api/src/hooks/learningResources/index.ts +++ b/frontends/api/src/hooks/learningResources/index.ts @@ -24,7 +24,6 @@ import { schoolQueries, platformsQueries, learningResourceKeys, - clearListMemberships, } from "./queries" import { userlistKeys } from "../userLists/queries" import { learningPathKeys } from "../learningPaths/queries" @@ -66,15 +65,11 @@ const useLearningResourcesBulkList = ( const queryClient = useQueryClient() return useQuery({ - queryKey: learningResourceKeys.bulk(ids), + ...learningResourceQueries.list({ resource_id: ids }), enabled: options?.enabled && ids.length > 0, - queryFn: async () => { - const res = (await learningResourcesApi.learningResourcesBulkList({ - ids: ids.join(","), - })) as { data: LearningResource[] } - - const resources = res.data.map(clearListMemberships) + select: (data) => { + const resources = data.results resources.forEach((resource) => { queryClient.setQueryData( diff --git a/frontends/api/src/hooks/learningResources/queries.ts b/frontends/api/src/hooks/learningResources/queries.ts index 626b9f5eab..bb2aceefb1 100644 --- a/frontends/api/src/hooks/learningResources/queries.ts +++ b/frontends/api/src/hooks/learningResources/queries.ts @@ -125,16 +125,6 @@ const learningResourceQueries = { .learningResourcesRetrieve({ id }) .then((res) => clearListMemberships(res.data)), }), - learningResourcesBulkList: (ids: string) => - queryOptions({ - queryKey: [...learningResourceKeys.root, "bulk", ids], - queryFn: () => - learningResourcesApi - .learningResourcesBulkList({ ids }) - .then((res) => - res.data.map((resource) => clearListMemberships(resource)), - ), - }), items: (id: number, params: ItemsListRequest) => queryOptions({ queryKey: learningResourceKeys.items(id, params), diff --git a/frontends/main/package.json b/frontends/main/package.json index 06aa568717..b31b0c371e 100644 --- a/frontends/main/package.json +++ b/frontends/main/package.json @@ -16,11 +16,28 @@ "@mitodl/course-search-utils": "^3.5.0", "@mitodl/mitxonline-api-axios": "^2025.11.24", "@mitodl/smoot-design": "^6.19.0", + "@mui/material": "^6.4.5", + "@mui/material-nextjs": "^6.4.3", "@next/bundle-analyzer": "^14.2.15", "@react-pdf/renderer": "^4.3.0", "@remixicon/react": "^4.2.0", "@sentry/nextjs": "^10.0.0", "@tanstack/react-query": "^5.66", + "@tiptap/core": "^3.13.0", + "@tiptap/extension-document": "^3.13.0", + "@tiptap/extension-heading": "^3.13.0", + "@tiptap/extension-highlight": "^3.13.0", + "@tiptap/extension-horizontal-rule": "^3.13.0", + "@tiptap/extension-image": "^3.13.0", + "@tiptap/extension-list": "^3.13.0", + "@tiptap/extension-subscript": "^3.13.0", + "@tiptap/extension-superscript": "^3.13.0", + "@tiptap/extension-text-align": "^3.13.0", + "@tiptap/extension-typography": "^3.13.0", + "@tiptap/extensions": "^3.13.0", + "@tiptap/pm": "^3.13.0", + "@tiptap/react": "^3.13.0", + "@tiptap/starter-kit": "^3.13.0", "api": "workspace:*", "async_hooks": "^1.0.0", "classnames": "^2.5.1", diff --git a/frontends/main/src/app-pages/Articles/ArticleDetailPage.tsx b/frontends/main/src/app-pages/Articles/ArticleDetailPage.tsx index af4b026c06..51d85de12c 100644 --- a/frontends/main/src/app-pages/Articles/ArticleDetailPage.tsx +++ b/frontends/main/src/app-pages/Articles/ArticleDetailPage.tsx @@ -2,7 +2,8 @@ import React from "react" import { useArticleDetailRetrieve } from "api/hooks/articles" -import { LoadingSpinner, ArticleEditor, styled } from "ol-components" +import { LoadingSpinner, styled } from "ol-components" +import { ArticleEditor } from "@/page-components/TiptapEditor/ArticleEditor" import { notFound } from "next/navigation" import { useFeatureFlagEnabled } from "posthog-js/react" import { FeatureFlags } from "@/common/feature_flags" diff --git a/frontends/main/src/app-pages/Articles/ArticleEditPage.tsx b/frontends/main/src/app-pages/Articles/ArticleEditPage.tsx index 78492d4481..e1efbfb455 100644 --- a/frontends/main/src/app-pages/Articles/ArticleEditPage.tsx +++ b/frontends/main/src/app-pages/Articles/ArticleEditPage.tsx @@ -6,7 +6,8 @@ import { notFound } from "next/navigation" import { Permission } from "api/hooks/user" import { useArticleDetailRetrieve } from "api/hooks/articles" import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" -import { styled, LoadingSpinner, ArticleEditor } from "ol-components" +import { styled, LoadingSpinner } from "ol-components" +import { ArticleEditor } from "@/page-components/TiptapEditor/ArticleEditor" import { articlesView } from "@/common/urls" const PageContainer = styled.div(({ theme }) => ({ diff --git a/frontends/main/src/app-pages/Articles/ArticleNewPage.tsx b/frontends/main/src/app-pages/Articles/ArticleNewPage.tsx index 53c23f288b..3ea8ae62cb 100644 --- a/frontends/main/src/app-pages/Articles/ArticleNewPage.tsx +++ b/frontends/main/src/app-pages/Articles/ArticleNewPage.tsx @@ -4,7 +4,8 @@ import React from "react" import { useRouter } from "next-nprogress-bar" import { Permission } from "api/hooks/user" import RestrictedRoute from "@/components/RestrictedRoute/RestrictedRoute" -import { ArticleEditor, styled } from "ol-components" +import { styled } from "ol-components" +import { ArticleEditor } from "@/page-components/TiptapEditor/ArticleEditor" import { articlesView } from "@/common/urls" const PageContainer = styled.div(({ theme }) => ({ diff --git a/frontends/main/src/app-pages/ProductPages/CoursePage.tsx b/frontends/main/src/app-pages/ProductPages/CoursePage.tsx index 8b926cbb2a..820bb48d4c 100644 --- a/frontends/main/src/app-pages/ProductPages/CoursePage.tsx +++ b/frontends/main/src/app-pages/ProductPages/CoursePage.tsx @@ -77,7 +77,7 @@ const CoursePage: React.FC = ({ readableId }) => { ) const page = pages.data?.items[0] const course = courses.data?.results?.[0] - const enabled = useFeatureFlagEnabled(FeatureFlags.ProductPageCourse) + const enabled = useFeatureFlagEnabled(FeatureFlags.MitxOnlineProductPages) const flagsLoaded = useFeatureFlagsLoaded() if (!enabled) { @@ -102,7 +102,7 @@ const CoursePage: React.FC = ({ readableId }) => { return ( ({ const TitleBox = styled(Stack)(({ theme }) => ({ color: theme.custom.colors.white, })) -const OfferedByTag = styled.div(({ theme }) => ({ +const ProductTag = styled.div(({ theme }) => ({ backgroundColor: theme.custom.colors.darkGray1, display: "flex", alignItems: "center", justifyContent: "center", - height: "32px", - padding: "0 12px", + padding: "4px 12px", borderRadius: "4px", - marginBottom: "4px", - ...theme.typography.subtitle1, - [theme.breakpoints.down("md")]: { - display: "none", - }, + ...theme.typography.subtitle2, })) const Page = styled.div(({ theme }) => ({ @@ -162,7 +157,7 @@ type HeadingData = { } type ProductPageTemplateProps = { - offeredBy: string + tags: string[] currentBreadcrumbLabel: string title: string shortDescription: React.ReactNode @@ -172,7 +167,7 @@ type ProductPageTemplateProps = { navLinks: HeadingData[] } const ProductPageTemplate: React.FC = ({ - offeredBy, + tags, currentBreadcrumbLabel, title, shortDescription, @@ -183,7 +178,7 @@ const ProductPageTemplate: React.FC = ({ return ( - + = ({ current={currentBreadcrumbLabel} /> - {offeredBy} + + {tags.map((tag) => { + return {tag} + })} + {title} diff --git a/frontends/main/src/app-pages/ProductPages/ProductSummary.test.tsx b/frontends/main/src/app-pages/ProductPages/ProductSummary.test.tsx index a53ed4b5d7..70048d5c55 100644 --- a/frontends/main/src/app-pages/ProductPages/ProductSummary.test.tsx +++ b/frontends/main/src/app-pages/ProductPages/ProductSummary.test.tsx @@ -450,7 +450,7 @@ describe("Program Pacing Row", () => { ) const summary = screen.getByRole("region", { name: "Program summary" }) const paceRow = within(summary).getByTestId(TestIds.PaceRow) - expect(paceRow).toHaveTextContent(`Program Format: ${expected}`) + expect(paceRow).toHaveTextContent(`Course Format: ${expected}`) }) test.each([ @@ -495,21 +495,18 @@ describe("Price & Certificate Row", () => { expect(priceRow).toHaveTextContent("Free to Learn") }) -}) -describe("Program Certificate Track Row", () => { test("Renders certificate information", () => { const program = factories.programs.program() + invariant(program.page.price) renderWithProviders( , ) const summary = screen.getByRole("region", { name: "Program summary" }) - const certRow = within(summary).getByTestId(TestIds.CertificateTrackRow) + const certRow = within(summary).getByTestId(TestIds.PriceRow) expect(certRow).toHaveTextContent("Certificate Track") - expect(certRow).toHaveTextContent( - `${program.min_price}\u2013$${program.max_price}`, - ) + expect(certRow).toHaveTextContent(program.page.price) }) }) diff --git a/frontends/main/src/app-pages/ProductPages/ProductSummary.tsx b/frontends/main/src/app-pages/ProductPages/ProductSummary.tsx index e1616b1d20..a3b9477522 100644 --- a/frontends/main/src/app-pages/ProductPages/ProductSummary.tsx +++ b/frontends/main/src/app-pages/ProductPages/ProductSummary.tsx @@ -1,13 +1,13 @@ import React, { HTMLAttributes } from "react" import { Alert, styled, VisuallyHidden } from "@mitodl/smoot-design" import { Dialog, Link, Skeleton, Stack, Typography } from "ol-components" +import type { StackProps } from "ol-components" import { RiCalendarLine, RiComputerLine, RiPriceTag3Line, RiTimeLine, RiFileCopy2Line, - RiAwardLine, } from "@remixicon/react" import { formatDate, NoSSR, pluralize } from "ol-utilities" import { @@ -17,9 +17,10 @@ import { } from "@mitodl/mitxonline-api-axios/v2" import { HeadingIds, parseReqTree } from "./util" import { LearningResource } from "api" -import { getCertificatePrice } from "@/common/mitxonline" +import { getCourseCertificatePrice } from "@/common/mitxonline" const ResponsiveLink = styled(Link)(({ theme }) => ({ + ...theme.typography.body2, // override default for "black" color is subtitle2 [theme.breakpoints.down("sm")]: { ...theme.typography.body3, }, @@ -46,18 +47,17 @@ const InfoRow = styled.div(({ theme }) => ({ }, })) -const InfoRowInner: React.FC<{ children?: React.ReactNode }> = ({ - children, -}) => ( +const InfoRowInner: React.FC> = ( + props, +) => ( - {children} - + {...props} + /> ) const InfoLabel = styled.span<{ underline?: boolean }>( @@ -68,7 +68,6 @@ const InfoLabel = styled.span<{ underline?: boolean }>( underline && { textDecoration: "underline" }, ], ) -const InfoValue = styled.span({}) const InfoLabelValue: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value, @@ -77,7 +76,7 @@ const InfoLabelValue: React.FC<{ label: string; value: React.ReactNode }> = ({ {label} {": "} - {value} + {value} ) : null @@ -118,23 +117,6 @@ const getEndDate = (run: CourseRunV2) => { ) } -const getUpgradeDeadline = (run: CourseRunV2) => { - if (run.is_archived) return null - return run.upgrade_deadline ? ( - - } - > - {formatDate(run.upgrade_deadline)} - - ) : null -} - type CourseInfoRowProps = { course: CourseWithCourseRunsSerializerV2 nextRun: CourseRunV2 @@ -176,7 +158,7 @@ const LearnMoreDialog: React.FC = ({ { @@ -197,7 +179,7 @@ const LearnMoreDialog: React.FC = ({ href={href} target="_blank" rel="noopener noreferrer" - color="red" + color="black" > Learn More @@ -261,44 +243,59 @@ const CourseDurationRow: React.FC = ({ ) } -const CertificateBox: React.FC = ({ nextRun }) => { - const certificatePrice = getCertificatePrice(nextRun) +const COURSE_CERT_INFO_LINK = ( + + Learn More + +) + +const CertificateBoxRoot = styled.div(({ theme }) => ({ + width: "100%", + backgroundColor: theme.custom.colors.lightGray1, + borderRadius: "8px", + padding: "16px", + display: "flex", + flexDirection: "column", + gap: "8px", +})) + +const CourseCertificateBox: React.FC = ({ nextRun }) => { + const certificatePrice = getCourseCertificatePrice(nextRun) + const upgradeDeadline = nextRun.is_archived ? null : nextRun.upgrade_deadline - const certInfoLink = ( - - Learn More - - ) - const upgradeDeadline = getUpgradeDeadline(nextRun) return ( - ({ - backgroundColor: theme.custom.colors.lightGray1, - borderRadius: "8px", - padding: "16px", - })} - > + {certificatePrice ? ( <> - + - {certInfoLink} + {COURSE_CERT_INFO_LINK} {upgradeDeadline ? ( ({ color: theme.custom.colors.red })} > - Payment deadline: {upgradeDeadline} + Payment deadline:{" "} + + } + > + {formatDate(upgradeDeadline)} + ) : null} @@ -307,10 +304,10 @@ const CertificateBox: React.FC = ({ nextRun }) => { Certificate deadline passed - {certInfoLink} + {COURSE_CERT_INFO_LINK} )} - + ) } @@ -324,7 +321,7 @@ const CoursePriceRow: React.FC = ({ ) diff --git a/frontends/main/src/app-pages/ProductPages/ProgramPage.test.tsx b/frontends/main/src/app-pages/ProductPages/ProgramPage.test.tsx index 2a08759525..9a7c1d5d10 100644 --- a/frontends/main/src/app-pages/ProductPages/ProgramPage.test.tsx +++ b/frontends/main/src/app-pages/ProductPages/ProgramPage.test.tsx @@ -196,6 +196,20 @@ describe("ProgramPage", () => { }, ) + test("Includes program type in banner area", async () => { + const program = makeProgram({ + ...makeReqs(), + program_type: "AwesomeProgramz", + }) + const page = makePage({ program_details: program }) + setupApis({ program, page }) + renderWithProviders() + + const banner = await screen.findByTestId("banner-container") + expect(within(banner).getByText("MITx")).toBeVisible() + expect(within(banner).getByText("AwesomeProgramz")).toBeVisible() + }) + test("Page has expected headings", async () => { const program = makeProgram({ ...makeReqs({ diff --git a/frontends/main/src/app-pages/ProductPages/ProgramPage.tsx b/frontends/main/src/app-pages/ProductPages/ProgramPage.tsx index 55e7cd257a..fa34b972d5 100644 --- a/frontends/main/src/app-pages/ProductPages/ProgramPage.tsx +++ b/frontends/main/src/app-pages/ProductPages/ProgramPage.tsx @@ -1,7 +1,7 @@ "use client" import React from "react" -import { PlainList, Skeleton, Stack, Typography } from "ol-components" +import { PlainList, Stack, Typography } from "ol-components" import { ResourceCard } from "@/page-components/ResourceCard/ResourceCard" import { pagesQueries } from "api/mitxonline-hooks/pages" @@ -261,7 +261,7 @@ const ProgramPage: React.FC = ({ readableId }) => { const page = pages.data?.items[0] const program = programs.data?.results?.[0] const programResource = programResources.data?.results?.[0] - const enabled = useFeatureFlagEnabled(FeatureFlags.ProductPageCourse) + const enabled = useFeatureFlagEnabled(FeatureFlags.MitxOnlineProductPages) const flagsLoaded = useFeatureFlagsLoaded() if (!enabled) { @@ -275,7 +275,7 @@ const ProgramPage: React.FC = ({ readableId }) => { if (!isLoading) { return notFound() } else { - return + return null } } @@ -284,9 +284,12 @@ const ProgramPage: React.FC = ({ readableId }) => { const imageSrc = page.feature_image ? page.program_details.page.feature_image_src : DEFAULT_RESOURCE_IMG + + const tags = ["MITx", program.program_type].filter((t): t is string => !!t) + return ( { ) } -const getCertificatePrice = (run: CourseRunV2) => { +/** + * Returns certificate price as formatted string, or null if upgrade not available + */ +const getCourseCertificatePrice = (run: CourseRunV2) => { if (!canUpgrade(run)) return null const product = run.products[0] const amount = product.price @@ -38,4 +41,4 @@ const getCertificatePrice = (run: CourseRunV2) => { }) } -export { getCertificatePrice, canUpgrade, upgradeRunUrl } +export { getCourseCertificatePrice, canUpgrade, upgradeRunUrl } diff --git a/frontends/main/src/common/urls.ts b/frontends/main/src/common/urls.ts index e60f83fa2b..f43d6f2dcb 100644 --- a/frontends/main/src/common/urls.ts +++ b/frontends/main/src/common/urls.ts @@ -196,4 +196,9 @@ export const TWITTER_SHARE_BASE_URL = "https://x.com/share" export const LINKEDIN_SHARE_BASE_URL = "https://www.linkedin.com/sharing/share-offsite" -export const COURSE_PAGE_VIEW = "/courses/[readableId]/" +export const COURSE_PAGE_VIEW = "/courses/[readableId]" +export const coursePageView = (readableId: string) => + generatePath(COURSE_PAGE_VIEW, { readableId }) +export const PROGRAM_PAGE_VIEW = "/programs/[readableId]" +export const programPageView = (readableId: string) => + generatePath(PROGRAM_PAGE_VIEW, { readableId }) diff --git a/frontends/main/src/page-components/EnrollmentDialogs/CourseEnrollmentDialog.tsx b/frontends/main/src/page-components/EnrollmentDialogs/CourseEnrollmentDialog.tsx index 8dd5a6233c..00c97bdeab 100644 --- a/frontends/main/src/page-components/EnrollmentDialogs/CourseEnrollmentDialog.tsx +++ b/frontends/main/src/page-components/EnrollmentDialogs/CourseEnrollmentDialog.tsx @@ -18,7 +18,7 @@ import { RiCheckLine, RiArrowRightLine, RiAwardFill } from "@remixicon/react" import { Alert, Button, ButtonProps } from "@mitodl/smoot-design" import { canUpgrade, - getCertificatePrice, + getCourseCertificatePrice, upgradeRunUrl, } from "@/common/mitxonline" import { useCreateEnrollment } from "api/mitxonline-hooks/enrollment" @@ -172,7 +172,8 @@ const CertificateUpsell: React.FC<{ }> = ({ courseRun }) => { const enabled = courseRun ? canUpgrade(courseRun) : false const product = courseRun?.products[0] - const price = courseRun && enabled ? getCertificatePrice(courseRun) : null + const price = + courseRun && enabled ? getCourseCertificatePrice(courseRun) : null const deadlineUI = courseRun?.upgrade_deadline ? ( <> Payment due: diff --git a/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.test.tsx b/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.test.tsx new file mode 100644 index 0000000000..e375e4dd31 --- /dev/null +++ b/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.test.tsx @@ -0,0 +1,313 @@ +import React from "react" +import { screen } from "@testing-library/react" +import { renderWithProviders } from "@/test-utils" +import { factories } from "api/test-utils" +import { PlatformEnum, ResourceTypeEnum } from "api" +import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react" +import type { PostHog } from "posthog-js" +import { FeatureFlags } from "@/common/feature_flags" +import { coursePageView, programPageView } from "@/common/urls" +import CallToActionSection from "./CallToActionSection" +import type { ImageConfig } from "ol-components" +import { kebabCase } from "lodash" +import { faker } from "@faker-js/faker/locale/en" + +jest.mock("posthog-js/react") + +const mockUsePostHog = jest.mocked(usePostHog) +const mockCapture = jest.fn() as PostHog["capture"] +const posthog = { + capture: mockCapture, +} as PostHog +mockUsePostHog.mockReturnValue(posthog) +const mockUseFeatureFlagEnabled = jest.mocked(useFeatureFlagEnabled) +mockUseFeatureFlagEnabled.mockReturnValue(false) + +const IMG_CONFIG: ImageConfig = { + width: 385, + height: 200, +} + +describe("CallToActionSection", () => { + describe("Resource URL rendering", () => { + it.each([ + { resourceType: ResourceTypeEnum.Course, expectedText: "Learn More" }, + { resourceType: ResourceTypeEnum.Program, expectedText: "Learn More" }, + { + resourceType: ResourceTypeEnum.Video, + expectedText: "Watch on YouTube", + }, + { + resourceType: ResourceTypeEnum.VideoPlaylist, + expectedText: "Watch on YouTube", + }, + { + resourceType: ResourceTypeEnum.Podcast, + expectedText: "Listen to Podcast", + }, + { + resourceType: ResourceTypeEnum.PodcastEpisode, + expectedText: "Listen to Podcast", + }, + { resourceType: ResourceTypeEnum.Article, expectedText: "View Article" }, + { + resourceType: ResourceTypeEnum.LearningPath, + expectedText: "Learn More", + }, + ])( + "renders link with correct text for $resourceType", + ({ resourceType, expectedText }) => { + const resource = factories.learningResources.resource({ + resource_type: resourceType, + url: "https://example.com/resource", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: expectedText }) + expect(link).toBeInTheDocument() + }, + ) + + it("renders 'Access Course Materials' for OCW courses", () => { + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + platform: { code: PlatformEnum.Ocw }, + url: "https://ocw.mit.edu/course", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { + name: "Access Course Materials", + }) + expect(link).toBeInTheDocument() + }) + }) + + describe("UTM parameters", () => { + it("adds UTM params to external URLs", () => { + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + title: "Test Course Title", + url: "https://external-site.com/course", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + expect(link).toHaveAttribute( + "href", + `https://external-site.com/course?utm_source=mit-learn&utm_medium=referral&utm_content=${kebabCase(resource.title)}`, + ) + }) + + it("adds UTM params to URLs with existing query params", () => { + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + title: "Test Course", + url: "https://external-site.com/course?existing=param", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + const href = link.getAttribute("href") + expect(href).toContain("utm_source=mit-learn") + expect(href).toContain("utm_medium=referral") + expect(href).toContain("utm_content=test-course") + expect(href).toContain("existing=param") + }) + + it("does NOT add UTM params to internal URLs", () => { + const NEXT_PUBLIC_ORIGIN = process.env.NEXT_PUBLIC_ORIGIN + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + title: "Test Course", + url: `${NEXT_PUBLIC_ORIGIN}/internal/page`, + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + const href = link.getAttribute("href") + expect(href).not.toContain("utm_source") + expect(href).not.toContain("utm_medium") + expect(href).not.toContain("utm_content") + expect(href).toBe(`${NEXT_PUBLIC_ORIGIN}/internal/page`) + }) + + it("does NOT add UTM params to relative URLs", () => { + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + title: "Test Course", + url: "/relative/path", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + const href = link.getAttribute("href") + expect(href).not.toContain("utm_source") + expect(href).not.toContain("utm_medium") + expect(href).not.toContain("utm_content") + expect(href).toBe("/relative/path") + }) + }) + + describe("MITx Online product pages", () => { + const makeMitxOnlineResource = () => + factories.learningResources.resource({ + resource_type: faker.helpers.arrayElement([ + ResourceTypeEnum.Course, + ResourceTypeEnum.Program, + ]), + readable_id: "test-readable-id", + platform: { code: PlatformEnum.Mitxonline }, + url: "https://mitxonline.mit.edu/external-url", + }) + + it("uses new course product page when feature flag is ON and resource is MITx Online course/program", () => { + mockUseFeatureFlagEnabled.mockImplementation( + (flag) => flag === FeatureFlags.MitxOnlineProductPages, + ) + const resource = makeMitxOnlineResource() + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + + const expectedUrl = + resource.resource_type === ResourceTypeEnum.Course + ? coursePageView(resource.readable_id) + : programPageView(resource.readable_id) + expect(link).toHaveAttribute("href", expectedUrl) + expect(link.getAttribute("href")).not.toContain("utm_") + }) + + it("uses external URL with UTM params when feature flag is OFF for MITx Online course/program", () => { + mockUseFeatureFlagEnabled.mockReturnValue(false) + + const resource = makeMitxOnlineResource() + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + const href = link.getAttribute("href") + expect(href).toContain("mitxonline.mit.edu") + expect(href).toContain("utm_source=mit-learn") + expect(href).toContain("utm_medium=referral") + }) + + it("uses external URL with UTM params for non-MITx Online course even when flag is ON", () => { + mockUseFeatureFlagEnabled.mockImplementation( + (flag) => flag === FeatureFlags.MitxOnlineProductPages, + ) + + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + readable_id: "ocw-course", + platform: { code: PlatformEnum.Ocw }, + url: "https://ocw.mit.edu/course", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { + name: "Access Course Materials", + }) + const href = link.getAttribute("href") + expect(href).toContain("ocw.mit.edu") + expect(href).toContain("utm_source=mit-learn") + expect(href).toContain("utm_medium=referral") + expect(href).not.toContain("/courses/") + }) + }) + + describe("PostHog integration", () => { + it("calls posthog.capture when CTA link is clicked", () => { + const originalPostHogKey = process.env.NEXT_PUBLIC_POSTHOG_API_KEY + process.env.NEXT_PUBLIC_POSTHOG_API_KEY = "test-key" + + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + url: "https://example.com/course", + }) + + renderWithProviders( + , + ) + + const link = screen.getByRole("link", { name: "Learn More" }) + link.click() + + expect(mockCapture).toHaveBeenCalledWith("cta_clicked", { + resource, + }) + + // Restore original value + if (originalPostHogKey) { + process.env.NEXT_PUBLIC_POSTHOG_API_KEY = originalPostHogKey + } else { + delete process.env.NEXT_PUBLIC_POSTHOG_API_KEY + } + }) + }) +}) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.tsx b/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.tsx index af6c4d07b1..abd65e411a 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/CallToActionSection.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react" import styled from "@emotion/styled" import { default as NextImage } from "next/image" -import { usePostHog } from "posthog-js/react" +import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react" import { Skeleton, theme, @@ -34,7 +34,14 @@ import { FACEBOOK_SHARE_BASE_URL, TWITTER_SHARE_BASE_URL, LINKEDIN_SHARE_BASE_URL, + coursePageView, + programPageView, } from "@/common/urls" +import { FeatureFlags } from "@/common/feature_flags" +import invariant from "tiny-invariant" + +const NEXT_PUBLIC_ORIGIN = process.env.NEXT_PUBLIC_ORIGIN +invariant(NEXT_PUBLIC_ORIGIN, "NEXT_PUBLIC_ORIGIN must be defined") const showChatClass = "show-chat" const showChatSelector = `.${showChatClass} &` @@ -270,10 +277,14 @@ const getCallToActionText = (resource: LearningResource): string => { } } -const buildCtaUrl = (url?: string | null, resourceTitle?: string) => { +const appendUtmParams = (url?: string | null, resourceTitle?: string) => { if (!url) return "#" try { - const parsedUrl = new URL(url) + const parsedUrl = new URL(url, NEXT_PUBLIC_ORIGIN) + if (parsedUrl.href.startsWith(NEXT_PUBLIC_ORIGIN)) { + // Do not add UTM params for internal URLs + return url + } parsedUrl.searchParams.set("utm_source", "mit-learn") parsedUrl.searchParams.set("utm_medium", "referral") if (resourceTitle) { @@ -287,6 +298,23 @@ const buildCtaUrl = (url?: string | null, resourceTitle?: string) => { } } +const getResourceUrl = ( + resource: LearningResource, + { mitxonlineProductPages }: { mitxonlineProductPages?: boolean }, +) => { + if ( + mitxonlineProductPages && + resource.platform?.code === PlatformEnum.Mitxonline + ) { + if (resource.resource_type === ResourceTypeEnum.Course) { + return coursePageView(resource.readable_id) + } else if (resource.resource_type === ResourceTypeEnum.Program) { + return programPageView(resource.readable_id) + } + } + return resource.url +} + const CallToActionSection = ({ imgConfig, resource, @@ -311,6 +339,10 @@ const CallToActionSection = ({ const posthog = usePostHog() const [shareExpanded, setShareExpanded] = useState(false) const [copyText, setCopyText] = useState("Copy Link") + const mitxonlineProductPages = useFeatureFlagEnabled( + FeatureFlags.MitxOnlineProductPages, + ) + if (hide) { return null } @@ -323,8 +355,8 @@ const CallToActionSection = ({ ) } - const { platform } = resource! - const offeredBy = resource?.offered_by + const { platform } = resource + const offeredBy = resource.offered_by const platformCode = (offeredBy?.code as PlatformEnum) === PlatformEnum.Xpro ? (offeredBy?.code as PlatformEnum) @@ -335,6 +367,10 @@ const CallToActionSection = ({ const bookmarkLabel = "Bookmark" const shareLabel = "Share" const socialIconSize = 18 + const url = appendUtmParams( + getResourceUrl(resource, { mitxonlineProductPages }), + resource.title, + ) return ( @@ -344,7 +380,7 @@ const CallToActionSection = ({ target="_blank" size="medium" endIcon={} - href={buildCtaUrl(resource.url, resource.title)} + href={url} onClick={() => { if (process.env.NEXT_PUBLIC_POSTHOG_API_KEY) { posthog.capture(PostHogEvents.CallToActionClicked, { resource }) diff --git a/frontends/main/src/page-components/ResourceCard/ResourceCard.tsx b/frontends/main/src/page-components/ResourceCard/ResourceCard.tsx index da86987e7c..cbf0efb26d 100644 --- a/frontends/main/src/page-components/ResourceCard/ResourceCard.tsx +++ b/frontends/main/src/page-components/ResourceCard/ResourceCard.tsx @@ -18,7 +18,7 @@ import { useIsUserListMember } from "api/hooks/userLists" import { useLearningResourceDetailSetCache } from "api/hooks/learningResources" import { useIsLearningPathMember } from "api/hooks/learningPaths" -const useResourceCard = (resource?: LearningResource | null) => { +export const useResourceCard = (resource?: LearningResource | null) => { const getDrawerHref = useResourceDrawerHref() const { data: user } = useUserMe() const { data: inUserList } = useIsUserListMember(resource?.id) diff --git a/frontends/ol-components/src/components/TiptapEditor/ArticleContext.tsx b/frontends/main/src/page-components/TiptapEditor/ArticleContext.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/ArticleContext.tsx rename to frontends/main/src/page-components/TiptapEditor/ArticleContext.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/ArticleEditor.tsx b/frontends/main/src/page-components/TiptapEditor/ArticleEditor.tsx similarity index 98% rename from frontends/ol-components/src/components/TiptapEditor/ArticleEditor.tsx rename to frontends/main/src/page-components/TiptapEditor/ArticleEditor.tsx index 7bb986a4e6..b3135f3f40 100644 --- a/frontends/ol-components/src/components/TiptapEditor/ArticleEditor.tsx +++ b/frontends/main/src/page-components/TiptapEditor/ArticleEditor.tsx @@ -4,7 +4,12 @@ import React, { ChangeEventHandler, useState, useEffect } from "react" import styled from "@emotion/styled" import { EditorContext, JSONContent, useEditor } from "@tiptap/react" import type { RichTextArticle } from "api/v1" -import { LoadingSpinner } from "../LoadingSpinner/LoadingSpinner" +import { + LoadingSpinner, + Typography, + HEADER_HEIGHT, + HEADER_HEIGHT_MD, +} from "ol-components" import Document from "@tiptap/extension-document" import { Placeholder, Selection } from "@tiptap/extensions" import type { Node as ProseMirrorNode } from "@tiptap/pm/model" @@ -55,16 +60,18 @@ import { useMediaUpload, } from "api/hooks/articles" import { Alert, Button, ButtonLink } from "@mitodl/smoot-design" -import Typography from "@mui/material/Typography" import { useUserHasPermission, Permission } from "api/hooks/user" +import dynamic from "next/dynamic" import { BannerNode } from "./extensions/node/Banner/BannerNode" -import { - HEADER_HEIGHT, - HEADER_HEIGHT_MD, -} from "../../components/ThemeProvider/MITLearnGlobalStyles" import { extractLearningResourceIds } from "./extensions/utils" import { LearningResourceProvider } from "./extensions/node/LearningResource/LearningResourceDataProvider" +const LearningResourceDrawer = dynamic( + () => + import("@/page-components/LearningResourceDrawer/LearningResourceDrawer"), + { ssr: false }, +) + const TOOLBAR_HEIGHT = 43 const ViewContainer = styled.div<{ toolbarVisible: boolean }>( @@ -456,7 +463,7 @@ const ArticleEditor = ({ onSave, readOnly, article }: ArticleEditorProps) => { ) : null} - + diff --git a/frontends/ol-components/src/components/TiptapEditor/TiptapEditor.styles.scss b/frontends/main/src/page-components/TiptapEditor/TiptapEditor.styles.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/TiptapEditor.styles.scss rename to frontends/main/src/page-components/TiptapEditor/TiptapEditor.styles.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/TiptapEditor.tsx b/frontends/main/src/page-components/TiptapEditor/TiptapEditor.tsx similarity index 99% rename from frontends/ol-components/src/components/TiptapEditor/TiptapEditor.tsx rename to frontends/main/src/page-components/TiptapEditor/TiptapEditor.tsx index 81fd09bcab..504f349369 100644 --- a/frontends/ol-components/src/components/TiptapEditor/TiptapEditor.tsx +++ b/frontends/main/src/page-components/TiptapEditor/TiptapEditor.tsx @@ -9,7 +9,7 @@ import { EditorContent } from "@tiptap/react" import type { Editor } from "@tiptap/core" import { ImageUploadButton } from "./vendor/components/tiptap-ui/image-upload-button" import { MediaEmbedButton } from "./extensions/ui/MediaEmbed/MediaEmbedButton" -import { pxToRem } from "../ThemeProvider/typography" +import { pxToRem } from "ol-components" import { Spacer } from "./vendor/components/tiptap-ui-primitive/spacer" import { diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx similarity index 97% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx index 9d7a0a4d66..6152249ae6 100644 --- a/frontends/ol-components/src/components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx +++ b/frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBar.tsx @@ -2,9 +2,8 @@ import React from "react" import { NodeViewWrapper } from "@tiptap/react" import type { ReactNodeViewProps } from "@tiptap/react" import styled from "@emotion/styled" -import Container from "@mui/material/Container" +import { Container, Avatar } from "ol-components" import { RiShareFill } from "@remixicon/react" -import Avatar from "@mui/material/Avatar" import { ActionButton } from "@mitodl/smoot-design" import { useUserMe } from "api/hooks/user" import { useArticle } from "../../../ArticleContext" diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarNode.ts b/frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarNode.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarNode.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/node/ArticleByLineInfoBar/ArticleByLineInfoBarNode.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Banner/BannerNode.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Banner/BannerNode.tsx similarity index 95% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Banner/BannerNode.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Banner/BannerNode.tsx index 94af877686..301a482b56 100644 --- a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Banner/BannerNode.tsx +++ b/frontends/main/src/page-components/TiptapEditor/extensions/node/Banner/BannerNode.tsx @@ -7,8 +7,7 @@ import { NodeViewContent, } from "@tiptap/react" import type { Node as ProseMirrorNode } from "@tiptap/pm/model" -import { BannerBackground } from "../../../../Banner/Banner" -import Container from "@mui/material/Container" +import { Container, BannerBackground } from "ol-components" import styled from "@emotion/styled" import type { ExtendedNodeConfig } from "../types" diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Divider/DividerNode.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Divider/DividerNode.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Divider/DividerNode.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Divider/DividerNode.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/Icons.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/Icons.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/Icons.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Image/Icons.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageAltTextInput.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageAltTextInput.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageAltTextInput.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageAltTextInput.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageNode.ts b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageNode.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageNode.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageNode.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageUpload.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageUpload.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageUpload.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageUpload.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx similarity index 74% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx index 690fb5156f..94a17f932c 100644 --- a/frontends/ol-components/src/components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx +++ b/frontends/main/src/page-components/TiptapEditor/extensions/node/Image/ImageWithCaption.tsx @@ -29,6 +29,17 @@ const Container = styled.figure(({ theme }) => ({ margin: "0 auto", }, + ".image-wrapper": { + position: "relative", + margin: "0 auto", + maxWidth: "100%", + }, + + "&.layout-default .image-wrapper": { + width: "auto", + display: "inline-block", + }, + [`@media (min-width: ${ARTICLE_MAX_WIDTH + CONTAINER_PADDING * 2}px)`]: { "&.layout-wide img": { width: "92vw", @@ -37,6 +48,21 @@ const Container = styled.figure(({ theme }) => ({ left: "50%", transform: "translateX(-50%)", }, + + "&.layout-wide .image-wrapper": { + width: "92vw", + maxWidth: "1400px", + position: "relative", + left: "50%", + transform: "translateX(-50%)", + }, + }, + "&.layout-full .image-wrapper": { + width: "100vw", + maxWidth: "100vw", + position: "relative", + left: "50%", + transform: "translateX(-50%)", }, "&.layout-full img": { @@ -146,8 +172,31 @@ const Image = styled.img<{ layout: Layout }>(({ layout }) => ({ "&&": { borderRadius: layout === Layout.full ? 0 : "8px", }, + "& .remove-button": { + opacity: 0, + pointerEvents: "none", + }, + + "&:hover .remove-button": { + opacity: 1, + pointerEvents: "auto", + }, })) +const ImageWrapper = styled("div")({ + position: "relative", + + "& .remove-button": { + opacity: 0, + pointerEvents: "none", + }, + + "&:hover .remove-button": { + opacity: 1, + pointerEvents: "auto", + }, +}) + const Caption = styled.figcaption(({ theme }) => ({ "&&&&&": { ...theme.typography.body2, @@ -159,8 +208,35 @@ const Caption = styled.figcaption(({ theme }) => ({ }, })) +const RemoveButton = styled("button")(({ theme }) => ({ + position: "absolute", + top: -7, + right: -7, + zIndex: 999999, + + background: theme.custom.colors.white, + border: `1px solid ${theme.custom.colors.lightGray2}`, + borderRadius: "50%", + width: 24, + height: 24, + + cursor: "pointer", + fontSize: 14, + lineHeight: 1, + + opacity: 0, // 👈 hidden + pointerEvents: "none", // 👈 not clickable when hidden + transition: "opacity 0.15s ease", + + "&:hover": { + background: theme.custom.colors.lightGray1, + }, +})) + export function ImageWithCaption({ node, + editor, + getPos, updateAttributes, }: ReactNodeViewProps) { const imgRef = useRef(null) @@ -199,6 +275,13 @@ export function ImageWithCaption({ } } + const handleRemove = () => { + const pos = getPos() + if (typeof pos !== "number") return + + editor.chain().focus().setNodeSelection(pos).deleteSelection().run() + } + return ( {isLoading && } @@ -241,14 +324,26 @@ export function ImageWithCaption({ )} - {alt setIsLoading(false)} - onError={() => setIsLoading(false)} - /> + + {isEditable && ( + + × + + )} + {alt setIsLoading(false)} + onError={() => setIsLoading(false)} + /> + {isEditable ? ( { @@ -27,29 +28,28 @@ const NodeWrapper = styled(NodeViewWrapper)({ pointerEvents: "auto", }, }) -const StyledLearningResourceListCard = styled(LearningResourceListCard)( - ({ theme }) => ({ - position: "relative", - - ".ProseMirror-selectednode &": { - borderColor: theme.custom.colors.red, - userSelect: "none", - }, - - "&& a": { - color: "inherit", - textDecoration: "none", - }, - - "&& a span": { - textDecoration: "none", - }, - "&:hover .remove-button": { - opacity: 1, - pointerEvents: "auto", - }, - }), -) + +const StyledLearningResourceListCard = styled(ResourceCard)(({ theme }) => ({ + position: "relative", + + ".ProseMirror-selectednode &": { + borderColor: theme.custom.colors.red, + userSelect: "none", + }, + + "&& a": { + color: "inherit", + textDecoration: "none", + }, + + "&& a span": { + textDecoration: "none", + }, + "&:hover .remove-button": { + opacity: 1, + pointerEvents: "auto", + }, +})) const RemoveButton = styled("button")(({ theme }) => ({ position: "absolute", @@ -82,16 +82,22 @@ export const LearningResourceListCardWrapper = ({ getPos, }: ReactNodeViewProps) => { const resourceId = node.attrs.resourceId - const href = node.attrs.href const editable = node.attrs.editable const { resource: data, isLoading } = useLearningResource(resourceId) const handleRemove = () => { const pos = getPos() - if (typeof pos !== "number") return - - editor.chain().focus().setNodeSelection(pos).deleteSelection().run() + if (typeof getPos !== "function" || typeof pos !== "number") return + + editor + .chain() + .focus() + .command(({ tr }) => { + tr.delete(pos, pos + node.nodeSize) + return true + }) + .run() } return ( @@ -106,11 +112,10 @@ export const LearningResourceListCardWrapper = ({ × )} - ) diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/LearningResource/LearningResourcePaste.ts b/frontends/main/src/page-components/TiptapEditor/extensions/node/LearningResource/LearningResourcePaste.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/LearningResource/LearningResourcePaste.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/node/LearningResource/LearningResourcePaste.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/Icons.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/Icons.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/Icons.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/Icons.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNode.ts b/frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNode.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNode.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNode.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx similarity index 77% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx index 82f75e8511..b74d7627b1 100644 --- a/frontends/ol-components/src/components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx +++ b/frontends/main/src/page-components/TiptapEditor/extensions/node/MediaEmbed/MediaEmbedNodeView.tsx @@ -109,21 +109,67 @@ const StyledNodeViewWrapper = styled(NodeViewWrapper)<{ .svg-icon { fill: white; } + + .remove-button { + opacity: 0; + pointer-events: none; + } + + &:hover .remove-button { + opacity: 1; + pointer-events: auto; + } ` +const RemoveButton = styled("button")(({ theme }) => ({ + position: "absolute", + top: -7, + right: -7, + zIndex: 2, + + background: theme.custom.colors.white, + border: `1px solid ${theme.custom.colors.lightGray2}`, + borderRadius: "50%", + width: 24, + height: 24, + + cursor: "pointer", + fontSize: 14, + lineHeight: 1, + + opacity: 0, // 👈 hidden + pointerEvents: "none", // 👈 not clickable when hidden + transition: "opacity 0.15s ease", + + "&:hover": { + background: theme.custom.colors.lightGray1, + }, +})) + interface MediaEmbedNodeProps { node: NodeViewProps["node"] + editor: NodeViewProps["editor"] + getPos: NodeViewProps["getPos"] updateAttributes: (attrs: Record) => void } export const MediaEmbedNodeView = ({ node, + editor, + getPos, updateAttributes, }: MediaEmbedNodeProps) => { const [hovering, setHovering] = useState(false) const { layout, caption, src, editable } = node.attrs + const handleRemove = () => { + const pos = getPos() + if (typeof pos !== "number") return + + editor.chain().focus().setNodeSelection(pos).deleteSelection().run() + } + return ( setHovering(true)} onMouseLeave={() => setHovering(false)} > + {editable && ( + + × + + )} {/* Toolbar — identical to ImageUpload version */} {editable && hovering && (
diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/node/types.ts b/frontends/main/src/page-components/TiptapEditor/extensions/node/types.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/node/types.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/node/types.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/Divider/DividerButton.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/ui/Divider/DividerButton.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/Divider/DividerButton.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/Divider/DividerButton.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/Icon.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/Icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/Icon.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/Icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/MediaEmbedButton.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/MediaEmbedButton.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/MediaEmbedButton.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/MediaEmbedButton.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/MediaUrlInputDialog.tsx b/frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/MediaUrlInputDialog.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/MediaUrlInputDialog.tsx rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/MediaUrlInputDialog.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/lib.ts b/frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/lib.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/lib.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/lib.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/useMediaEmbed.ts b/frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/useMediaEmbed.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/ui/MediaEmbed/useMediaEmbed.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/ui/MediaEmbed/useMediaEmbed.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/extensions/utils.ts b/frontends/main/src/page-components/TiptapEditor/extensions/utils.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/extensions/utils.ts rename to frontends/main/src/page-components/TiptapEditor/extensions/utils.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/index.ts b/frontends/main/src/page-components/TiptapEditor/index.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/index.ts rename to frontends/main/src/page-components/TiptapEditor/index.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/README.md b/frontends/main/src/page-components/TiptapEditor/vendor/README.md similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/README.md rename to frontends/main/src/page-components/TiptapEditor/vendor/README.md diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-center-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-center-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-center-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-center-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-justify-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-justify-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-justify-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-justify-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-left-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-left-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-left-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-left-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-right-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-right-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/align-right-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/align-right-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/arrow-left-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/arrow-left-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/arrow-left-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/arrow-left-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/ban-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/ban-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/ban-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/ban-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/blockquote-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/blockquote-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/blockquote-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/blockquote-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/bold-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/bold-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/bold-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/bold-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/chevron-down-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/chevron-down-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/chevron-down-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/chevron-down-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/close-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/close-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/close-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/close-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/code-block-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/code-block-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/code-block-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/code-block-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/code2-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/code2-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/code2-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/code2-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/corner-down-left-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/corner-down-left-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/corner-down-left-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/corner-down-left-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/external-link-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/external-link-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/external-link-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/external-link-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-five-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-five-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-five-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-five-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-four-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-four-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-four-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-four-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-one-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-one-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-one-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-one-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-six-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-six-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-six-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-six-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-three-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-three-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-three-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-three-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-two-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-two-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/heading-two-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/heading-two-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/highlighter-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/highlighter-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/highlighter-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/highlighter-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/image-plus-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/image-plus-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/image-plus-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/image-plus-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/italic-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/italic-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/italic-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/italic-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/link-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/link-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/link-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/link-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-ordered-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-ordered-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-ordered-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-ordered-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-todo-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-todo-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/list-todo-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/list-todo-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/moon-star-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/moon-star-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/moon-star-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/moon-star-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/redo2-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/redo2-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/redo2-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/redo2-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/strike-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/strike-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/strike-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/strike-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/subscript-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/subscript-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/subscript-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/subscript-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/sun-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/sun-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/sun-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/sun-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/superscript-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/superscript-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/superscript-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/superscript-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/trash-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/trash-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/trash-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/trash-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/underline-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/underline-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/underline-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/underline-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/undo2-icon.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/undo2-icon.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-icons/undo2-icon.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-icons/undo2-icon.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/blockquote-node/blockquote-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/blockquote-node/blockquote-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/blockquote-node/blockquote-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/blockquote-node/blockquote-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/code-block-node/code-block-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/code-block-node/code-block-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/code-block-node/code-block-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/code-block-node/code-block-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/heading-node/heading-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/heading-node/heading-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/heading-node/heading-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/heading-node/heading-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node-extension.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/horizontal-rule-node/horizontal-rule-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/image-node/image-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/image-node/image-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/image-node/image-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/image-node/image-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/list-node/list-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/list-node/list-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/list-node/list-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/list-node/list-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/paragraph-node/paragraph-node.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/paragraph-node/paragraph-node.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-node/paragraph-node/paragraph-node.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-node/paragraph-node/paragraph-node.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/data/content.json b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/data/content.json similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/data/content.json rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/data/content.json diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/simple-editor.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/simple-editor.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/simple-editor.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/simple-editor.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/theme-toggle.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/theme-toggle.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-templates/simple/theme-toggle.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-templates/simple/theme-toggle.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-colors.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-colors.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-colors.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-colors.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-group.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-group.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-group.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge-group.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/badge.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/badge/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-colors.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-colors.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-colors.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-colors.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-group.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-group.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-group.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button-group.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/card.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/card/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/dropdown-menu.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/dropdown-menu/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/input/input.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/popover/popover.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/separator/separator.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/spacer.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/spacer.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/spacer.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/spacer/spacer.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/toolbar/toolbar.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui-primitive/tooltip/tooltip.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/blockquote-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/blockquote-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/blockquote-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/blockquote-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/use-blockquote.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/use-blockquote.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/use-blockquote.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/blockquote-button/use-blockquote.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/code-block-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/code-block-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/code-block-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/code-block-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/use-code-block.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/use-code-block.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/use-code-block.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/code-block-button/use-code-block.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.scss b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/color-highlight-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/use-color-highlight.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/use-color-highlight.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/use-color-highlight.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-button/use-color-highlight.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/color-highlight-popover.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/color-highlight-popover/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/heading-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/heading-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/heading-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/heading-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/use-heading.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/use-heading.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-button/use-heading.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-button/use-heading.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/heading-dropdown-menu/use-heading-dropdown-menu.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/image-upload-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/image-upload-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/image-upload-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/image-upload-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/use-image-upload.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/use-image-upload.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/use-image-upload.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/image-upload-button/use-image-upload.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/link-popover.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/link-popover.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/link-popover.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/link-popover.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/use-link-popover.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/use-link-popover.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/link-popover/use-link-popover.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/link-popover/use-link-popover.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/list-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/list-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/list-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/list-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/use-list.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/use-list.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-button/use-list.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-button/use-list.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/list-dropdown-menu/use-list-dropdown-menu.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/mark-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/mark-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/mark-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/mark-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/use-mark.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/use-mark.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/mark-button/use-mark.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/mark-button/use-mark.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/text-align-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/text-align-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/text-align-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/text-align-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/use-text-align.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/use-text-align.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/use-text-align.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/text-align-button/use-text-align.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/index.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/index.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/index.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/index.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/undo-redo-button.tsx diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/use-undo-redo.ts b/frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/use-undo-redo.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/use-undo-redo.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/components/tiptap-ui/undo-redo-button/use-undo-redo.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-composed-ref.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-composed-ref.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-composed-ref.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-composed-ref.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-cursor-visibility.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-cursor-visibility.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-cursor-visibility.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-cursor-visibility.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-element-rect.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-element-rect.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-element-rect.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-element-rect.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-menu-navigation.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-menu-navigation.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-menu-navigation.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-menu-navigation.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-mobile.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-mobile.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-mobile.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-mobile.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-scrolling.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-scrolling.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-scrolling.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-scrolling.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-throttled-callback.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-throttled-callback.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-throttled-callback.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-throttled-callback.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-tiptap-editor.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-tiptap-editor.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-tiptap-editor.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-tiptap-editor.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-unmount.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-unmount.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-unmount.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-unmount.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-window-size.ts b/frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-window-size.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/hooks/use-window-size.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/hooks/use-window-size.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/lib/tiptap-utils.ts b/frontends/main/src/page-components/TiptapEditor/vendor/lib/tiptap-utils.ts similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/lib/tiptap-utils.ts rename to frontends/main/src/page-components/TiptapEditor/vendor/lib/tiptap-utils.ts diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/styles/_keyframe-animations.scss b/frontends/main/src/page-components/TiptapEditor/vendor/styles/_keyframe-animations.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/styles/_keyframe-animations.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/styles/_keyframe-animations.scss diff --git a/frontends/ol-components/src/components/TiptapEditor/vendor/styles/_variables.scss b/frontends/main/src/page-components/TiptapEditor/vendor/styles/_variables.scss similarity index 100% rename from frontends/ol-components/src/components/TiptapEditor/vendor/styles/_variables.scss rename to frontends/main/src/page-components/TiptapEditor/vendor/styles/_variables.scss diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index 13e34dd414..4d972a1d0b 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -31,22 +31,6 @@ "@remixicon/react": "^4.2.0", "@tanstack/react-query": "^5.66.0", "@testing-library/dom": "^10.4.0", - "@tiptap/core": "^3.13.0", - "@tiptap/extension-document": "^3.13.0", - "@tiptap/extension-heading": "^3.13.0", - "@tiptap/extension-highlight": "^3.13.0", - "@tiptap/extension-horizontal-rule": "^3.13.0", - "@tiptap/extension-image": "^3.13.0", - "@tiptap/extension-link": "^3.13.0", - "@tiptap/extension-list": "^3.13.0", - "@tiptap/extension-subscript": "^3.13.0", - "@tiptap/extension-superscript": "^3.13.0", - "@tiptap/extension-text-align": "^3.13.0", - "@tiptap/extension-typography": "^3.13.0", - "@tiptap/extensions": "^3.13.0", - "@tiptap/pm": "^3.13.0", - "@tiptap/react": "^3.13.0", - "@tiptap/starter-kit": "^3.13.0", "@types/react-dom": "^19.2.3", "@types/tinycolor2": "^1.4.6", "api": "workspace:*", diff --git a/frontends/ol-components/src/index.ts b/frontends/ol-components/src/index.ts index ef27b4aa64..b1d1846d53 100644 --- a/frontends/ol-components/src/index.ts +++ b/frontends/ol-components/src/index.ts @@ -84,6 +84,8 @@ export type { TabPanelProps } from "@mui/lab/TabPanel" export { default as Toolbar } from "@mui/material/Toolbar" export type { ToolbarProps } from "@mui/material/Toolbar" +export { default as Avatar } from "@mui/material/Avatar" + // Mui Form Inputs export { default as Autocomplete } from "@mui/material/Autocomplete" export type { AutocompleteProps } from "@mui/material/Autocomplete" @@ -168,11 +170,13 @@ export type { LinkProps } from "./components/Link/Link" export { pxToRem } from "./components/ThemeProvider/typography" export * from "./components/ThemeProvider/MITLearnGlobalStyles" +export { + HEADER_HEIGHT, + HEADER_HEIGHT_MD, +} from "./components/ThemeProvider/MITLearnGlobalStyles" export { AppRouterCacheProvider as NextJsAppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter" -export * from "./components/TiptapEditor" - // /** // * @deprecated Please use component from @mitodl/smoot-design instead // */ diff --git a/learning_resources/filters.py b/learning_resources/filters.py index 64d62cad22..4ed025140c 100644 --- a/learning_resources/filters.py +++ b/learning_resources/filters.py @@ -92,6 +92,11 @@ class LearningResourceFilter(FilterSet): field_name="readable_id", ) + resource_id = NumberInFilter( + label="Comma-separated list of learning resource IDs", + field_name="id", + ) + sortby = ChoiceFilter( label="Sort By", method="filter_sortby", diff --git a/learning_resources/views.py b/learning_resources/views.py index 56e2687820..2df588382c 100644 --- a/learning_resources/views.py +++ b/learning_resources/views.py @@ -334,64 +334,6 @@ def summary(self, request, **kwargs): # noqa: ARG002 serializer = LearningResourceSummarySerializer(page, many=True) return self.get_paginated_response(serializer.data) - @extend_schema( - summary="Retrieve multiple learning resources by IDs", - description="Fetch multiple learning resources in a single request.", - parameters=[ - OpenApiParameter( - name="ids", - type=OpenApiTypes.STR, - location=OpenApiParameter.QUERY, - description="Comma-separated list of IDs (e.g. 1,2,3)", - required=True, - ) - ], - responses=LearningResourceSerializer(many=True), - ) - @action( - detail=False, - methods=["GET"], - url_path="bulk", - pagination_class=None, - ) - @method_decorator( - cache_page_for_all_users( - settings.REDIS_VIEW_CACHE_DURATION, - cache="redis", - key_prefix="learning_resources_bulk", - ) - ) - def bulk(self, request, *args, **kwargs): # noqa: ARG002 - """ - Retrieve multiple learning resources by IDs. - - Example: - GET /api/v1/learning_resources/bulk/?ids=1,2,3 - """ - ids_param = request.query_params.get("ids") - if not ids_param: - return Response( - {"detail": "`ids` query parameter is required."}, - status=400, - ) - - try: - ids = [int(i) for i in ids_param.split(",") if i.strip()] - except ValueError: - return Response( - {"detail": "`ids` must be a comma-separated list of integers."}, - status=400, - ) - - queryset = self.get_queryset().filter(id__in=ids) - - # Preserve client order - resources_by_id = {r.id: r for r in queryset} - ordered_resources = [resources_by_id[i] for i in ids if i in resources_by_id] - - serializer = self.get_serializer(ordered_resources, many=True) - return Response(serializer.data) - @extend_schema_view( list=extend_schema( diff --git a/main/settings.py b/main/settings.py index f46bbb974b..3aa7236ff8 100644 --- a/main/settings.py +++ b/main/settings.py @@ -34,7 +34,7 @@ from main.settings_pluggy import * # noqa: F403 from openapi.settings_spectacular import open_spectacular_settings -VERSION = "0.51.0" +VERSION = "0.51.1" log = logging.getLogger() @@ -821,6 +821,15 @@ def get_all_config_keys(): QDRANT_ENCODER = get_string( name="QDRANT_ENCODER", default="vector_search.encoders.fastembed.FastEmbedEncoder" ) + +QDRANT_POINT_UPLOAD_BATCH_SIZE = get_int( + name="QDRANT_POINT_UPLOAD_BATCH_SIZE", default=1000 +) + +QDRANT_BATCH_SIZE_BYTES = get_int( + name="QDRANT_BATCH_SIZE_BYTES", default=10 * 1024 * 1024 +) # default 10 MB limit for batch processing + # toggle to use requests (default for local) or webdriver which renders js elements EMBEDDINGS_EXTERNAL_FETCH_USE_WEBDRIVER = get_bool( "EMBEDDINGS_EXTERNAL_FETCH_USE_WEBDRIVER", default=False diff --git a/openapi/specs/v1.yaml b/openapi/specs/v1.yaml index 9e0d94f1d2..0411ed88b3 100644 --- a/openapi/specs/v1.yaml +++ b/openapi/specs/v1.yaml @@ -884,6 +884,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -1522,6 +1531,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -1936,6 +1954,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -2350,6 +2377,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -2768,6 +2804,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -3167,6 +3212,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -3576,395 +3630,6 @@ paths: items: $ref: '#/components/schemas/UserListRelationship' description: '' - /api/v1/learning_resources/bulk/: - get: - operationId: learning_resources_bulk_list - description: Fetch multiple learning resources in a single request. - summary: Retrieve multiple learning resources by IDs - parameters: - - in: query - name: certification - schema: - type: boolean - - in: query - name: certification_type - schema: - type: array - items: - type: string - enum: - - completion - - micromasters - - none - - professional - description: |- - The type of certification offered - - * `micromasters` - MicroMasters Credential - * `professional` - Professional Certificate - * `completion` - Certificate of Completion - * `none` - No Certificate - explode: true - style: form - - in: query - name: course_feature - schema: - type: array - items: - type: string - description: Content feature for the resources. Load the 'api/v1/course_features' - endpoint for a list of course features - explode: true - style: form - - in: query - name: delivery - schema: - type: array - items: - type: array - items: - enum: - - online - - hybrid - - in_person - - offline - type: string - description: |- - * `online` - Online - * `hybrid` - Hybrid - * `in_person` - In person - * `offline` - Offline - enum: - - hybrid - - in_person - - offline - - online - description: |- - The delivery of course/program resources - - * `online` - Online - * `hybrid` - Hybrid - * `in_person` - In person - * `offline` - Offline - explode: true - style: form - - in: query - name: department - schema: - type: array - items: - type: string - enum: - - '1' - - '10' - - '11' - - '12' - - '14' - - '15' - - '16' - - '17' - - '18' - - '2' - - '20' - - 21A - - 21G - - 21H - - 21L - - 21M - - '22' - - '24' - - '3' - - '4' - - '5' - - '6' - - '7' - - '8' - - '9' - - CC - - CMS-W - - EC - - ES - - ESD - - HST - - IDS - - MAS - - PE - - SP - - STS - - WGS - description: |- - The department that offers learning resources - - * `1` - Civil and Environmental Engineering - * `2` - Mechanical Engineering - * `3` - Materials Science and Engineering - * `4` - Architecture - * `5` - Chemistry - * `6` - Electrical Engineering and Computer Science - * `7` - Biology - * `8` - Physics - * `9` - Brain and Cognitive Sciences - * `10` - Chemical Engineering - * `11` - Urban Studies and Planning - * `12` - Earth, Atmospheric, and Planetary Sciences - * `14` - Economics - * `15` - Management - * `16` - Aeronautics and Astronautics - * `17` - Political Science - * `18` - Mathematics - * `20` - Biological Engineering - * `21A` - Anthropology - * `21G` - Global Languages - * `21H` - History - * `21L` - Literature - * `21M` - Music and Theater Arts - * `22` - Nuclear Science and Engineering - * `24` - Linguistics and Philosophy - * `CC` - Concourse - * `CMS-W` - Comparative Media Studies/Writing - * `EC` - Edgerton Center - * `ES` - Experimental Study Group - * `ESD` - Engineering Systems Division - * `HST` - Medical Engineering and Science - * `IDS` - Data, Systems, and Society - * `MAS` - Media Arts and Sciences - * `PE` - Athletics, Physical Education and Recreation - * `SP` - Special Programs - * `STS` - Science, Technology, and Society - * `WGS` - Women's and Gender Studies - explode: true - style: form - - in: query - name: free - schema: - type: boolean - description: The course/program is offered for free - - in: query - name: ids - schema: - type: string - description: Comma-separated list of IDs (e.g. 1,2,3) - required: true - - in: query - name: level - schema: - type: array - items: - type: string - enum: - - advanced - - graduate - - high_school - - intermediate - - introductory - - noncredit - - undergraduate - description: |- - The academic level of the resources - - * `undergraduate` - Undergraduate - * `graduate` - Graduate - * `high_school` - High School - * `noncredit` - Non-Credit - * `advanced` - Advanced - * `intermediate` - Intermediate - * `introductory` - Introductory - explode: true - style: form - - in: query - name: offered_by - schema: - type: array - items: - type: string - enum: - - bootcamps - - climate - - mitpe - - mitx - - ocw - - see - - xpro - description: |- - The organization that offers a learning resource - - * `mitx` - MITx - * `ocw` - MIT OpenCourseWare - * `bootcamps` - Bootcamps - * `xpro` - MIT xPRO - * `mitpe` - MIT Professional Education - * `see` - MIT Sloan Executive Education - * `climate` - MIT Climate - explode: true - style: form - - in: query - name: platform - schema: - type: array - items: - type: string - enum: - - bootcamps - - canvas - - climate - - csail - - ctl - - edx - - emeritus - - globalalumni - - mitpe - - mitxonline - - ocw - - oll - - podcast - - scc - - see - - simplilearn - - susskind - - whu - - xpro - - youtube - description: |- - The platform on which learning resources are offered - - * `edx` - edX - * `ocw` - MIT OpenCourseWare - * `oll` - Open Learning Library - * `mitxonline` - MITx Online - * `bootcamps` - Bootcamps - * `xpro` - MIT xPRO - * `csail` - CSAIL - * `mitpe` - MIT Professional Education - * `see` - MIT Sloan Executive Education - * `scc` - Schwarzman College of Computing - * `ctl` - Center for Transportation & Logistics - * `whu` - WHU - * `susskind` - Susskind - * `globalalumni` - Global Alumni - * `simplilearn` - Simplilearn - * `emeritus` - Emeritus - * `podcast` - Podcast - * `youtube` - YouTube - * `canvas` - Canvas - * `climate` - MIT Climate - explode: true - style: form - - in: query - name: professional - schema: - type: boolean - - in: query - name: readable_id - schema: - type: array - items: - type: string - description: A unique text identifier for the resources - explode: true - style: form - - in: query - name: resource_category - schema: - type: array - items: - type: string - enum: - - course - - learning_material - - program - description: |- - The resource category of the learning resources - - * `course` - Course - * `program` - Program - * `learning_material` - Learning Material - explode: true - style: form - - in: query - name: resource_type - schema: - type: array - items: - type: string - enum: - - article - - course - - learning_path - - podcast - - podcast_episode - - program - - video - - video_playlist - description: |- - The type of learning resource - - * `course` - Course - * `program` - Program - * `learning_path` - Learning Path - * `podcast` - Podcast - * `podcast_episode` - Podcast Episode - * `video` - Video - * `video_playlist` - Video Playlist - * `article` - Article - explode: true - style: form - - in: query - name: sortby - schema: - type: string - enum: - - -id - - -last_modified - - -mitcoursenumber - - -readable_id - - -start_date - - -views - - id - - last_modified - - mitcoursenumber - - new - - readable_id - - start_date - - upcoming - - views - description: |- - Sort By - - * `id` - Object ID ascending - * `-id` - Object ID descending - * `readable_id` - Readable ID ascending - * `-readable_id` - Readable ID descending - * `last_modified` - Last Modified Date ascending - * `-last_modified` - Last Modified Date descending - * `new` - Newest resources first - * `start_date` - Start Date ascending - * `-start_date` - Start Date descending - * `mitcoursenumber` - MIT course number ascending - * `-mitcoursenumber` - MIT course number descending - * `views` - Popularity ascending - * `-views` - Popularity descending - * `upcoming` - Next start date ascending - - in: query - name: topic - schema: - type: array - items: - type: string - description: Topics covered by the resources. Load the '/api/v1/topics' endpoint - for a list of topics - explode: true - style: form - tags: - - learning_resources - responses: - '200': - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/LearningResource' - description: '' /api/v1/learning_resources/summary/: get: operationId: learning_resources_summary_list @@ -4275,6 +3940,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -6762,6 +6436,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -7516,6 +7199,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -7930,6 +7622,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -8431,6 +8132,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -9277,6 +8987,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: @@ -9757,6 +9476,15 @@ paths: * `learning_material` - Learning Material explode: true style: form + - in: query + name: resource_id + schema: + type: array + items: + type: integer + description: Comma-separated list of learning resource IDs + explode: true + style: form - in: query name: resource_type schema: diff --git a/vector_search/conftest.py b/vector_search/conftest.py index 0148a4c1d9..cdc757259d 100644 --- a/vector_search/conftest.py +++ b/vector_search/conftest.py @@ -1,6 +1,5 @@ import numpy as np import pytest -from langchain.text_splitter import RecursiveCharacterTextSplitter from qdrant_client.http.models.models import CountResult from vector_search.encoders.base import BaseEncoder @@ -18,7 +17,7 @@ def embed(self, text: str) -> list: # noqa: ARG002 return np.random.random((10, 1)) def embed_documents(self, texts: list[str]) -> list[list[float]]: - return np.random.random((10, len(texts))) + return [self.embed(text) for text in texts] @pytest.fixture(autouse=True) @@ -41,29 +40,6 @@ def _use_test_qdrant_settings(settings, mocker): [], None, ] - get_text_splitter_patch = mocker.patch("vector_search.utils._chunk_documents") - get_text_splitter_patch.return_value = ( - RecursiveCharacterTextSplitter().create_documents( - texts=["test dociment"], - metadatas=[ - { - "run_title": "", - "platform": "", - "offered_by": "", - "run_readable_id": "", - "resource_readable_id": "", - "content_type": "", - "file_extension": "", - "content_feature_type": "", - "course_number": "", - "file_type": "", - "description": "", - "key": "", - "url": "", - } - ], - ) - ) mock_qdrant.count.return_value = CountResult(count=10) mocker.patch( "vector_search.utils.qdrant_client", diff --git a/vector_search/tasks.py b/vector_search/tasks.py index 67f909082b..e89e757f5c 100644 --- a/vector_search/tasks.py +++ b/vector_search/tasks.py @@ -47,7 +47,7 @@ reject_on_worker_lost=True, autoretry_for=(RetryError,), retry_backoff=True, - rate_limit="600/m", + rate_limit="300/m", ) def generate_embeddings(ids, resource_type, overwrite): """ diff --git a/vector_search/utils.py b/vector_search/utils.py index 1e165ef26e..b71c98d251 100644 --- a/vector_search/utils.py +++ b/vector_search/utils.py @@ -1,5 +1,7 @@ +import gc import logging import uuid +from functools import cache from django.conf import settings from django.db.models import Q @@ -43,6 +45,7 @@ logger = logging.getLogger(__name__) +@cache def qdrant_client(): return QdrantClient( url=settings.QDRANT_HOST, @@ -231,6 +234,14 @@ def embed_topics(): client.upload_points(TOPICS_COLLECTION_NAME, points=points, wait=False) +@cache +def _get_text_splitter(**kwargs): + if settings.LITELLM_TOKEN_ENCODING_NAME: + kwargs["encoding_name"] = settings.LITELLM_TOKEN_ENCODING_NAME + return RecursiveCharacterTextSplitter.from_tiktoken_encoder(**kwargs) + return RecursiveCharacterTextSplitter(**kwargs) + + def _chunk_documents(encoder, texts, metadatas): # chunk the documents. use semantic chunking if enabled chunk_params = { @@ -239,17 +250,8 @@ def _chunk_documents(encoder, texts, metadatas): if settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE: chunk_params["chunk_size"] = settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE - if settings.LITELLM_TOKEN_ENCODING_NAME: - """ - If we have a specific model encoding - we can use that to stay within token limits - """ - chunk_params["encoding_name"] = settings.LITELLM_TOKEN_ENCODING_NAME - recursive_splitter = RecursiveCharacterTextSplitter.from_tiktoken_encoder( - **chunk_params - ) - else: - recursive_splitter = RecursiveCharacterTextSplitter(**chunk_params) + + recursive_splitter = _get_text_splitter(**chunk_params) if settings.CONTENT_FILE_EMBEDDING_SEMANTIC_CHUNKING_ENABLED: """ @@ -467,16 +469,23 @@ def _embed_course_metadata_as_contentfile(serialized_resources): client.upload_points(CONTENT_FILES_COLLECTION_NAME, points=points, wait=False) -def _process_content_embeddings(serialized_content): +def _generate_content_file_points(serialized_content): """ - Chunk and embed content file documents + Chunk and embed content file documents, yielding PointStructs """ - embeddings = [] - metadata = [] - ids = [] encoder = dense_encoder() vector_name = encoder.model_short_name() - remove_docs = [] + + """ + Break up requests according to chunk size to stay under openai limits + 300,000 tokens per request + max array size: 2048 + see: https://platform.openai.com/docs/guides/rate-limits + """ + request_chunk_size = int( + 300000 / settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE + ) + for doc in serialized_content: embedding_context = _content_file_embedding_context(doc) if not embedding_context: @@ -493,57 +502,67 @@ def _process_content_embeddings(serialized_content): the content and number of chunk have changed so we need to remove all old points """ - remove_docs.append(doc["id"]) + # Remove old points directly to prevent re-fetching content + remove_params = { + "key": doc["key"], + "resource_readable_id": doc["resource_readable_id"], + } + if doc.get("run_readable_id"): + remove_params["run_readable_id"] = doc["run_readable_id"] + + remove_points_matching_params( + remove_params, collection_name=CONTENT_FILES_COLLECTION_NAME + ) + split_docs = _chunk_documents(encoder, [embedding_context], [doc]) - split_texts = [d.page_content for d in split_docs if d.page_content] + + # Identify non-empty chunks and their original indices + valid_chunks = [(i, d) for i, d in enumerate(split_docs) if d.page_content] + + if not valid_chunks: + continue + + split_texts = [d.page_content for _, d in valid_chunks] resource_vector_point_id = vector_point_id(doc["resource_readable_id"]) - split_metadatas = [ - { - "resource_point_id": str(resource_vector_point_id), - "chunk_number": chunk_id, - "chunk_content": d.page_content, - **{ - key: d.metadata[key] - for key in QDRANT_CONTENT_FILE_PARAM_MAP - if key in d.metadata - }, - } - for chunk_id, d in enumerate(split_docs) - if d.page_content - ] - split_ids = [ - vector_point_id( - f"{doc['resource_readable_id']}." - f"{doc.get('run_readable_id', '')}." - f"{doc['key']}.{md['chunk_number']}" - ) - for md in split_metadatas - ] - split_embeddings = [] - """ - Break up requests according to chunk size to stay under openai limits - 300,000 tokens per request - max array size: 2048 - see: https://platform.openai.com/docs/guides/rate-limits - """ - request_chunk_size = int( - 300000 / settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE - ) for i in range(0, len(split_texts), request_chunk_size): - split_chunk = split_texts[i : i + request_chunk_size] - split_embeddings.extend(list(encoder.embed_documents(split_chunk))) - embeddings.extend(split_embeddings) - metadata.extend(split_metadatas) - ids.extend(split_ids) - if remove_docs: - remove_qdrant_records(remove_docs, CONTENT_FILE_TYPE) - if ids: - return points_generator(ids, metadata, embeddings, vector_name) - return None + chunk_texts = split_texts[i : i + request_chunk_size] + chunk_embeddings = encoder.embed_documents(chunk_texts) + + for j, embedding in enumerate(chunk_embeddings): + # Map back to the original valid_chunk index + relative_index = i + j + if relative_index >= len(valid_chunks): + break + chunk_id, split_doc = valid_chunks[relative_index] + + metadata = { + "resource_point_id": str(resource_vector_point_id), + "chunk_number": chunk_id, + "chunk_content": split_doc.page_content, + **{ + key: split_doc.metadata[key] + for key in QDRANT_CONTENT_FILE_PARAM_MAP + if key in split_doc.metadata + }, + } + point_id = vector_point_id( + f"{doc['resource_readable_id']}." + f"{doc.get('run_readable_id', '')}." + f"{doc['key']}.{chunk_id}" + ) -def embed_learning_resources(ids, resource_type, overwrite): + yield models.PointStruct( + id=point_id, payload=metadata, vector={vector_name: embedding} + ) + # Explicitly free memory for large chunks + del chunk_texts + del chunk_embeddings + gc.collect() + + +def embed_learning_resources(ids, resource_type, overwrite): # noqa: PLR0915, C901 """ Embed learning resources @@ -559,7 +578,6 @@ def embed_learning_resources(ids, resource_type, overwrite): return client = qdrant_client() - create_qdrant_collections(force_recreate=False) if resource_type != CONTENT_FILE_TYPE: serialized_resources = list(serialize_bulk_learning_resources(ids)) @@ -581,44 +599,112 @@ def embed_learning_resources(ids, resource_type, overwrite): points = _process_resource_embeddings(serialized_resources) _embed_course_metadata_as_contentfile(serialized_resources) else: - serialized_resources = list(serialize_bulk_content_files(ids)) - # TODO: Pass actual Ids when we want scheduled content file summarization # noqa: FIX002, TD002, TD003 E501 - # Currently we only want to summarize content that either already has a summary - # OR is in a course where atleast one other content file has a summary - summary_content_ids = [ - resource["id"] - for resource in serialized_resources - if resource.get("summary") - or resource.get("require_summaries") - or ContentFile.objects.exclude(summary="") - .filter(run__id=resource.get("run_id")) - .exists() - ] - ContentSummarizer().summarize_content_files_by_ids( - summary_content_ids, overwrite - ) + serialized_resources = serialize_bulk_content_files(ids) + + # populated/modified by reference in process_batch + summary_content_ids = [] + + # Batching parameters + current_batch_docs = [] + current_batch_size = 0 collection_name = CONTENT_FILES_COLLECTION_NAME - points = [ - ( - vector_point_id( - f"{doc['resource_readable_id']}." - f"{doc.get('run_readable_id', '')}." - f"{doc['key']}.0" - ), - doc, - ) - for doc in serialized_resources - ] - if not overwrite: - filtered_point_ids = filter_existing_qdrant_points_by_ids( - [point[0] for point in points], - collection_name=CONTENT_FILES_COLLECTION_NAME, - ) - serialized_resources = [ - point[1] for point in points if point[0] in filtered_point_ids + + def process_batch(docs_batch, summaries_list): + """Process a batch of documents""" + # Collect IDs for summarization + contentfile_points = [ + ( + vector_point_id( + f"{doc['resource_readable_id']}." + f"{doc.get('run_readable_id', '')}." + f"{doc['key']}.0" + ), + doc, + ) + for doc in docs_batch ] - points = _process_content_embeddings(serialized_resources) + if not overwrite: + filtered_point_ids = filter_existing_qdrant_points_by_ids( + [point[0] for point in contentfile_points], + collection_name=collection_name, + ) + docs_batch = [ + point[1] + for point in contentfile_points + if point[0] in filtered_point_ids + ] + for resource in docs_batch: + if ( + resource.get("summary") + or resource.get("require_summaries") + or ContentFile.objects.exclude(summary="") + .filter(run__id=resource.get("run_id")) + .exists() + ): + summaries_list.append(resource["id"]) + + points_generator_iter = _generate_content_file_points(docs_batch) + points_upload_batch = [] + + for point in points_generator_iter: + points_upload_batch.append(point) + if len(points_upload_batch) >= settings.QDRANT_POINT_UPLOAD_BATCH_SIZE: + client.batch_update_points( + collection_name=collection_name, + update_operations=[ + models.UpsertOperation( + upsert=models.PointsList( + points=points_upload_batch, + ) + ), + ], + wait=False, + ) + points_upload_batch = [] + + if points_upload_batch: + client.batch_update_points( + collection_name=collection_name, + update_operations=[ + models.UpsertOperation( + upsert=models.PointsList( + points=points_upload_batch, + ) + ), + ], + wait=False, + ) + + # Explicit deletions to help GC + del points_generator_iter + del points_upload_batch + + # We don't delete docs_batch here because it's a reference passed in, + # but the caller clears the list. + + for doc in serialized_resources: + current_batch_docs.append(doc) + current_batch_size += len(doc.get("content", "") or "") + + if current_batch_size >= settings.QDRANT_BATCH_SIZE_BYTES: + process_batch(current_batch_docs, summary_content_ids) + current_batch_docs = [] + current_batch_size = 0 + gc.collect() + + # Process remaining + if current_batch_docs: + process_batch(current_batch_docs, summary_content_ids) + current_batch_docs = [] + gc.collect() + + if summary_content_ids: + ContentSummarizer().summarize_content_files_by_ids( + summary_content_ids, overwrite + ) + + points = None # Handled inside the loop if points: client.batch_update_points( collection_name=collection_name, diff --git a/vector_search/utils_test.py b/vector_search/utils_test.py index c4e48cc17b..01d96e37d9 100644 --- a/vector_search/utils_test.py +++ b/vector_search/utils_test.py @@ -1,3 +1,4 @@ +import random from decimal import Decimal from unittest.mock import MagicMock @@ -35,6 +36,7 @@ from vector_search.utils import ( _chunk_documents, _embed_course_metadata_as_contentfile, + _get_text_splitter, create_qdrant_collections, embed_learning_resources, embed_topics, @@ -69,7 +71,7 @@ def test_vector_point_id_used_for_embed(mocker, content_type): else: resources = ContentFileFactory.create_batch(5, content="test content") - summarize_content_files_by_ids_mock = mocker.patch( + mocker.patch( "learning_resources.content_summarizer.ContentSummarizer.summarize_content_files_by_ids" ) @@ -102,11 +104,6 @@ def test_vector_point_id_used_for_embed(mocker, content_type): .upsert.points ] ) == sorted(point_ids) - # TODO: Pass "[resource.id for resource in resources]" instead of [] when we want the scheduled content file summarization # noqa: FIX002, TD002, TD003 - summarize_content_files_by_ids_mock.assert_called_once_with( - [], - True, # noqa: FBT003 - ) @pytest.mark.parametrize("content_type", ["course", "content_file"]) @@ -138,7 +135,7 @@ def test_embed_learning_resources_no_overwrite(mocker, content_type): for doc in serialize_bulk_content_files([r.id for r in resources[0:3]]) ], ) - summarize_content_files_by_ids_mock = mocker.patch( + mocker.patch( "learning_resources.content_summarizer.ContentSummarizer.summarize_content_files_by_ids" ) embed_learning_resources( @@ -167,11 +164,6 @@ def test_embed_learning_resources_no_overwrite(mocker, content_type): ) == 3 ) - # TODO: Pass "[resource.id for resource in resources]" instead of [] when we want the scheduled content file summarization # noqa: FIX002, TD002, TD003 - summarize_content_files_by_ids_mock.assert_called_once_with( - [], - False, # noqa: FBT003 - ) def test_filter_existing_qdrant_points(mocker): @@ -368,7 +360,7 @@ def test_document_chunker(mocker): mocked_splitter.assert_called() settings.CONTENT_FILE_EMBEDDING_SEMANTIC_CHUNKING_ENABLED = False - + _get_text_splitter.cache_clear() mocked_splitter = mocker.patch("vector_search.utils.RecursiveCharacterTextSplitter") mocked_chunker = mocker.patch("vector_search.utils.SemanticChunker") @@ -377,10 +369,52 @@ def test_document_chunker(mocker): mocked_splitter.assert_called() +def test_expected_document_chunks(mocker): + """ + Test that the expected number of chunks are uploaded + """ + + settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE = random.randrange(10, 120) # noqa: S311 + settings.CONTENT_FILE_EMBEDDING_CHUNK_OVERLAP = random.randrange( # noqa: S311 + 1, settings.CONTENT_FILE_EMBEDDING_CHUNK_SIZE_OVERRIDE + ) + settings.CONTENT_FILE_EMBEDDING_SEMANTIC_CHUNKING_ENABLED = False + + encoder = dense_encoder() + mock_qdrant = mocker.patch("qdrant_client.QdrantClient") + mocker.patch( + "vector_search.utils.qdrant_client", + return_value=mock_qdrant, + ) + + encoder.token_encoding_name = None + + content_file = ContentFileFactory.create( + content="this is a. test: document. " * 1000 + ) + chunked = _chunk_documents( + encoder, + [content_file.content], + list(serialize_bulk_content_files([content_file.id])), + ) + + embed_learning_resources([content_file.id], "content_file", overwrite=True) + + num_points_uploaded = sum( + [ + len(mock_call.kwargs["update_operations"][0].upsert.points) + for mock_call in mock_qdrant.batch_update_points.mock_calls + ] + ) + + assert len(chunked) == num_points_uploaded + + def test_document_chunker_tiktoken(mocker): """ Test that we use tiktoken if a token encoding is specified """ + settings.LITELLM_TOKEN_ENCODING_NAME = None encoder = dense_encoder() encoder.token_encoding_name = None @@ -391,6 +425,8 @@ def test_document_chunker_tiktoken(mocker): _chunk_documents(encoder, ["this is a test document"], [{}]) mocked_splitter.assert_not_called() + # work around cache for testing + _get_text_splitter.cache_clear() settings.LITELLM_TOKEN_ENCODING_NAME = "test" # noqa: S105 _chunk_documents(encoder, ["this is a test document"], [{}]) mocked_splitter.assert_called() @@ -605,7 +641,6 @@ def test_embed_learning_resources_summarizes_only_contentfiles_with_summary(mock mock_qdrant = mocker.patch("qdrant_client.QdrantClient") mocker.patch("vector_search.utils.qdrant_client", return_value=mock_qdrant) mocker.patch("vector_search.utils.create_qdrant_collections") - mocker.patch("vector_search.utils._process_content_embeddings", return_value=None) mocker.patch( "vector_search.utils.filter_existing_qdrant_points_by_ids", return_value=[] ) diff --git a/yarn.lock b/yarn.lock index 15296ff431..311cf706b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15350,6 +15350,8 @@ __metadata: "@mitodl/course-search-utils": "npm:^3.5.0" "@mitodl/mitxonline-api-axios": "npm:^2025.11.24" "@mitodl/smoot-design": "npm:^6.19.0" + "@mui/material": "npm:^6.4.5" + "@mui/material-nextjs": "npm:^6.4.3" "@next/bundle-analyzer": "npm:^14.2.15" "@react-pdf/renderer": "npm:^4.3.0" "@remixicon/react": "npm:^4.2.0" @@ -15358,6 +15360,21 @@ __metadata: "@testing-library/jest-dom": "npm:^6.4.8" "@testing-library/react": "npm:^16.3.0" "@testing-library/user-event": "npm:^14.5.2" + "@tiptap/core": "npm:^3.13.0" + "@tiptap/extension-document": "npm:^3.13.0" + "@tiptap/extension-heading": "npm:^3.13.0" + "@tiptap/extension-highlight": "npm:^3.13.0" + "@tiptap/extension-horizontal-rule": "npm:^3.13.0" + "@tiptap/extension-image": "npm:^3.13.0" + "@tiptap/extension-list": "npm:^3.13.0" + "@tiptap/extension-subscript": "npm:^3.13.0" + "@tiptap/extension-superscript": "npm:^3.13.0" + "@tiptap/extension-text-align": "npm:^3.13.0" + "@tiptap/extension-typography": "npm:^3.13.0" + "@tiptap/extensions": "npm:^3.13.0" + "@tiptap/pm": "npm:^3.13.0" + "@tiptap/react": "npm:^3.13.0" + "@tiptap/starter-kit": "npm:^3.13.0" "@types/jest": "npm:^29.5.12" "@types/lodash": "npm:^4.17.7" "@types/node": "npm:^22.0.0" @@ -17149,22 +17166,6 @@ __metadata: "@testing-library/dom": "npm:^10.4.0" "@testing-library/react": "npm:^16.3.0" "@testing-library/user-event": "npm:^14.5.2" - "@tiptap/core": "npm:^3.13.0" - "@tiptap/extension-document": "npm:^3.13.0" - "@tiptap/extension-heading": "npm:^3.13.0" - "@tiptap/extension-highlight": "npm:^3.13.0" - "@tiptap/extension-horizontal-rule": "npm:^3.13.0" - "@tiptap/extension-image": "npm:^3.13.0" - "@tiptap/extension-link": "npm:^3.13.0" - "@tiptap/extension-list": "npm:^3.13.0" - "@tiptap/extension-subscript": "npm:^3.13.0" - "@tiptap/extension-superscript": "npm:^3.13.0" - "@tiptap/extension-text-align": "npm:^3.13.0" - "@tiptap/extension-typography": "npm:^3.13.0" - "@tiptap/extensions": "npm:^3.13.0" - "@tiptap/pm": "npm:^3.13.0" - "@tiptap/react": "npm:^3.13.0" - "@tiptap/starter-kit": "npm:^3.13.0" "@types/lodash.throttle": "npm:^4.1.9" "@types/react-dom": "npm:^19.2.3" "@types/react-slick": "npm:^0"