{"version":3,"file":"app-83e7d971.f584a1446b2fc034d810.bundle.js","mappings":"AAGA","sources":["webpack://lernplattform-admin-frontend/./src/resources/elements/pagination-widget/pagination-widget.html","webpack://lernplattform-admin-frontend/./src/resources/elements/phone-input/phone-input.html","webpack://lernplattform-admin-frontend/./src/resources/elements/picture-select-form/picture-select-form.html","webpack://lernplattform-admin-frontend/./src/resources/elements/presentation-viewer/presentation-viewer.html","webpack://lernplattform-admin-frontend/./src/resources/elements/preview-block/preview-block.html","webpack://lernplattform-admin-frontend/./src/resources/elements/preview-image/preview-image.html","webpack://lernplattform-admin-frontend/./src/resources/elements/progress-circle/progress-circle.html","webpack://lernplattform-admin-frontend/./src/resources/elements/question-block-preview/question-block-preview.html","webpack://lernplattform-admin-frontend/./src/resources/elements/question-list-widget/question-list-widget.html","webpack://lernplattform-admin-frontend/./src/resources/elements/question-preview-widget/question-preview-widget.html","webpack://lernplattform-admin-frontend/./src/resources/elements/questions-form/questions-form.html","webpack://lernplattform-admin-frontend/./src/resources/elements/quiz-template-card/quick-quiz-card/quick-quiz-card.html","webpack://lernplattform-admin-frontend/./src/resources/elements/quiz-template-card/quiz-template-card.html","webpack://lernplattform-admin-frontend/./src/resources/elements/recent-student-activity-table/recent-student-activity-table.html","webpack://lernplattform-admin-frontend/./src/resources/elements/required-update-overlay/required-update-overlay.html","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/components/ias-jump-link-edit/ias-jump-link-edit.html","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/components/internal-link-edit/internal-link-edit.html","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar-entry.html","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar.html","webpack://lernplattform-admin-frontend/./src/resources/elements/pagination-widget/pagination-widget.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/phone-input/phone-input.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/picture-select-form/picture-select-form.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/presentation-viewer/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/presentation-viewer/presentation-viewer.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/preview-block/preview-block.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/preview-image/preview-image.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/progress-circle/progress-circle.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/question-block-preview/question-block-preview.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/question-list-widget/question-list-widget.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/question-preview-widget/question-preview-widget.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/questions-form/questions-form.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/quiz-template-card/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/quiz-template-card/quiz-template-card.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/recent-student-activity-table/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/recent-student-activity-table/recent-student-activity-table.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/required-update-overlay/required-update-overlay.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/components/ias-jump-link-edit/ias-jump-link-edit.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/components/internal-link-edit/internal-link-edit.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/index.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/config/advancedRow.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/config/firstRow.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/config/imageRow.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/config/tableRow.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar-entry.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar-utils.ts","webpack://lernplattform-admin-frontend/./src/resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar.ts"],"sourcesContent":["// Module\nvar code = \"<template class=\\\"pagination-widget\\\"> <div if.to-view=\\\"showFilterRangeInfoText\\\" class=\\\"d-flex text-secondary align-self-center pagination-widget__range-text\\\"> <div t=\\\"${ shortenEntryText ? 'globals.xToYOfNEntriesShort' : 'globals.xToYOfNEntries' }\\\" t-params.to-view=\\\"filterRange\\\"></div> <div if.to-view=\\\"settingsCallback\\\" class=\\\"ml-1 pagination-widget__icon-wrapper\\\"> <a href=\\\"javascript:void(0);\\\" readonly.to-view=\\\"readonly\\\" click.delegate=\\\"settingsCallback()\\\"> <safe-icon class=\\\"pagination-widget__icon\\\" icon-name=\\\"System/Tabler/Dots-Vertical\\\"></safe-icon> </a> </div> </div> <div if.to-view=\\\"pages > 1\\\" class=\\\"ml-auto\\\"> <nav aria-label=\\\"Page navigation\\\"> <ul class=\\\"pagination m-0\\\"> <li class=\\\"page-item\\\"> <button-standard class=\\\"mr-1\\\" btn-fill-mode=\\\"fill-width\\\" readonly.to-view=\\\"readonly || currentPage === 1\\\" click.delegate=\\\"showPage(1)\\\" icon-name=\\\"System/Tabler/Chevrons-Left\\\" btn-color=\\\"secondary\\\"></button-standard> </li> <li class=\\\"page-item\\\"> <button-standard class=\\\"mr-1\\\" btn-fill-mode=\\\"fill-width\\\" readonly.to-view=\\\"readonly || currentPage === 1\\\" click.delegate=\\\"previousPage()\\\" icon-name=\\\"System/Tabler/Chevron-Left\\\" btn-color=\\\"secondary\\\"></button-standard> </li> <li repeat.for=\\\"i of pageQuickLinks\\\" class=\\\"page-item\\\"> <button-standard class=\\\"mr-1 d-none d-lg-block justify-content-center\\\" btn-fill-mode=\\\"fill-width\\\" readonly.to-view=\\\"readonly\\\" click.delegate=\\\"showPage(i + 1)\\\" btn-text.to-view=\\\"i + 1\\\" btn-color.to-view=\\\"currentPage === i + 1 ? 'primary' : 'secondary'\\\"></button-standard> </li> <li class=\\\"page-item\\\"> <button-standard class=\\\"ml-0\\\" btn-fill-mode=\\\"fill-width\\\" readonly.to-view=\\\"readonly || currentPage > pages - 1\\\" click.delegate=\\\"nextPage()\\\" icon-name=\\\"System/Tabler/Chevron-Right\\\" btn-color=\\\"secondary\\\"></button-standard> </li> <li class=\\\"page-item\\\"> <button-standard class=\\\"ml-1\\\" btn-fill-mode=\\\"fill-width\\\" readonly.to-view=\\\"readonly || currentPage > pages - 1\\\" click.delegate=\\\"showPage(pages)\\\" icon-name=\\\"System/Tabler/Chevrons-Right\\\" btn-color=\\\"secondary\\\"></button-standard> </li> </ul> </nav> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"phone-input ${ isInvalid ? 'is-invalid' : '' }\\\"> <div class=\\\"input-group ${disabled ? 'phone-input--disabled' : ''} ${isInvalid ? 'phone-input--is-invalid' : ''}\\\"> <select if.to-view=\\\"isAttached\\\" class=\\\"custom-select phone-input__calling-code\\\" disabled.to-view=\\\"disabled\\\" blur.trigger=\\\"forwardBlur()\\\" value.two-way=\\\"callingCodeValue\\\"> <option repeat.for=\\\"callingCode of callingCodes\\\" model.bind=\\\"callingCode\\\">+${callingCode}</option> </select> <input disabled.to-view=\\\"disabled\\\" class=\\\"form-control\\\" type=\\\"tel\\\" t=\\\"[placeholder]${placeholderTk}\\\" value.two-way=\\\"nationalNumberValue\\\" blur.trigger=\\\"forwardBlur()\\\" data-input> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"row\\\"> <div class=\\\"col-12\\\"> <label class=\\\"form-label\\\">${title}</label> </div> <div class=\\\"col-8\\\"> <div class=\\\"form-group\\\"> <preview-image image-src.to-view=\\\"pictureUrl ? pictureUrl : picture.url\\\" size=\\\"small\\\"></preview-image> </div> </div> <div class=\\\"col-4\\\"> <button class=\\\"btn btn-secondary btn-sm w-100 mb-2\\\" click.delegate=\\\"handleSelectImageClick()\\\" t=\\\"elements.pictureSelectForm.changePicture\\\"></button> <button if.to-view=\\\"picture.url\\\" class=\\\"btn btn-danger btn-sm w-100 mb-2\\\" click.delegate=\\\"handleDeleteImageClick()\\\" t=\\\"globals.delete\\\"></button> <p if.to-view=\\\"showPictureTitle\\\" class=\\\"mb-0 text-muted\\\" t=\\\"elements.pictureSelectForm.pictureTitle\\\" t-params.to-view=\\\"{ pictureTitle: pictureTitle ? pictureTitle : '-' }\\\"></p> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"presentation-viewer ${ isFullscreen ? 'presentation-viewer--is-fullscreen' : '' } ${ noSelection ? 'presentation-viewer--no-selection' : '' }\\\"> <div class=\\\"presentation-viewer__presentation-controls\\\"> <label for=\\\"courseSelect\\\" t=\\\"elements.distinarCourseView.distinarCourseSelection\\\" class=\\\"presentation-viewer__presentation-label text-nowrap\\\"></label> <search-and-select class=\\\"presentation-viewer__presentation-input ${ selectedCourseId ? 'input-group-append' : '' }\\\" data.to-view=\\\"searchSelectPresentations\\\" sas-item-changed.trigger=\\\"onPresentationSelected($event)\\\" search-text.to-view=\\\"searchSelectValue\\\" t=\\\"[label]globals.searchSelect\\\"></search-and-select> <dropdown-button if.to-view=\\\"selectedCourseId\\\" offset.to-view=\\\"[0,0]\\\" placement=\\\"bottom-start\\\" show-arrow.to-view=\\\"false\\\"> <div class=\\\"input-group-append h-100\\\" slot=\\\"button\\\"> <button-standard btn-color=\\\"secondary\\\" icon-name=\\\"System/Tabler/Dots\\\"></button-standard> </div> <div class=\\\"dropdown-item pointer d-flex justify-content-between align-items-center\\\" click.delegate=\\\"startGeneratePdf()\\\"> <span t=\\\"globals.generatePdf\\\"></span> <safe-icon icon-name=\\\"System/Tabler/File\\\"></safe-icon> </div> </dropdown-button> <lecture-button if.to-view=\\\"canStartQuickDistinar\\\" class=\\\"presentation-viewer__quickinar-btn mr-2\\\" type=\\\"fill\\\" click.delegate=\\\"startQuickDistinar()\\\" disabled.to-view=\\\"noSelection\\\"> <span class=\\\"text-nowrap\\\" t=\\\"elements.distinarCourseView.startDistinar\\\"></span> </lecture-button> <lecture-button if.to-view=\\\"supportsFullscreen\\\" class=\\\"presentation-viewer__fullscreen-btn\\\" type=\\\"fill\\\" click.delegate=\\\"onMaximizeClicked()\\\" disabled.to-view=\\\"noSelection\\\"> <safe-icon class=\\\"presentation-viewer__fullscreen-btn-icon\\\" icon-name=\\\"System/Tabler/Maximize\\\"></safe-icon> <span class=\\\"text-nowrap\\\" t=\\\"elements.distinarCourseView.fullscreen\\\"></span> </lecture-button> <div class=\\\"presentation-viewer__presentation-controls-line\\\"></div> </div> <div class=\\\"presentation-viewer__slide-controls\\\"> <span class=\\\"presentation-viewer__current-slide-index\\\" t=\\\"elements.distinarCourseView.currentSlideIndex\\\" t-params.to-view=\\\"currentSlideNavigationInfo\\\"></span> <div class=\\\"presentation-viewer__slide-controls-spacer\\\"></div> <whiteboard-controls show.to-view=\\\"showWhiteboard\\\" whiteboard.to-view=\\\"whiteboard\\\"></whiteboard-controls> <div class=\\\"presentation-viewer__slide-controls-spacer\\\"></div> <lecture-button class=\\\"presentation-viewer__toggle-whiteboard-btn ${showWhiteboard ? 'presentation-viewer__toggle-whiteboard-btn--toggled' : ''}\\\" type=\\\"fill\\\" click.delegate=\\\"onToggleWhiteboardClicked()\\\" disabled.to-view=\\\"noSelection\\\"> <safe-icon class=\\\"presentation-viewer__previous-btn-icon\\\" icon-name=\\\"Distinar/ToggleWhiteboard\\\"></safe-icon> </lecture-button> <lecture-button class=\\\"presentation-viewer__previous-slide-btn\\\" type=\\\"outline\\\" click.delegate=\\\"onNavigateToPrevious()\\\" disabled.to-view=\\\"!navigationInfo.hasPreviousSlide\\\"> <safe-icon class=\\\"presentation-viewer__previous-btn-icon\\\" icon-name=\\\"Distinar/CourseArrowLeft\\\"></safe-icon> </lecture-button> <lecture-button class=\\\"presentation-viewer__next-slide-btn\\\" type=\\\"outline\\\" click.delegate=\\\"onNavigateToNext()\\\" disabled.to-view=\\\"!navigationInfo.hasNextSlide\\\"> <safe-icon class=\\\"presentation-viewer__next-btn-icon\\\" icon-name=\\\"Distinar/CourseArrowRight\\\"></safe-icon> </lecture-button> </div> <content-editor-slide-selection class=\\\"presentation-viewer__side-bar\\\" context-identifier.to-view=\\\"contextIdentifier\\\"></content-editor-slide-selection> <div class=\\\"presentation-viewer__content\\\"> <fullscreenable-container view-model.ref=\\\"fullscreenableContainer\\\" class=\\\"presentation-viewer__fullscreen-container\\\" is-fullscreen.from-view=\\\"isFullscreen\\\" mousemove.trigger=\\\"handleMousemove($event)\\\"> <content-editor-presentation-slide class=\\\"presentation-viewer__slide\\\" context-identifier.to-view=\\\"contextIdentifier\\\" slide-id.to-view=\\\"slideId\\\" empty-placeholder-tk=\\\"common.presentation-slide.noPresentationSelected\\\" is-non-interactive-preview.to-view=\\\"false\\\"></content-editor-presentation-slide> <whiteboard view-model.ref=\\\"whiteboard\\\" class=\\\"presentation-viewer__whiteboard\\\" is-shown.to-view=\\\"showWhiteboard\\\" whiteboard-changed.trigger=\\\"onWhiteboardChanged($event)\\\"></whiteboard> <div class=\\\"presentation-viewer__whiteboard-controls ${ (mouseMovedRecently || mouseInsideControlElement) && isFullscreen && showWhiteboard ? 'presentation-viewer__whiteboard-controls--visible' : '' }\\\" data-control-element=\\\"data-control-element\\\"> <whiteboard-controls whiteboard.to-view=\\\"whiteboard\\\"></whiteboard-controls> </div> <div class=\\\"presentation-viewer__controls ${ (mouseMovedRecently || mouseInsideControlElement) && isFullscreen ? 'presentation-viewer__controls--visible' : '' }\\\" data-control-element=\\\"data-control-element\\\"> <div class=\\\"presentation-viewer__main-controls\\\"> <div class=\\\"presentation-viewer__controls-row\\\"> <lecture-button class=\\\"presentation-viewer__whiteboard-btn ${showWhiteboard ? 'presentation-viewer__whiteboard-btn--toggled' : '' }\\\" type=\\\"fill\\\" click.delegate=\\\"onToggleWhiteboardClicked()\\\"> <safe-icon class=\\\"presentation-viewer__whiteboard-btn-icon\\\" icon-name=\\\"Distinar/ToggleWhiteboard\\\"></safe-icon> </lecture-button> <lecture-button if.to-view=\\\"isFullscreen\\\" class=\\\"presentation-viewer__exit-fullscreen-btn\\\" type=\\\"fill\\\" click.delegate=\\\"onMinimizeClicked()\\\"> <span class=\\\"text-nowrap\\\" t=\\\"elements.lectureCourseLectureSlide.exitFullscreen\\\"></span> </lecture-button> </div> </div> </div> <div show.to-view=\\\"isFullscreen\\\" class=\\\"presentation-viewer__additional-fullscreen-content\\\"> <slot name=\\\"additionalFullscreenContent\\\"></slot> </div> </fullscreenable-container> </div> <loading-overlay visible.to-view=\\\"loading\\\"></loading-overlay> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <course-lecture-block-audio if.to-view=\\\"block.type === BlockType.AUDIO\\\" class=\\\"preview-block__block\\\" block.to-view=\\\"block\\\"></course-lecture-block-audio> <course-lecture-block-video if.to-view=\\\"block.type === BlockType.VIDEO\\\" class=\\\"preview-block__block\\\" block.to-view=\\\"block\\\" is-static.to-view=\\\"false\\\"></course-lecture-block-video> <course-lecture-block-text if.to-view=\\\"block.type === BlockType.TEXT\\\" class=\\\"preview-block__block\\\" block.to-view=\\\"block\\\" show-annotations.to-view=\\\"true\\\"></course-lecture-block-text> <course-lecture-block-file if.to-view=\\\"block.type === BlockType.FILE\\\" class=\\\"preview-block__block preview-block__block--file\\\" block.to-view=\\\"block\\\"></course-lecture-block-file> <preview-image if.to-view=\\\"block.type === BlockType.PICTURE\\\" image-src.to-view=\\\"block.pictureUrl\\\"></preview-image> <mp-interactive-text if.to-view=\\\"block.type === BlockType.MP_INTERACTIVE_TEXT\\\" clear-btn-tk=\\\"globals.clear\\\" restart-btn-tk=\\\"globals.restart\\\" finished-text-tk=\\\"globals.finished\\\" class=\\\"preview-block__block-mp-interactive\\\" markdown.to-view=\\\"block.text\\\"></mp-interactive-text> <mp-tiles-preview if.to-view=\\\"block.type === BlockType.MP_TILES\\\" tiles.to-view=\\\"block.tiles\\\"></mp-tiles-preview> <div if.to-view=\\\"block.type === BlockType.MP_QUESTION || block.type === BlockType.QUESTION\\\"> <question-preview-widget question.to-view=\\\"currentQuestion\\\"></question-preview-widget> <div class=\\\"pt-2\\\"> <pagination-widget class=\\\"flex-grow-1\\\" offset.to-view=\\\"currentQuestionIndex\\\" limit.to-view=\\\"1\\\" show-filter-range-info-text.to-view=\\\"false\\\" total.to-view=\\\"questions.length\\\" new-offset-callback.to-view=\\\"newOffsetCallback\\\" num-page-quick-links.to-view=\\\"6\\\"></pagination-widget> </div> </div> <mp-rfg-preview if.to-view=\\\"block.type === BlockType.MP_RFG\\\" course-id.to-view=\\\"courseId\\\" text.to-view=\\\"block.text & debounce:500\\\"></mp-rfg-preview> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"preview-image ${size === PreviewImageSize.Small ? 'preview-image--small' : ''}\\\"> <img if.to-view=\\\"imageSrc\\\" class=\\\"preview-image__image\\\" src.to-view=\\\"imageSrc\\\"> <div else class=\\\"preview-image__placeholder\\\"> <safe-icon class=\\\"preview-image__placeholder-icon\\\" icon-name=\\\"Uce/Photo\\\"></safe-icon> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"progress-circle\\\" style.to-view=\\\"{ '--progress-circle-internal-circumference': circumference,\\n                           '--progress-circle-internal-progress-offset': progressOffset,\\n                           '--progress-circle-internal-size': size }\\\"> <svg class=\\\"progress-circle__svg\\\" viewBox=\\\"0 0 40 40\\\"> <circle class=\\\"progress-circle__svg-circle\\\" stroke-width.to-view=\\\"strokeWidth\\\" fill=\\\"transparent\\\" r.to-view=\\\"radius\\\" cx.to-view=\\\"viewBoxSize / 2\\\" cy.to-view=\\\"viewBoxSize / 2\\\"/> <circle ref=\\\"progressCircle\\\" class=\\\"progress-circle__svg-circle-progress\\\" stroke-width.to-view=\\\"strokeWidth\\\" fill=\\\"transparent\\\" r.to-view=\\\"radius\\\" cx.to-view=\\\"viewBoxSize / 2\\\" cy.to-view=\\\"viewBoxSize / 2\\\"/> </svg> <div class=\\\"progress-circle__text-container\\\"> <span class=\\\"progress-circle__text\\\">${progress}%</span> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"question-block-preview\\\"> <div class=\\\"question-block-preview__content\\\"> <progress-bar></progress-bar> <h6 class=\\\"question-block-preview__title\\\" t=\\\"elements.questionBlockPreview.title\\\"></h6> <p class=\\\"question-block-preview__description\\\" t=\\\"elements.questionBlockPreview.questionsDescription\\\"></p> <div class=\\\"question-block-preview__status\\\"> <p class=\\\"question-block-preview__status-text\\\" t=\\\"elements.questionBlockPreview.answeredCorrectly\\\"></p> <p class=\\\"question-block-preview__status-summary\\\" t-params.to-view=\\\"{ count: block.questionIds.length }\\\" t=\\\"elements.questionBlockPreview.questionsProgress\\\"></p> </div> <button-standard class=\\\"question-block-preview__button\\\" btn-fill-mode=\\\"fill-width\\\" t=\\\"[btn-text]elements.questionBlockPreview.startQuestions\\\"></button-standard> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <div if.to-view=\\\"chapters.length\\\" class=\\\"row\\\"> <div class=\\\"col-12\\\"> <div class=\\\"form-group\\\"> <label class=\\\"form-label\\\" t=\\\"elements.questionListWidget.selectChapter\\\"></label> <div class=\\\"input-group\\\"> <select class=\\\"custom-select\\\" value.bind=\\\"selectedChapter\\\" change.trigger=\\\"handleSelectedChapterChanged()\\\" disabled.to-view=\\\"chaptersLoading || questionsLoading\\\"> <option model.bind=\\\"null\\\" t=\\\"elements.questionListWidget.allChapters\\\"></option> <option repeat.for=\\\"chapter of chapters\\\" model.bind=\\\"chapter\\\">${chapter.title}</option> </select> <span class=\\\"input-group-append\\\"> <button-standard click.trigger=\\\"resetSelectedChapterFilter()\\\" icon-name=\\\"System/Tabler/X\\\" btn-color=\\\"secondary\\\"></button-standard> </span> </div> </div> </div> </div> <div class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" refresh-version.to-view=\\\"tableRefreshVersionInternal\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\"></custom-table> </div> </div> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"question-preview-widget ${ formattedQuestion ? 'question-preview-widget--has-question' : '' } question-preview-widget--vertical-display-mode-${verticalDisplayMode}\\\"> <alert-message if.to-view=\\\"formattedQuestion && !isRegularQuestion && !isPictureQuestion && !isLTQuestion\\\" message.to-view=\\\"{ tk: 'elements.questionPreviewWidget.notSupported' }\\\" message-style=\\\"alert-danger\\\"></alert-message> <static-block-preview if.to-view=\\\"!formattedQuestion\\\" text-tk=\\\"pages.content-editor.selectQuestion\\\" icon-name=\\\"File/question\\\"></static-block-preview> <div if.to-view=\\\"isRegularQuestion\\\" class=\\\"${ verticalDisplayMode === VerticalDisplayMode.Scroll ? 'question-preview-widget__question' : '' }\\\"> <div class=\\\"${ verticalDisplayMode === VerticalDisplayMode.Scroll ? 'question-preview-widget__question-text-and-image' : '' }\\\"> <h3 class=\\\"question-preview-widget__title question-preview-widget__title--vertical-display-mode-${verticalDisplayMode}-${imageMode} question-preview-widget__title--vertical-display-mode-${verticalDisplayMode}\\\">${formattedQuestion.text}</h3> <exam-image if.to-view=\\\"formattedQuestion.pictureUrl\\\" class=\\\"question-preview-widget__image question-preview-widget__image--vertical-display-mode-${verticalDisplayMode}\\\" alt-text.to-view=\\\"formattedQuestion.text\\\" blink-img-src.to-view=\\\"formattedQuestion.pictureBlinkUrl\\\" img-src.to-view=\\\"formattedQuestion.pictureUrl\\\" blur-hash.to-view=\\\"formattedQuestion.pictureBlurHash\\\" blink-blur-hash.to-view=\\\"formattedQuestion.pictureBlinkBlurHash\\\" enable-zoom.to-view=\\\"false\\\"></exam-image> </div> <possible-answers theme=\\\"lecturer\\\" class=\\\"question-preview-widget__answers question-preview-widget__answers--vertical-display-mode-${verticalDisplayMode}\\\" question-type.to-view=\\\"formattedQuestion.type\\\" answers.to-view=\\\"formattedQuestion.answers[0]\\\" readonly.to-view=\\\"true\\\" vertical-display-mode.to-view=\\\"verticalDisplayMode\\\" pictures-enabled.to-view=\\\"true\\\" voice-over-enabled.to-view=\\\"false\\\" reveal-answer.to-view=\\\"true\\\"></possible-answers> </div> <div if.to-view=\\\"isPictureQuestion\\\" class=\\\"${ verticalDisplayMode === VerticalDisplayMode.Scroll ? 'question-preview-widget__question' : '' }\\\"> <h3 class=\\\"question-preview-widget__title question-preview-widget__title--vertical-display-mode-${verticalDisplayMode} question-preview-widget__title--vertical-display-mode-${verticalDisplayMode}-margins\\\">${formattedQuestion.text}</h3> <picture-puzzle class=\\\"question-preview-widget__image ${ verticalDisplayMode === VerticalDisplayMode.Scale ? 'question-preview-widget__image--puzzle' : 'question-preview-widget__image--vertical-display-mode-scroll' } question-preview-widget__image--vertical-display-mode-${verticalDisplayMode}-margins\\\" question.to-view=\\\"formattedQuestion\\\" disabled.to-view=\\\"true\\\" selected-answers.to-view=\\\"[1,2,3,4]\\\" reveal-answer.to-view=\\\"true\\\" readonly.to-view=\\\"true\\\"></picture-puzzle> </div> <template if.to-view=\\\"isLTQuestion\\\"> <fill-in-the-blank-preview class=\\\"${ verticalDisplayMode === VerticalDisplayMode.Scroll ? 'question-preview-widget__cloze-question-scroll' : '' }\\\" question.to-view=\\\"formattedQuestion\\\"></fill-in-the-blank-preview> </template> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"row row-cards row-deck\\\"> <div class=\\\"col-12\\\"> <div class=\\\"card\\\"> <custom-table columns.to-view=\\\"tableColumns\\\" unbounded=\\\"true\\\" refresh-version.to-view=\\\"tableRefreshVersion\\\" hide-footer.to-view=\\\"!canAddMultiple\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\"></custom-table> <div class=\\\"card-footer text-right d-flex\\\"> <div if.to-view=\\\"availableCourses.length\\\" class=\\\"d-flex align-items-center ml-auto flex-grow-1\\\"> <h6 class=\\\"mb-0 mr-2\\\" t=\\\"globals.course\\\"></h6> <select class=\\\"custom-select\\\" change.delegate=\\\"changeCourse()\\\" value.two-way=\\\"selectedCourse\\\"> <option repeat.for=\\\"course of availableCourses\\\" model.bind=\\\"course\\\">${course.title}</option> </select> </div> <button-standard if.to-view=\\\"showCopyQuestionButton\\\" class=\\\"${ availableCourses.length ? 'ml-2' : 'ml-auto' }\\\" click.delegate=\\\"copyQuestionFromCourse()\\\" disabled.to-view=\\\"!canAddQuestion\\\" t=\\\"[btnText]elements.questionsForm.copyQuestion\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"primary\\\"></button-standard> <button-standard else class=\\\"${ availableCourses.length ? 'ml-2' : 'ml-auto' }\\\" click.delegate=\\\"addQuestion()\\\" disabled.to-view=\\\"!canAddQuestion\\\" t=\\\"[btnText]elements.questionsForm.addQuestion\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"primary\\\"></button-standard> <button-standard if.to-view=\\\"isProvider\\\" class=\\\"ml-2\\\" click.delegate=\\\"createQuestion()\\\" disabled.to-view=\\\"!canAddQuestion\\\" t=\\\"[btnText]elements.questionsForm.createQuestion\\\" icon-name=\\\"System/Tabler/Plus\\\" btn-color=\\\"primary\\\"></button-standard> </div> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"quiz-template-card quiz-template-card--quick-quiz mdc-button mdc-button--touch mdc-button--raised\\\"> <div class=\\\"mdc-button__ripple\\\"></div> <safe-icon class=\\\"quiz-template-card__icon\\\" icon-name=\\\"LectureQuiz/QuickQuiz\\\"></safe-icon> <p class=\\\"quiz-template-card__info\\\" t=\\\"elements.quiz-template-card.chooseChapterAndStart\\\"></p> </template>\";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"quiz-template-card mdc-button mdc-button--touch mdc-button--raised quiz-template-card--style-${cardStyle} ${ disabled ? 'quiz-template-card--disabled' : '' }\\\"> <div class=\\\"mdc-button__ripple\\\"></div> <h3 class=\\\"quiz-template-card__title\\\">${quizTemplate.name}</h3> <div class=\\\"quiz-template-card__course-tag\\\">${quizTemplate.courseTitle}</div> <div if.to-view=\\\"creatorName\\\" class=\\\"quiz-template-card__info quiz-template-card__info--shared-by\\\"> <safe-icon icon-name=\\\"System/Tabler/User-Circle\\\" class=\\\"quiz-template-card__info-icon\\\"></safe-icon> <span t=\\\"elements.quiz-template-card.sharedBy\\\" t-params.to-view=\\\"{ name: creatorName }\\\"></span> </div> <p class=\\\"quiz-template-card__info\\\" t=\\\"elements.quiz-template-card.questionCountAndDuration\\\" t-params.to-view=\\\"questionCountAndDurationDisplay\\\"></p> <div class=\\\"quiz-template-card__btns\\\"> <button-standard if.to-view=\\\"releaseable\\\" stop-propagation class=\\\"quiz-template-card__btn\\\" active.to-view=\\\"!isSharedOrReleased\\\" click.trigger=\\\"handleReleaseClicked()\\\" icon-name.to-view=\\\"isSharedOrReleased ? 'System/Tabler/Eye' : 'System/Tabler/EyeOff'\\\" tooltip-tk=\\\"elements.quiz-template-card.${ isSharedOrReleased  ? 'released' : 'notReleased' }\\\" icon-color=\\\"white\\\" btn-size=\\\"tiny\\\" btn-fill-mode=\\\"fill-width\\\" btn-type=\\\"icon\\\"></button-standard> <button-standard if.to-view=\\\"playable\\\" class=\\\"ml-2 quiz-template-card__btn\\\" click.trigger=\\\"handlePlayClicked()\\\" icon-name=\\\"System/Tabler/Play\\\" tooltip-tk=\\\"elements.quiz-template-card.startQuiz\\\" icon-color=\\\"white\\\" btn-size=\\\"tiny\\\" btn-type=\\\"icon\\\"></button-standard> <button-standard if.to-view=\\\"editable\\\" class=\\\"ml-2 quiz-template-card__btn\\\" click.trigger=\\\"handleEditClicked()\\\" icon-name=\\\"System/Tabler/Edit\\\" icon-color=\\\"white\\\" tooltip-tk=\\\"globals.edit\\\" btn-size=\\\"tiny\\\" btn-type=\\\"icon\\\"></button-standard> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"recent-student-activity-table\\\"> <div class=\\\"recent-student-activity-table__title card-title\\\" t=\\\"elements.recentStudentActivityTable.title\\\"></div> <custom-table columns.to-view=\\\"tableColumns\\\" get-shown-entries-data.call=\\\"fetchTableData(limit, offset)\\\"></custom-table> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <common-full-screen-container view-model.ref=\\\"fullScreenContainer\\\" animation-player.to-view=\\\"animationPlayer\\\" importance.to-view=\\\"2\\\"> <div ref=\\\"overlayElement\\\" class=\\\"required-update-overlay__overlay\\\" tabindex=\\\"0\\\" style.to-view=\\\"{ 'opacity': opacity }\\\"> <div class=\\\"required-update-overlay__content-wrapper lp-site-content-small\\\"> <div class=\\\"required-update-overlay__top-content\\\"> <div class=\\\"required-update-overlay__info-title\\\" t=\\\"elements.required-update-overlay.infoTitle\\\"></div> <div class=\\\"required-update-overlay__info-text\\\" t=\\\"elements.required-update-overlay.infoText\\\"></div> </div> <div class=\\\"required-update-overlay__bottom-content\\\"> <button-standard t=\\\"[btnText]elements.required-update-overlay.reloadPage\\\" btn-fill-mode.to-view=\\\"ButtonFillMode.FillWidth\\\" click.delegate=\\\"handleReloadClick()\\\"></button-standard> </div> </div> </div> </common-full-screen-container> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"ias-jump-link-edit\\\" show.bind=\\\"visible\\\"> <div class=\\\"ias-jump-link-edit__row\\\"> <div t=\\\"elements.rich-text-editor.ias-jump-link-edit.jumpSlide\\\"></div> <x-select items.to-view=\\\"jumpSlideOptions\\\" value.to-view=\\\"attributes.jumpSlideOrdinal\\\" selection-changed.trigger=\\\"onJumpSlideOrdinalInput($event)\\\"/> </div> <div class=\\\"ias-jump-link-edit__row\\\"> <div t=\\\"elements.rich-text-editor.ias-jump-link-edit.type\\\"></div> <x-select items.to-view=\\\"typeOptions\\\" value.to-view=\\\"attributes.type\\\" selection-changed.trigger=\\\"onTypeInput($event)\\\"/> </div> <div class=\\\"ias-jump-link-edit__row\\\"> <div t=\\\"elements.rich-text-editor.ias-jump-link-edit.text\\\"></div> <input if.to-view=\\\"visible\\\" value.two-way=\\\"attributes.text\\\" input.trigger=\\\"onUserInput($event)\\\"> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"internal-link-edit\\\" show.bind=\\\"visible\\\"> <button-standard class=\\\"internal-link-edit__edit\\\" click.delegate=\\\"handleEditClick()\\\" t=\\\"globals.edit\\\" btn-color=\\\"primary\\\" btn-type=\\\"uce\\\"></button-standard> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template> <button-standard if.to-view=\\\"!entry.children && entry.type !== 'colorSelect'\\\" icon-name.to-view=\\\"entry.iconName\\\" click.delegate=\\\"entry.callback()\\\" t=\\\"[btn-text]${entry.textTk}\\\" btn-type.to-view=\\\"flat ? 'editor-flat' : null\\\" btn-color.to-view=\\\"entry.isActive ? 'primary' : 'secondary'\\\" tooltip-tk.to-view=\\\"entry.tooltipTk\\\" btn-size=\\\"icon\\\"></button-standard> <div if.to-view=\\\"entry.children\\\" class=\\\"rich-text-editor-toolbar__option-select-wrapper ${entry.extraClasses}\\\"> <button-standard icon-name.to-view=\\\"entry.showIconInsteadOfSelectedChild ? entry.iconName : selectEntry.iconName\\\" class=\\\"rich-text-editor-toolbar-entry__main-button\\\" t=\\\"[btn-text]${entry.iconOnlyInSelect || entry.showIconInsteadOfSelectedChild ? '' : selectEntry.textTk}\\\" click.delegate=\\\"handleDropDownMainButtonClick()\\\" tooltip-tk.to-view=\\\"entry.tooltipTk\\\" btn-color.to-view=\\\"(entry.highlightOnActive && entry.activeChild) ? 'primary' : 'secondary'\\\" btn-fill-mode=\\\"fill-width\\\" btn-size=\\\"icon\\\"></button-standard> <button-standard class=\\\"rich-text-editor-toolbar-entry__dropdown-button\\\" click.delegate=\\\"expanded = !expanded\\\" btn-color=\\\"secondary\\\" dropdown-open.to-view=\\\"textAlignmentOptionSelectVisible\\\" btn-size=\\\"icon\\\"></button-standard> <div if.to-view=\\\"expanded\\\" class=\\\"rich-text-editor-toolbar__option-select ${entry.extraChildClasses}\\\"> <button-standard repeat.for=\\\"option of entry.children\\\" icon-name.to-view=\\\"option.iconName\\\" click.delegate=\\\"handleChildClick(option)\\\" t=\\\"[btn-text]${option.textTk}\\\" btn-color.to-view=\\\"option.isActive ? 'primary' : 'secondary'\\\" tooltip-tk.to-view=\\\"option.tooltipTk\\\" btn-fill-mode=\\\"fill-width\\\" btn-size=\\\"icon\\\"></button-standard> </div> </div> <div if.to-view=\\\"entry.type === 'colorSelect'\\\" class=\\\"rich-text-editor-toolbar__option-select-wrapper ${entry.extraClasses}\\\"> <button-standard icon-name.to-view=\\\"entry.iconName\\\" class=\\\"rich-text-editor-toolbar-entry__main-button\\\" click.delegate=\\\"expanded = !expanded\\\" btn-color.to-view=\\\"(entry.highlightOnActive && entry.activeChild) ? 'primary' : 'secondary'\\\" btn-fill-mode=\\\"fill-width\\\" tooltip-tk.to-view=\\\"entry.tooltipTk\\\" btn-size=\\\"icon\\\"></button-standard> <div if.to-view=\\\"expanded\\\" class=\\\"rich-text-editor-toolbar__option-select ${entry.extraChildClasses}\\\"> <content-editor-color-select color-selected.trigger=\\\"handleColorSelected($event)\\\" current-color.to-view=\\\"entry.attributes.color\\\"></content-editor-color-select> </div> </div> </template> \";\n// Exports\nexport default code;","// Module\nvar code = \"<template class=\\\"rich-text-editor-toolbar rich-text-editor-toolbar--mode-${mode} ${ interactiveTextControls ? 'rich-text-editor-toolbar--with-interactive-text-options' : '' }\\\"> <template if.to-view=\\\"courseId\\\"></template> <div if.to-view=\\\"editor\\\" class=\\\"rich-text-editor-toolbar__bar\\\"> <div class=\\\"rich-text-editor-toolbar__row\\\"> <template repeat.for=\\\"entry of entries\\\"> <rich-text-editor-toolbar-entry if.to-view=\\\"showEntry(entry, isPresentation, interactiveTextControls, isIas, mode === Mode.SystemText)\\\" class=\\\"rich-text-editor-toolbar__entry\\\" entry.to-view=\\\"entry\\\"></rich-text-editor-toolbar-entry> </template> <button-standard click.delegate=\\\"toggleSearchAndReplace()\\\" icon-name=\\\"System/Tabler/Search\\\" tooltip-tk=\\\"pages.content-editor.tooltips.searchAndReplace\\\" btn-color.to-view=\\\"showSearchAndReplace ? 'primary' : 'secondary'\\\" btn-size=\\\"icon\\\"></button-standard> <button-standard if.to-view=\\\"isProvider && !isPresentation && mode !== Mode.SystemText\\\" click.delegate=\\\"toggleAi()\\\" icon-name=\\\"System/Ai\\\" tooltip-tk=\\\"pages.content-editor.tooltips.generateWithAi\\\" btn-color.to-view=\\\"showAi ? 'primary' : 'secondary'\\\" btn-size=\\\"icon\\\"></button-standard> </div> <x-icon class=\\\"rich-text-editor-toolbar__toggle-advanced-icon\\\" click.delegate=\\\"toggleAdvanced()\\\" icon-name=\\\"${ showAdvanced ? 'Uce/CollapseMinus' : 'Uce/CollapsePlus' }\\\" size=\\\"unspecified\\\" icon-type=\\\"custom\\\"></x-icon> <div if.to-view=\\\"showAdvanced\\\" class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--advanced\\\"> <p class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.advancedOptions\\\"></p> <rich-text-editor-toolbar-entry repeat.for=\\\"entry of advancedRow\\\" if.to-view=\\\"showEntry(entry, isPresentation, interactiveTextControls, isIas, mode === Mode.SystemText)\\\" class=\\\"rich-text-editor-toolbar__entry\\\" entry.to-view=\\\"entry\\\"></rich-text-editor-toolbar-entry> </div> <div if.to-view=\\\"showTableOptions\\\" class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--extra\\\"> <p class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.tableOptions\\\"></p> <rich-text-editor-toolbar-entry repeat.for=\\\"entry of tableRow\\\" class=\\\"rich-text-editor-toolbar__entry\\\" flat.to-view=\\\"true\\\" entry.to-view=\\\"entry\\\"></rich-text-editor-toolbar-entry> </div> <div if.to-view=\\\"showTableOptions\\\" class=\\\"rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--filler\\\"></div> <div if.to-view=\\\"showImageOptions\\\" class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--extra\\\"> <p class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.imageOptions\\\"></p> <rich-text-editor-toolbar-entry repeat.for=\\\"entry of imageRow\\\" class=\\\"rich-text-editor-toolbar__entry\\\" flat.to-view=\\\"true\\\" entry.to-view=\\\"entry\\\"></rich-text-editor-toolbar-entry> </div> <div if.to-view=\\\"showSearchAndReplace\\\" class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--bottom-tray\\\"> <div> <div class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.search\\\"></div> <input type=\\\"search\\\" value.two-way=\\\"searchTerm\\\" class=\\\"rich-text-editor-toolbar__bottom-tray-input\\\"> </div> <div> <div class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.replace\\\"></div> <input type=\\\"search\\\" value.two-way=\\\"replaceTerm\\\" class=\\\"rich-text-editor-toolbar__bottom-tray-input\\\"> </div> <button-standard click.delegate=\\\"handleReplace()\\\" tooltip-tk=\\\"elements.richTextEditorToolbar.replaceNext\\\" icon-name=\\\"System/Arrow - Right\\\" btn-type=\\\"editor-flat\\\" btn-color=\\\"secondary\\\" btn-size=\\\"icon\\\"></button-standard> <button-standard click.delegate=\\\"handleReplaceAll()\\\" tooltip-tk=\\\"elements.richTextEditorToolbar.replaceAll\\\" icon-name=\\\"System/Chevron-Right\\\" btn-type=\\\"editor-flat\\\" btn-color=\\\"secondary\\\" btn-size=\\\"icon\\\"></button-standard> </div> <form if.to-view=\\\"showAi\\\" class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--bottom-tray\\\" submit.delegate=\\\"handleRegenerateWithAi()\\\"> <div> <div class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"pages.course.pageOrChapter\\\"></div> <select class=\\\"rich-text-editor-toolbar__bottom-tray-input\\\" change.delegate=\\\"changePage()\\\" value.two-way=\\\"selectedPage\\\"> <option repeat.for=\\\"page of availablePages\\\" model.bind=\\\"page\\\">${page.chapterPrefix} ${page.label}</option> </select> </div> <div> <div class=\\\"rich-text-editor-toolbar__advanced-description\\\" t=\\\"elements.richTextEditorToolbar.textLength\\\"></div> <select class=\\\"rich-text-editor-toolbar__bottom-tray-input\\\" value.two-way=\\\"aiWordCount\\\"> <option model.bind=\\\"10\\\" t=\\\"elements.richTextEditorToolbar.textLengthSmall\\\"></option> <option model.bind=\\\"100\\\" t=\\\"elements.richTextEditorToolbar.textLengthMedium\\\"></option> <option model.bind=\\\"300\\\" t=\\\"elements.richTextEditorToolbar.textLengthLarge\\\"></option> </select> </div> <button-standard if.to-view=\\\"!isGenerating\\\" t=\\\"[btnText]elements.richTextEditorToolbar.regenerateWithAi\\\" submit.to-view=\\\"true\\\" btn-type=\\\"editor-flat\\\" btn-color=\\\"secondary\\\" btn-size=\\\"icon\\\"></button-standard> <button-standard else click.delegate=\\\"cancelAiGeneration()\\\" t=\\\"[btnText]globals.cancel\\\" btn-type=\\\"editor-flat\\\" btn-color=\\\"secondary\\\" btn-size=\\\"icon\\\"></button-standard> </form> <div if.to-view=\\\"showSearchAndReplace || showAi\\\" class=\\\"rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--filler\\\"></div> <template if.to-view=\\\"errorTk && showAi\\\"> <div class=\\\"rich-text-editor-toolbar__row rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--error\\\" t.to-view=\\\"errorTk\\\"></div> <div class=\\\"rich-text-editor-toolbar__row--extra rich-text-editor-toolbar__row--filler\\\"></div> </template> </div></template> \";\n// Exports\nexport default code;","import { bindable, computedFrom } from 'aurelia-framework';\nimport { MathUtils } from 'common/utils/MathUtils';\n\ninterface IFilterRange {\n  from: number;\n  to: number;\n  of: number;\n}\n\nexport class PaginationWidget {\n  @bindable() public limit: number = 10;\n  @bindable() public offset: number = 0;\n  @bindable() public total: number = 0;\n  @bindable() public readonly: boolean = false;\n  @bindable() public numPageQuickLinks: number = 7;\n  @bindable() public newOffsetCallback: (offset: number) => void = () => { console.error('No callback registered!'); };\n  @bindable() public settingsCallback: (() => void) | null = null;\n  @bindable() public showFilterRangeInfoText = true;\n\n  @computedFrom('total', 'limit')\n  private get pages(): number {\n    return Math.ceil(this.total / this.limit);\n  }\n\n  @computedFrom('offset', 'limit')\n  private get currentPage(): number {\n    return Math.ceil(this.offset / this.limit) + 1;\n  }\n\n  @computedFrom('pages', 'currentPage', 'numPageQuickLinks')\n  private get minPage(): number {\n    return Math.max(0, Math.min(this.currentPage - Math.ceil(this.numPageQuickLinks / 2), this.pages - this.numPageQuickLinks));\n  }\n\n  @computedFrom('pages', 'minPage', 'numPageQuickLinks')\n  private get maxPage(): number {\n    return Math.min(this.pages, this.minPage + this.numPageQuickLinks);\n  }\n\n  @computedFrom('minPage', 'maxPage')\n  private get pageQuickLinks(): Array<number> {\n    return Array.from(Array((this.maxPage - this.minPage)), (_, x) => x + this.minPage);\n  }\n\n  @computedFrom('numPageQuickLinks')\n  private get shortenEntryText(): boolean {\n    return this.numPageQuickLinks < 4;\n  }\n\n  @computedFrom('offset', 'limit', 'total')\n  private get filterRange(): IFilterRange {\n    return {\n      from: MathUtils.clamp(this.offset + 1, this.total, 0),\n      to: MathUtils.clamp(this.offset + this.limit, this.total, 0),\n      of: this.total\n    };\n  }\n\n  private showPage(page: number): void {\n    (document.activeElement as HTMLElement)?.blur();\n    this.newOffsetCallback((page - 1) * this.limit);\n  }\n\n  private nextPage(): void {\n    this.showPage(Math.min(this.currentPage + 1, this.pages));\n  }\n\n  private previousPage(): void {\n    this.showPage(Math.max(this.currentPage - 1, 0));\n  }\n}\n","import { autoinject, bindable, observable } from 'aurelia-framework';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { StringUtils } from 'common/utils/StringUtils';\n\n@autoinject()\nexport class PhoneInput {\n  @bindable() public value: string = '';\n  @bindable() public callingCodes: string[] = [];\n  @bindable() public defaultCallingCode = '43';\n  @bindable() public isInvalid = false;\n  @bindable() public disabled = false;\n  @bindable() public changeNotifier = false;\n  @bindable() public placeholderTk = 'globals.phone';\n\n  @observable() private callingCodeValue = '';\n  @observable() private nationalNumberValue = '';\n\n  private element: HTMLElement;\n  private isAttached = false;\n\n  constructor(\n    element: Element,\n  ) {\n    this.element = element as HTMLElement;\n  }\n\n  public attached(): void {\n    this.isAttached = true;\n    this.valueChanged();\n  }\n\n  public detached(): void {\n    this.isAttached = false;\n  }\n\n  private valueChanged(): void {\n    if(!this.isAttached) return;\n\n    const { callingCode = '', nationalNumber = '' } = StringUtils.parsePhoneNumber(this.value, this.callingCodes);\n    // if the national number part is empty, we still keep the current value of the calling code select box\n    this.callingCodeValue = (nationalNumber ? callingCode : this.callingCodeValue) || this.defaultCallingCode;\n    this.nationalNumberValue = nationalNumber;\n  }\n\n  private callingCodeValueChanged(): void {\n    if(!this.isAttached) return;\n    this.updateValue();\n  }\n\n  private nationalNumberValueChanged(): void {\n    if(!this.isAttached) return;\n    this.updateValue();\n  }\n\n  private updateValue(): void {\n    // if the national number is empty, consider the whole number empty\n    this.value = this.nationalNumberValue\n      ? StringUtils.formatPhoneNumber(this.callingCodeValue, this.nationalNumberValue)\n      : '';\n  }\n\n  private forwardBlur(): void {\n    DomUtils.fireEvent(this.element, { name: 'blur', bubbles: false });\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { IPictureDto } from 'common/dto/course/content/course.interface';\nimport { GalleryMediaType } from 'common/enum/gallery.enum';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\n\n@autoinject()\nexport class PictureSelectForm {\n  @bindable() public courseId: number | null = null;\n  @bindable() public picture: IPictureDto | null = null;\n  @bindable() public pictureId: number | null = null;\n  @bindable() public pictureTitle: string | null = null;\n  @bindable() public pictureUrl: string | null = null;\n  @bindable() public pictureFileName: string | null = null;\n  @bindable() public showPictureTitle: boolean = false;\n\n  constructor(private dialogService: CustomDialogService) { }\n\n  private handleDeleteImageClick(): void {\n    this.pictureId = null;\n    this.picture = null;\n    this.pictureTitle = null;\n    this.pictureUrl = null;\n    this.pictureFileName = null;\n  }\n\n  private async handleSelectImageClick(): Promise<void> {\n    const result = await this.dialogService.showMediaSelect({\n      selectedMediaType: GalleryMediaType.Picture\n    });\n    if(result?.type === GalleryMediaType.Picture) {\n      this.pictureId = result.value.id;\n      this.pictureTitle = result.value.title;\n      this.pictureUrl = result.value.url;\n      this.pictureFileName = result.value.filename;\n      this.picture = {\n        id: result.value.id,\n        url: result.value.url,\n        blurHash: result.value.blurHash,\n        width: result.value.width,\n        height: result.value.height\n      };\n    }\n  }\n}","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/presentation-viewer/presentation-viewer'));\n}\n","import { autoinject, bindable, computedFrom, observable } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { FullscreenableContainer } from 'common/aurelia-components/fullscreenable-container/fullscreenable-container';\nimport { IWhiteboardChangedEventData, Whiteboard, WhiteboardBackgroundColor, WhiteboardColor, WhiteboardTool } from 'common/aurelia-components/whiteboard/whiteboard';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { DISTINAR_PRESENTATION_CONTEXT } from 'common/constants/content.editor.constants';\nimport { IPresentationListEntryDto } from 'common/dto/course/content/course.interface';\nimport { IPageDto } from 'common/dto/course/content/page.interface';\nimport { ISlideDto } from 'common/dto/course/content/slide.interface';\nimport { IPresentationPdfOperationDto } from 'common/dto/course/management/presentation.management.interface';\nimport { PresentationOwnership } from 'common/enum/course.enum';\nimport { GalleryMediaType } from 'common/enum/gallery.enum';\nimport { KeyboardKey } from 'common/enum/keyboard.key.enum';\nimport { RoleName } from 'common/enum/role.enums';\nimport * as PresentationContentMutations from 'common/store/mutations/presentation.content.mutations';\nimport * as PresentationContextMutations from 'common/store/mutations/presentation.context.mutations';\nimport { navigationInfoSelectorCreator, presentationIdSelectorCreator, selectTitle, slideIdSelectorCreator } from 'common/store/selectors/presentation.context.selectors';\nimport { IPresentationContentWithPerformanceData, IPresentationNavigationInfo } from 'common/store/store.interface';\nimport { AppUtils } from 'common/utils/AppUtils';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { FullscreenUtils } from 'common/utils/FullscreenUtils';\nimport { FunctionUtils } from 'common/utils/FunctionUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { CourseCacheService } from '../../../services/CourseCacheService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { EventService } from '../../../services/EventService';\nimport { ToastService } from '../../../services/ToastService';\nimport { WhiteboardService } from '../../../services/WhiteboardService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { MediaShareApiService } from '../../../services/api/MediaShareApiService';\nimport { PresentationManagementApiService } from '../../../services/api/PresentationManagementApiService';\nimport { IQuickDistinarData, IState } from '../../../store/state';\nimport { ISearchSelectData, ISearchSelectGroup, SasSelectedItemChangedEvent, SearchSelectGroupDisplayMode } from '../search-and-select/search-and-select';\nimport { LinkBehavior } from '../toast-controller/toasts/default-toast/default-toast';\n\n@autoinject()\nexport class PresentationViewer {\n  public static MOUSE_MOVED_RECENTLY_TIMEOUT = 1000;\n\n  @bindable() public presentationId: number | null = null;\n  @bindable() public contextIdentifier: string | null = null;\n\n  @selectState(PresentationViewer, slideIdSelectorCreator, ['contextIdentifier']) private slideId?: number;\n  @selectState(PresentationViewer, navigationInfoSelectorCreator, ['contextIdentifier']) private navigationInfo?: IPresentationNavigationInfo | null;\n\n  @observable() private isFullscreen = false;\n\n  private domElement: HTMLElement;\n  private subscriptionManager: SubscriptionManager;\n\n  private availableCoursePresentations: Array<IPresentationListEntryDto> = [];\n  private availableUserPresentations: Array<IPresentationListEntryDto> = [];\n  private availableSharedPresentations: Array<IPresentationListEntryDto> = [];\n  private allAvailablePresentations: Array<number> = [];\n  private presentationContentById: {\n    [presentationId: number]: IPresentationContentWithPerformanceData | undefined;\n  } = {};\n\n  private selectedCourseId: number | null = null;\n  private selectedChapter: IPageDto | null = null;\n  private selectedSlide: ISlideDto | null = null;\n\n  private isProvider: boolean | null = null;\n  private tenantId: number | null = null;\n  private userId: number | null = null;\n\n  private showWhiteboard = false;\n  private whiteboard: Whiteboard | null = null;\n\n  private searchSelectPresentations: ISearchSelectData | null = null;\n  private searchSelectValue = '';\n\n  private mouseMovedRecently = false;\n  private mouseInsideControlElement = false;\n  private debouncedResetMouseMoved = FunctionUtils.debounce(() => { this.mouseMovedRecently = false; }, PresentationViewer.MOUSE_MOVED_RECENTLY_TIMEOUT);\n  private fullscreenableContainer: FullscreenableContainer | null = null;\n\n  private loading = false;\n  private showQuickEvents = false;\n  private isFrp = AppUtils.isFrp;\n  private supportsFullscreen = FullscreenUtils.isEnabled;\n\n  constructor(\n    element: Element,\n    subscriptionManagerService: SubscriptionManagerService,\n    private courseCacheService: CourseCacheService,\n    private storeService: StoreService<IState>,\n    private courseManagementApiService: CourseManagementApiService,\n    private presentationManagementApiService: PresentationManagementApiService,\n    private whiteboardService: WhiteboardService,\n    private eventService: EventService,\n    private toastService: ToastService,\n    private dialogService: CustomDialogService,\n    private mediaShareApiService: MediaShareApiService,\n    private i18n: I18N\n  ) {\n    this.domElement = element as HTMLElement;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole,\n      state => !!state.distinarPresentationResumeData?.slideId,\n    ], (state, currentRole, _hasPresentationResumeData) => { // recent changes may have broke the resume-presentation feature, keep an eye on this\n      this.isProvider = currentRole?.role === RoleName.Provider;\n      this.tenantId = currentRole?.tenantId ?? null;\n      this.updateAvailablePresentations();\n      this.showQuickEvents = !!state?.currentTenant?.showQuickEvents;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.presentationContentById\n    ], (_, presentationContentById) => {\n      this.presentationContentById = presentationContentById;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.user.id,\n    ], (_, userId) => {\n      this.userId = userId;\n    });\n\n    this.updateAvailablePresentations();\n\n    this.subscriptionManager.subscribeToKeyboardEvent(window, KeyboardKey.ArrowLeft, () => {\n      if(this.dialogService.isAnyDialogOpen()) return;\n      this.onNavigateToPrevious();\n    });\n\n    this.subscriptionManager.subscribeToKeyboardEvent(window, KeyboardKey.ArrowRight, () => {\n      if(this.dialogService.isAnyDialogOpen()) return;\n      this.onNavigateToNext();\n    });\n\n    if(this.presentationId != null) this.setPresentation(this.presentationId);\n\n    const { contextIdentifier } = this;\n    if(contextIdentifier == null) return;\n    this.storeService.subscribeMemoized(this, selectTitle, title => {\n      this.searchSelectValue = title ?? '';\n    }, { contextIdentifier });\n\n    this.storeService.subscribeMemoized(this, presentationIdSelectorCreator, id => {\n      if(id != null) this.setPresentation(id);\n    }, { contextIdentifier });\n  }\n\n  protected detached(): void {\n    this.debouncedResetMouseMoved.cancel();\n    this.mouseMovedRecently = false;\n    this.subscriptionManager.disposeSubscriptions();\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  @computedFrom('slideId')\n  private get noSelection(): boolean {\n    return this.slideId == null;\n  }\n\n  @computedFrom('contextIdentifier', 'isProvider', 'isFrp', 'showQuickEvents')\n  private get canStartQuickDistinar(): boolean {\n    return !this.isProvider\n      && this.contextIdentifier !== DISTINAR_PRESENTATION_CONTEXT\n      && !this.isFrp\n      && this.showQuickEvents;\n  }\n\n  @computedFrom('navigationInfo')\n  private get currentSlideNavigationInfo(): ({ index: number; total: number }) {\n    return {\n      index: this.navigationInfo?.slideIndex ?? 0,\n      total: this.navigationInfo?.totalSlides ?? 0,\n    };\n  }\n\n  private presentationIdChanged(): void {\n    if(this.presentationId != null) this.setPresentation(this.presentationId);\n  }\n\n  private isFullscreenChanged(): void {\n    if(this.contextIdentifier == null) return;\n    void this.storeService.dispatch(PresentationContextMutations.setIsFullscreen, this.contextIdentifier, this.isFullscreen);\n  }\n\n  private contextIdentifierChanged(): void {\n    if(this.contextIdentifier == null) return;\n    // update again if presentation is already set (but hasControls is not)\n\n    this.storeService.dispatch(PresentationContextMutations.setHasControls, this.contextIdentifier, true);\n  }\n\n  private slideIdChanged(newSlideId?: number | null, oldSlideId?: number | null): void {\n    if(oldSlideId != null && this.showWhiteboard) this.saveSlideWhiteboard(oldSlideId);\n    if(newSlideId != null) this.restoreOrClearSlideWhiteboard(newSlideId);\n  }\n\n  private saveSlideWhiteboard(slideId: number): void {\n    if(this.whiteboard == null) return;\n    const whiteboardSvg = this.whiteboard?.cloneSvgElement();\n    if(whiteboardSvg != null) this.whiteboardService.setSlideWhiteboard(slideId, {\n      svgData: whiteboardSvg,\n      backgroundColor: this.whiteboard.getBackgroundColor(),\n    });\n  }\n\n  private restoreOrClearSlideWhiteboard(slideId: number): void {\n    const storedWhiteboard = this.whiteboardService.getSlideWhiteboard(slideId);\n    if(storedWhiteboard?.svgData != null) {\n      this.whiteboard?.setSvgElement(storedWhiteboard.svgData);\n      this.whiteboard?.setBackgroundColor(storedWhiteboard.backgroundColor);\n      this.updateWhiteboardForAttendees(\n        this.showWhiteboard ? this.whiteboard?.exportSvgAsDataUrl() ?? null : null,\n        this.showWhiteboard ? storedWhiteboard.backgroundColor : null,\n      );\n    } else {\n      this.whiteboard?.clear();\n      this.whiteboard?.setBackgroundColor(WhiteboardBackgroundColor.None);\n      this.updateWhiteboardForAttendees(null, null);\n    }\n  }\n\n  private onMaximizeClicked(): void {\n    assertNotNullOrUndefined(this.fullscreenableContainer, 'can\\'t PresentationViewer.handleMaximizeClick without a contentWrapper');\n    this.fullscreenableContainer.requestFullscreen();\n  }\n\n  private onMinimizeClicked(): void {\n    assertNotNullOrUndefined(this.fullscreenableContainer, 'can\\'t PresentationViewer.handleMinimizeClick without a contentWrapper');\n    this.fullscreenableContainer.exitFullscreen();\n  }\n\n  private async onNavigateToPrevious(): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    await this.storeService.dispatch(PresentationContextMutations.navigateBack, this.contextIdentifier);\n  }\n\n  private async onNavigateToNext(): Promise<void> {\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n    await this.storeService.dispatch(PresentationContextMutations.navigateForward, this.contextIdentifier);\n  }\n\n  private onToggleWhiteboardClicked(): void {\n    this.showWhiteboard = !this.showWhiteboard;\n\n    if(this.showWhiteboard) {\n      this.whiteboard?.setTool(WhiteboardTool.Freehand);\n      this.whiteboard?.setColor(WhiteboardColor.Marine);\n      this.whiteboard?.setToolSize(4);\n    } else if(this.slideId != null) {\n      // save on disabling whiteboard, to allow slideChange without save if no whiteboard change happened\n      this.saveSlideWhiteboard(this.slideId);\n    }\n  }\n\n  private onPresentationSelected(event: SasSelectedItemChangedEvent<number>): void {\n    void this.setPresentation(event.detail.id);\n  }\n\n  private onWhiteboardChanged(event: CustomEvent<IWhiteboardChangedEventData>): void {\n    this.updateWhiteboardForAttendees(\n      this.showWhiteboard ? event.detail.svgData : null,\n      this.showWhiteboard ? event.detail.backgroundColor : null,\n    );\n  }\n\n  private updateWhiteboardForAttendees(svgData: string | null, backgroundColor: string | null): void {\n    if(this.slideId == null) return;\n    DomUtils.fireEvent<ISlideWhiteboardChangedEventDetail>(this.domElement, {\n      name: 'slide-whiteboard-changed',\n      bubbles: true,\n      detail: { slideId: this.slideId, svgData, backgroundColor },\n    });\n  }\n\n  private elementIsInsideControlElement(element: HTMLElement): boolean {\n    return DomUtils.isInsideElementWithDataAttribute(element, 'controlElement');\n  }\n\n  private handleMousemove(event: MouseEvent): boolean {\n    assertNotNullOrUndefined(event.target, 'event without a target in PresentationViewer.handleMousemove');\n    this.mouseInsideControlElement = this.elementIsInsideControlElement(event.target as HTMLElement);\n\n    this.mouseMovedRecently = true;\n    this.debouncedResetMouseMoved();\n    return true;\n  }\n\n  private async startQuickDistinar(): Promise<void> {\n    assertNotNullOrUndefined(this.tenantId, 'tenantId cannot be null or undefined');\n    assertNotNullOrUndefined(this.userId, 'userId cannot be null or undefined');\n    assertNotNullOrUndefined(this.selectedCourseId, 'selectedCourseId cannot be null or undefined');\n    assertNotNullOrUndefined(this.slideId, 'slideId cannot be null or undefined');\n    const data: IQuickDistinarData = {\n      slideId: this.slideId,\n      presentationId: this.selectedCourseId\n    };\n    await this.eventService.createAndStartQuickDistinar(this.tenantId, this.userId, data);\n  }\n\n  private async startGeneratePdf(): Promise<void> {\n    assertNotNullOrUndefined(this.selectedCourseId, 'presentation id must be defined');\n\n    const { operationId } = await this.presentationManagementApiService.saveAsPdf(this.selectedCourseId);\n    this.toastService.operation<IPresentationPdfOperationDto>({\n      operationId,\n      operationName: this.i18n.tr('globals.generatePdf'),\n      successCallback: async result => {\n        const generateResult = await this.dialogService.showPdfGeneratedDialog({\n          ...result,\n          operationId\n        });\n        const uploadedSomething = generateResult && generateResult.fileIds.length > 0;\n        if(uploadedSomething && generateResult.share) {\n          const shares = await this.dialogService.showMediaShare({\n            // We always take the first file, no matter how many are saved\n            mediaId: generateResult.fileIds[0],\n            mediaType: GalleryMediaType.File,\n          });\n          if(shares && shares.mediaId && shares.mediaType) {\n            try {\n              await this.mediaShareApiService.setMediaShares({\n                mediaId: shares.mediaId,\n                mediaType: shares.mediaType,\n                shares: shares.shares,\n              });\n              this.toastService.success('elements.distinarCourseView.share.success', {}, {\n                behavior: LinkBehavior.Navigate,\n                textTk: 'globals.editShares',\n                linkHref: '/share'\n              });\n            } catch(e) {\n              this.toastService.error('elements.distinarCourseView.share.error', {}, null, {\n                behavior: LinkBehavior.Navigate,\n                textTk: 'globals.editShares',\n                linkHref: '/share'\n              });\n            }\n          }\n        }\n\n        return uploadedSomething ?? false;\n      }\n    });\n  }\n\n  private async setPresentation(presentationId: number): Promise<void> {\n    await this.courseCacheService.ensurePresentationInCache(presentationId);\n\n    if(this.selectedCourseId === presentationId) return;\n\n    this.selectedCourseId = presentationId;\n    if(this.searchSelectPresentations) this.searchSelectPresentations.defaultItemId = this.selectedCourseId;\n\n    assertNotNullOrUndefined(this.contextIdentifier, 'contextIdentifier cannot be null or undefined');\n\n    const content = this.presentationContentById[presentationId];\n    if(!content) return;\n    this.loading = true;\n    await this.storeService\n      .pipe(PresentationContentMutations.setContent, content)\n      .pipe(PresentationContextMutations.setInitialState, this.contextIdentifier, presentationId)\n      .pipe(PresentationContextMutations.setIsFullscreen, this.contextIdentifier, this.isFullscreen)\n      /**\n       * always true in AFE, as only students lack controls in distinar\n       *\n       * only used for picture fullscreen controls at the moment\n       */\n      .pipe(PresentationContextMutations.setHasControls, this.contextIdentifier, true)\n      .dispatch();\n    this.loading = false;\n  }\n\n  private async updateAvailablePresentations(): Promise<void> {\n    if(this.isProvider == null) return;\n\n    if(this.isProvider) {\n      const availableCoursePresentations = await this.courseManagementApiService.getAvailablePresentationCourses();\n      this.availableCoursePresentations = availableCoursePresentations.map(p => ({ id: p.id, title: p.title, ownership: PresentationOwnership.Provider }));\n    } else {\n      if(this.tenantId == null) return;\n      const presentationlist = await this.presentationManagementApiService.getPresentationOverview();\n      this.availableUserPresentations = presentationlist.filter(presentation => presentation.ownership === PresentationOwnership.Own);\n      this.availableCoursePresentations = presentationlist.filter(presentation => presentation.ownership === PresentationOwnership.Provider);\n      this.availableSharedPresentations = presentationlist.filter(presentation => presentation.ownership === PresentationOwnership.Shared);\n    }\n\n    const allPresentations = this.availableCoursePresentations\n      .concat(this.availableUserPresentations)\n      .concat(this.availableSharedPresentations);\n\n    this.allAvailablePresentations = allPresentations.map(p => p.id);\n\n    // prepare presentation data for search-select element\n    this.searchSelectPresentations = {\n      ...this.searchSelectPresentations,\n      noResultsTextTk: 'elements.distinarCourseView.searchSelectNoResults',\n      searchIndex: [],\n    };\n    // add course-presentations to search-select\n    if(this.availableCoursePresentations.length > 0) {\n      this.searchSelectPresentations.searchIndex.push(\n        this.prepareSearchSelectGroups('elements.distinarCourseView.searchResultGroupCourse', this.availableCoursePresentations)\n      );\n    }\n    // add user owned presentations to search-select\n    if(this.availableUserPresentations.length > 0) {\n      this.searchSelectPresentations.searchIndex.push(\n        this.prepareSearchSelectGroups('elements.distinarCourseView.searchResultGroupMyOwn', this.availableUserPresentations)\n      );\n    }\n    // add shared with tenant presentations to search-select\n    if(this.availableSharedPresentations.length > 0) {\n      this.searchSelectPresentations.searchIndex.push(\n        this.prepareSearchSelectGroups('elements.distinarCourseView.searchResultGroupSharedWithTenant', this.availableSharedPresentations)\n      );\n    }\n  }\n\n  private prepareSearchSelectGroups(\n    textTk: string,\n    presentations: IPresentationListEntryDto[]\n  ): ISearchSelectGroup {\n    return {\n      displaySettings: {\n        displayMode: SearchSelectGroupDisplayMode.TextTk,\n        textTk,\n      },\n      items: presentations.map(presentation => ({\n        text: presentation.title,\n        id: presentation.id,\n      })).sort((a, b) => a.text.localeCompare(b.text)),\n    };\n  }\n}\n\nexport interface ISlideWhiteboardChangedEventDetail extends IWhiteboardChangedEventData { slideId: number }\nexport type SlideWhiteboardChangedEvent = CustomEvent<ISlideWhiteboardChangedEventDetail>;\n\nexport type PresentationCourseSelectedEventDetail = { courseId: number };\nexport type PresentationCourseSelectedEvent = CustomEvent<PresentationCourseSelectedEventDetail>;\n\nexport type PresentationChapterSelectedEventDetail = { chapter: IPageDto | null };\nexport type PresentationChapterSelectedEvent = CustomEvent<PresentationChapterSelectedEventDetail>;\n\nexport type PresentationSlideSelectedEventDetail = { slide: ISlideDto };\nexport type PresentationSlideSelectedEvent = CustomEvent<PresentationSlideSelectedEventDetail>;\n","import { IBlockEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { bindable, autoinject } from 'aurelia-framework';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { IQuestionDto } from 'common/dto/course/content/question.interface';\nimport { QuestionsApiService } from '../../../services/api/QuestionsApiService';\nimport { FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { VirtualFilterField } from '../custom-table/custom-table-helpers';\n\n@autoinject\nexport class PreviewBlock {\n  @bindable public courseId: number | null = null;\n  @bindable public block: IBlockEditResponseDto<string> | null = null;\n\n  private BlockType = BlockType;\n  private questions: IQuestionDto[] = [];\n  private currentQuestion: IQuestionDto | null = null;\n  private currentQuestionIndex: number = 0;\n\n  constructor(private questionsApi: QuestionsApiService) { }\n\n  private blockChanged(): void {\n    if(this.block?.type === BlockType.QUESTION || this.block?.type === BlockType.MP_QUESTION) {\n      this.updateQuestions();\n    }\n  }\n\n  private async updateQuestions(): Promise<void> {\n    const filter = {\n      offset: 0,\n      limit: 500,\n      filter: [\n        {\n          field: 'id' as VirtualFilterField,\n          type: FilterOperationType.In,\n          value: this.block?.questionIds,\n        }\n      ]\n    };\n    const results = await this.questionsApi.getAllAvailableQuestions(filter);\n    this.questions = results.rows;\n    this.currentQuestion = this.questions?.[0] ?? null;\n  }\n\n  private newOffsetCallback = (offset: number): void => {\n    this.currentQuestion = this.questions?.[offset] ?? null;\n    this.currentQuestionIndex = offset;\n  };\n}","import { bindable } from 'aurelia-framework';\n\nexport enum PreviewImageSize {\n  Small = 'small',\n  Regular = 'regular',\n}\n\nexport class PreviewImage {\n  @bindable() public imageSrc: string | null = null;\n  @bindable() public size: PreviewImageSize = PreviewImageSize.Regular;\n  private PreviewImageSize = PreviewImageSize;\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\n\n@autoinject()\nexport class ProgressCircle {\n  @bindable() public progress: number = 0;\n  @bindable() public size: number = 45;\n\n  private viewBoxSize = 40;\n  private strokeWidth = 3;\n\n  private progressCircle: SVGCircleElement | null = null;\n\n  @computedFrom('strokeWidth', 'viewBoxSize')\n  private get radius(): number {\n    return (this.viewBoxSize / 2) - (this.strokeWidth * 1.5);\n  }\n\n  @computedFrom('radius')\n  private get circumference(): number {\n    return this.radius * 2 * Math.PI;\n  }\n\n  @computedFrom('circumference', 'progress')\n  private get progressOffset(): number {\n    return this.circumference - this.progress / 100 * this.circumference;\n  }\n}\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { IBlockDto } from 'common/dto/course/content/block.interface';\n\n@autoinject()\nexport class QuestionBlockPreview {\n  @bindable() public block: IBlockDto | null = null;\n}\n\n","import { autoinject, bindable } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { QuestionType } from 'common/dto/course/content/course.types';\nimport { IQuestionDto } from 'common/dto/course/content/question.interface';\nimport { IPageEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { QuestionsApiService } from '../../../services/api/QuestionsApiService';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { FilterUtils } from '../../../utils/FilterUtils';\nimport { CustomTableCell } from '../custom-table/custom-table-cell/custom-table-cell';\nimport { createPrepareServersideSortingFunction, prepareServersideFiltering, VirtualFilterField } from '../custom-table/custom-table-helpers';\nimport { IColumnDefinition } from '../custom-table/custom-table-interfaces';\nimport { IconButtonCell } from '../custom-table/icon-button-cell/icon-button-cell';\n\nenum ListMode {\n  Quiz = 'quiz',\n  Course = 'course',\n}\n\n/**\n * @event {AddQuestionEvent} add-question\n */\n@autoinject()\nexport class QuestionListWidget {\n  @bindable() public courseId: number | null = null;\n  @bindable() public selectedQuestionsIds: number[] | null = null;\n  @bindable() public showEdit: boolean = true;\n  @bindable() public listMode: ListMode = ListMode.Course;\n  @bindable() public learnChaptersEnabled: boolean = true;\n  @bindable() public excludeAssessmentOnly: boolean = false;\n  @bindable() public tableRefreshVersion = 0;\n\n  private questionsLoading = false;\n  private chaptersLoading = false;\n  private tableRefreshVersionInternal = 0;\n\n  private chapters: IPageEditResponseDto[] = [];\n  private selectedChapter: IPageEditResponseDto | null = null;\n\n  private allowedQuestionTypes: QuestionType[] = Object.values(QuestionType);\n  private countByType: { [key: string]: number } = {};\n  private domElement: HTMLElement;\n\n  private filter: IFilterDto<IQuestionDto> = {\n    offset: 0,\n    limit: 10,\n    order: [{\n      field: 'text',\n      dir: ColumnSortDirection.ASC,\n    }],\n    filter: []\n  };\n\n  private tableColumns: Array<IColumnDefinition<CustomTableCell<IQuestionDto> | IconButtonCell<IQuestionDto>>> = [\n    {\n      id: 'externalId',\n      nameTk: 'elements.questionListWidget.externalId',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => q.externalId ?? '-'\n      },\n      prepareSorting: createPrepareServersideSortingFunction(this.filter, 'importId' as VirtualFilterField),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: q => prepareServersideFiltering(this.filter, 'importId' as VirtualFilterField, q),\n      },\n      shrink: true,\n    },\n    {\n      id: 'title',\n      nameTk: 'globals.title',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => q.type === QuestionType.LT ? QuestionUtils.splitFillInTheBlankQuestionIntoArray(q.text).join(' |') : q.text,\n        spanClasses: () => 'btn btn-link text-left',\n        callback: q => this.showQuestionPreview(q)\n      },\n      prepareSorting: createPrepareServersideSortingFunction(this.filter, 'text'),\n      filter: {\n        placeholderTk: 'globals.filter',\n        prepare: q => prepareServersideFiltering(this.filter, 'text', q),\n      },\n      wrap: true\n    },\n    {\n      id: 'questionType',\n      nameTk: 'elements.questionListWidget.questionType',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => this.i18N.tr(`globals.questionTypes.questionType${q.type}`)\n      },\n      filter: this.getQuestionTypeFilter(),\n      shrink: true,\n    },\n    {\n      id: 'navigation',\n      nameTk: '',\n      cellViewModel: IconButtonCell,\n      cellOptions: {\n        iconName: 'System/Tabler/Edit',\n        callback: q => { this.editQuestion(q.id); }\n      },\n      shrink: true,\n      visible: () => this.showEdit\n    },\n    {\n      id: 'add',\n      nameTk: '',\n      cellViewModel: IconButtonCell,\n      cellOptions: {\n        iconNameTransformer: q => this.canAddQuestion(q) ? 'System/Tabler/Check' : 'System/Tabler/Plus-Circle',\n        callback: q => { this.addQuestion(q); }\n      },\n      shrink: true,\n      visible: () => !!this.selectedQuestionsIds\n    },\n  ];\n\n  constructor(\n    private questionsApi: QuestionsApiService,\n    private courseManagementApiService: CourseManagementApiService,\n    private customDialogService: CustomDialogService,\n    private i18N: I18N,\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  private async fetchTableData(limit: number, offset: number): Promise<{ total: number; data: Array<IQuestionDto> }> {\n    if(this.questionsLoading || !this.courseId) return { data: [], total: 0 };\n\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n    this.filter = FilterUtils.removeDuplicateFilterFields(\n      this.filter,\n      {\n        field: 'courseId' as VirtualFilterField,\n        type: FilterOperationType.Eq,\n        value: this.courseId,\n      }\n    );\n    this.getQuestionTypeCount();\n\n    if(this.listMode === ListMode.Quiz) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'type',\n          type: FilterOperationType.In,\n          value: this.allowedQuestionTypes,\n        }\n      );\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'learningOnly',\n          type: FilterOperationType.Eq,\n          value: false,\n        }\n      );\n    }\n\n    if(!this.learnChaptersEnabled) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'isTrainingOnly' as VirtualFilterField,\n          type: FilterOperationType.Eq,\n          value: true,\n        }\n      );\n    }\n\n    if(this.excludeAssessmentOnly) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'assessmentOnly' as VirtualFilterField,\n          type: FilterOperationType.Eq,\n          value: false,\n        }\n      );\n    }\n\n    this.questionsLoading = true;\n    const results = await this.questionsApi.getAllAvailableQuestions(this.filter);\n    this.questionsLoading = false;\n    return { data: results.rows, total: results.count };\n  }\n\n  private async getCourseChapters(): Promise<void> {\n    if(!this.courseId || this.chaptersLoading) return;\n    this.chaptersLoading = true;\n    let chapters = await this.courseManagementApiService.getPages(this.courseId);\n    chapters = chapters\n      .filter(page => page.type === 'chapter' && page.parentId === null)\n      .sort((a, b) => a.title.localeCompare(b.title));\n    if(!this.learnChaptersEnabled) {\n      chapters = chapters.filter(chapter => chapter.isTrainingOnly);\n    }\n    this.chapters = chapters;\n    this.chaptersLoading = false;\n  }\n\n  private async getQuestionTypeCount(): Promise<void> {\n    assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n    const filter: IFilterDto<IQuestionDto> = {\n      filter: [{\n        field: 'courseId' as VirtualFilterField,\n        type: FilterOperationType.Eq,\n        value: this.courseId,\n      }]\n    };\n    if(this.listMode === ListMode.Quiz) {\n      filter.filter = [\n        {\n          field: 'learningOnly',\n          type: FilterOperationType.Eq,\n          value: false,\n        }, ...filter.filter ?? []];\n    }\n    if(!this.learnChaptersEnabled) {\n      filter.filter = [\n        {\n          field: 'isTrainingOnly' as VirtualFilterField,\n          type: FilterOperationType.Eq,\n          value: true,\n        }, ...filter.filter ?? []];\n    }\n    if(this.selectedChapter) {\n      filter.filter = [{\n        field: 'chapterId' as VirtualFilterField,\n        type: FilterOperationType.Eq,\n        value: this.selectedChapter.id,\n      }, ...filter.filter ?? []];\n    }\n    this.countByType = await this.questionsApi.getCountByType(filter);\n    const questionTypeColumnIndex = this.tableColumns.findIndex(column => column.id === 'questionType');\n    if(this.tableColumns?.[questionTypeColumnIndex].filter) {\n      this.tableColumns[questionTypeColumnIndex].filter = this.getQuestionTypeFilter();\n    }\n  }\n\n  private handleSelectedChapterChanged(): void {\n    if(this.selectedChapter) {\n      this.filter = FilterUtils.removeDuplicateFilterFields(\n        this.filter,\n        {\n          field: 'chapterId' as VirtualFilterField,\n          type: FilterOperationType.Eq,\n          value: this.selectedChapter.id,\n        }\n      );\n      this.tableRefreshVersionInternal++;\n    } else {\n      this.resetSelectedChapterFilter();\n    }\n  }\n\n  private resetSelectedChapterFilter(): void {\n    this.selectedChapter = null;\n    const filterIndex: number = this.filter.filter?.findIndex(filter => filter.field as VirtualFilterField === 'chapterId') ?? -1;\n    this.filter.filter?.splice(filterIndex, 1);\n    this.tableRefreshVersionInternal++;\n  }\n\n  private async courseIdChanged(): Promise<void> {\n    await this.getCourseChapters();\n    this.tableRefreshVersionInternal++;\n  }\n\n  private async tableRefreshVersionChanged(): Promise<void> {\n    await this.getCourseChapters();\n    this.tableRefreshVersionInternal++;\n  }\n\n  private selectedQuestionsIdsChanged(): void {\n    this.tableRefreshVersionInternal++;\n  }\n\n  private listModeChanged(): void {\n    if(this.listMode === ListMode.Quiz) {\n      this.allowedQuestionTypes = [QuestionType.LT, QuestionType.MC, QuestionType.SC, QuestionType.TF];\n    }\n    this.tableRefreshVersionInternal++;\n  }\n\n  private showQuestionPreview(question: IQuestionDto): void {\n    this.customDialogService.showQuestionPreview(question);\n  }\n\n  private canAddQuestion(question: IQuestionDto): boolean {\n    return !!this.selectedQuestionsIds?.find(id => id === question.id);\n  }\n\n  private addQuestion(question: IQuestionDto): void {\n    DomUtils.fireEvent(this.domElement, {\n      detail: {\n        questionId: question.id,\n      },\n      bubbles: false,\n      name: 'add-question',\n    });\n  }\n\n  private async editQuestion(questionId: number): Promise<void> {\n    assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n    const result = await this.customDialogService.showEditQuestionDialog({\n      courseId: this.courseId,\n      questionId,\n      dialogService: this.customDialogService\n    });\n    if(result) this.tableRefreshVersionInternal++;\n  }\n\n  private getQuestionTypeFilter(): QuestionTypeFilter {\n    return {\n      placeholderTk: 'elements.questionListWidget.allQuestionTypes',\n      prepare: q => prepareServersideFiltering(this.filter, 'type', q, FilterOperationType.Eq),\n      options: this.allowedQuestionTypes\n        .filter(type => this.countByType?.[type] != null && this.countByType[type] !== 0)\n        .map(type => (\n          {\n            keyTk: `elements.questionListWidget.questionType${type}`,\n            tkParams: { count: `${this.countByType[type]}` },\n            value: type\n          }\n        ))\n    };\n  }\n}\n\nexport type AddQuestionEvent = CustomEvent<{ questionId: number }>;\ntype QuestionTypeFilter = {\n  placeholderTk: string;\n  prepare: (value: string) => void;\n  options: Array<{ keyTk?: string; tkParams?: Record<string, string>; value: string }>;\n};\n","import { bindable, computedFrom } from 'aurelia-framework';\nimport { QuestionType } from 'common/dto/course/content/course.types';\nimport { IQuestionDto } from 'common/dto/course/content/question.interface';\nimport { IQuizQuestionDto } from 'common/dto/course/quiz/quiz.question.interface';\nimport { VerticalDisplayMode } from 'common/enum/responsiveness-mode.enum';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\n\nenum ImageMode {\n  WithImage = 'with-image',\n  WithoutImage = 'without-image',\n}\n\nexport class QuestionPreviewWidget {\n  @bindable() public question: IQuestionDto | null = null;\n  @bindable() public verticalDisplayMode = VerticalDisplayMode.Scale;\n\n  private VerticalDisplayMode = VerticalDisplayMode;\n  private ImageMode = ImageMode;\n  private imageMode = ImageMode.WithoutImage;\n  private formattedQuestion: IQuizQuestionDto | null = null;\n\n  private questionChanged(): void {\n    if(this.question) {\n      this.formattedQuestion = this.formatQuestion(this.question);\n      this.imageMode = this.formattedQuestion.pictureUrl ? ImageMode.WithImage : ImageMode.WithoutImage;\n    }\n  }\n\n  private formatQuestion(question: IQuestionDto): IQuizQuestionDto {\n    return QuestionUtils.quizQuestionFromQuestion(question, false, null);\n  }\n\n  @computedFrom('formattedQuestion.type')\n  private get isRegularQuestion(): boolean {\n    if(!this.formattedQuestion?.type) return false;\n    return [QuestionType.SC, QuestionType.MC, QuestionType.TF].includes(this.formattedQuestion.type);\n  }\n\n  @computedFrom('formattedQuestion.type')\n  private get isPictureQuestion(): boolean {\n    if(!this.formattedQuestion?.type) return false;\n    return [QuestionType.MCP, QuestionType.SCP].includes(this.formattedQuestion.type);\n  }\n\n  @computedFrom('formattedQuestion.type')\n  private get isLTQuestion(): boolean {\n    if(!this.formattedQuestion?.type) return false;\n    return this.formattedQuestion.type === QuestionType.LT;\n  }\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { QuestionType } from 'common/dto/course/content/course.types';\nimport { IQuestionDto } from 'common/dto/course/content/question.interface';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ColumnSortDirection, FilterOperationType } from 'common/dto/filter/filter.enum';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { RoleName } from 'common/enum/role.enums';\nimport * as CourseContentMutations from 'common/store/mutations/course.content.mutations';\nimport { isPresentationSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { QuestionUtils } from 'common/utils/QuestionUtils';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { CustomDialogService } from '../../../services/CustomDialogService';\nimport { ContentEditorApiService } from '../../../services/api/ContentEditorApiService';\nimport { CourseManagementApiService } from '../../../services/api/CourseManagementApiService';\nimport { QuestionsApiService } from '../../../services/api/QuestionsApiService';\nimport { TenantApiService } from '../../../services/api/TenantApiService';\nimport { setSelectedGalleryCourseId } from '../../../store/actions';\nimport { IState } from '../../../store/state';\nimport { CustomTableCell } from '../custom-table/custom-table-cell/custom-table-cell';\nimport { VirtualFilterField } from '../custom-table/custom-table-helpers';\nimport { IColumnDefinition } from '../custom-table/custom-table-interfaces';\nimport { IconCell } from '../custom-table/icon-cell/icon-cell';\n\n@autoinject()\nexport class QuestionsForm {\n  @bindable() public questionIds: number[] = [];\n  @bindable() public courseId: number | null = null;\n  @bindable() public canAddMultiple: boolean = false;\n  @bindable() contextIdentifier = '';\n  @selectState(QuestionsForm, isPresentationSelectorCreator, ['contextIdentifier']) private isPresentation?: boolean | null;\n\n  private tableRefreshVersion = 0;\n  private availableCourses: Array<ICourseEditResponseDto> = [];\n  private selectedCourseId: number | null = null;\n  private selectedCourse: ICourseEditResponseDto | null = null;\n  private isProvider: boolean = false;\n\n  private filter: IFilterDto<IQuestionDto> = {\n    offset: 0,\n    limit: 10,\n    order: [{\n      field: 'text',\n      dir: ColumnSortDirection.ASC,\n    }],\n    filter: []\n  };\n\n  private tableColumns: Array<IColumnDefinition<CustomTableCell<IQuestionDto> | IconCell<IQuestionDto>>> = [\n    {\n      id: 'externalId',\n      nameTk: 'elements.questionListWidget.externalId',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => q.externalId ?? '-'\n      },\n      shrink: true,\n    },\n    {\n      id: 'title',\n      nameTk: 'globals.title',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => q.type === QuestionType.LT ? QuestionUtils.splitFillInTheBlankQuestionIntoArray(q.text).join(' |') : q.text,\n        spanClasses: () => 'btn btn-link text-left',\n        callback: q => this.showQuestionPreview(q)\n      },\n      wrap: true\n    },\n    {\n      id: 'questionType',\n      nameTk: 'elements.questionListWidget.questionType',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: q => this.i18N.tr(`globals.questionTypes.questionType${q.type}`)\n      },\n      shrink: true,\n    },\n    {\n      id: 'remove',\n      nameTk: '',\n      cellViewModel: IconCell,\n      cellOptions: {\n        iconTransformer: () => 'System/Tabler/Trash',\n        callback: q => { this.removeQuestion(q); }\n      },\n      shrink: true,\n    },\n  ];\n\n  constructor(\n    private questionsApi: QuestionsApiService,\n    private dialogService: CustomDialogService,\n    private i18N: I18N,\n    private storeService: StoreService<IState>,\n    private courseManagementApiService: CourseManagementApiService,\n    private tenantApi: TenantApiService,\n    private contentEditorApi: ContentEditorApiService,\n  ) { }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.role === RoleName.Provider,\n    ], (_, isProvider) => {\n      this.isProvider = isProvider;\n      this.updateAvailableCourses();\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.gallery.selectedCourseId\n    ], (_, selectedCourseId) => {\n      if(!this.selectedCourseId && selectedCourseId) {\n        this.selectedCourseId = selectedCourseId;\n        this.selectedCourse = this.availableCourses.find(course => course.id === this.selectedCourseId) ?? null;\n      }\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  private questionIdsChanged(): void {\n    this.tableRefreshVersion++;\n  }\n\n  private async updateAvailableCourses(): Promise<void> {\n    this.availableCourses = this.isProvider\n      ? await this.courseManagementApiService.getAvailableRegularCourses()\n      : await this.tenantApi.getAvailableRegularCourses();\n    const courseId = this.courseId ?? this.selectedCourseId;\n    this.selectedCourse = this.availableCourses.find(course => course.id === courseId) ?? this.availableCourses[0];\n    this.changeCourse();\n  }\n\n  private async changeCourse(): Promise<void> {\n    this.selectedCourseId = this.selectedCourse?.id ?? null;\n    await this.storeService.dispatch(setSelectedGalleryCourseId, this.selectedCourseId);\n  }\n\n  private async fetchTableData(limit: number, offset: number): Promise<{ total: number; data: Array<IQuestionDto> }> {\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n    this.filter.filter = [{\n      field: 'id' as VirtualFilterField,\n      type: FilterOperationType.In,\n      value: this.questionIds,\n    }];\n\n    const results = await this.questionsApi.getAllAvailableQuestions(this.filter);\n    return { data: results.rows, total: results.count };\n  }\n\n  private removeQuestion(question: IQuestionDto): void {\n    this.questionIds = this.questionIds.filter(id => id !== question.id);\n    this.tableRefreshVersion++;\n  }\n\n  private async addQuestion(): Promise<void> {\n    const courseId = this.selectedCourse?.id ?? this.courseId;\n    assertNotNullOrUndefined(courseId, 'courseId cannot be null or undefined');\n\n    const result = await this.dialogService.showAddQuestionDialog({ courseId, questionIds: this.questionIds });\n    if(result) {\n      this.questionIds.push(result);\n      this.tableRefreshVersion++;\n    }\n  }\n\n  private async createQuestion(): Promise<void> {\n    const courseId = this.selectedCourse?.id ?? this.courseId;\n    assertNotNullOrUndefined(courseId, 'courseId cannot be null or undefined');\n    const result = await this.dialogService.showCreateQuestionDialog(courseId);\n    if(!result) return;\n    const question = await this.dialogService.showEditQuestionDialog({\n      courseId,\n      questionId: result.id,\n      dialogService: this.dialogService\n    });\n    if(question) {\n      this.questionIds.push(question.id);\n      this.tableRefreshVersion++;\n      const course = await this.contentEditorApi.getCourseContent(courseId);\n      await this.storeService.dispatch(CourseContentMutations.setContent, course);\n    }\n  }\n\n  @computedFrom('canAddMultiple', 'tableRefreshVersion')\n  private get canAddQuestion(): boolean {\n    if(this.canAddMultiple) return true;\n    return this.questionIds.length === 0;\n  }\n\n  @computedFrom(\n    'availableCourses.length',\n    'selectedCourse',\n    'courseId',\n    'isPresentation',\n    'isProvider'\n  )\n  private get showCopyQuestionButton(): boolean {\n    return this.isProvider\n      && !!this.availableCourses.length\n      && this.selectedCourse?.id !== this.courseId\n      && !this.isPresentation;\n  }\n\n  private showQuestionPreview(question: IQuestionDto): void {\n    this.dialogService.showQuestionPreview(question);\n  }\n\n  private async copyQuestionFromCourse(): Promise<void> {\n    assertNotNullOrUndefined(this.courseId, 'courseId cannot be null or undefined');\n    const questions = await this.dialogService.showCopyQuestionDialog({\n      canSelectMultiple: this.canAddMultiple,\n      sourceCourseId: this.selectedCourse?.id,\n      targetCourseId: this.courseId,\n    });\n    if(questions) {\n      this.questionIds = [...this.questionIds, ...questions.map(q => q.id)];\n      this.tableRefreshVersion++;\n    }\n  }\n}\n","import { PLATFORM } from 'aurelia-pal';\nimport { FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/quiz-template-card/quiz-template-card'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/quiz-template-card/quick-quiz-card/quick-quiz-card.html'));\n}\n","import { autoinject, bindable, computedFrom } from 'aurelia-framework';\nimport { I18N } from 'aurelia-i18n';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { IQuizTemplateWithQuestionCountDto } from 'common/dto/course/quiz/template/quiz.template.interface';\nimport { RoleName } from 'common/enum/role.enums';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { NicknameUtils } from 'common/utils/NicknameUtils';\nimport { TIME_TO_ANSWER_QUIZ_QUESTION_IN_MINUTES } from '../../../globals/defaultValues';\nimport { IState } from '../../../store/state';\n\nexport enum QuizTemplateCardStyle {\n  Own = 'own',\n  Curated = 'curated',\n}\n\n/**\n * @event {ReleaseIconClickedEvent} release-clicked\n * @event {PlayClickedEvent} play-clicked\n * @event {EditClickedEvent} edit-clicked\n */\n@autoinject()\nexport class QuizTemplateCard {\n  @bindable() public quizTemplate: IQuizTemplateWithQuestionCountDto | null = null;\n  @bindable() public editable = false;\n  @bindable() public playable = false;\n  @bindable() public releaseable = false;\n  @bindable() public disabled = false;\n  private domElement: HTMLElement;\n  private userId: number | null = null;\n  private isProvider: boolean = false;\n\n  constructor(\n    private i18N: I18N,\n    private storeService: StoreService<IState>,\n    element: Element,\n  ) {\n    this.domElement = element as HTMLElement;\n  }\n\n  protected attached(): void {\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.user.id,\n      state => state.currentRole?.role === RoleName.Provider\n    ], (_, userId, isProvider) => {\n      this.isProvider = isProvider;\n      this.userId = userId;\n    });\n  }\n\n  protected detached(): void {\n    this.storeService.disposeAllSubscriptions(this);\n  }\n\n  @computedFrom('quizTemplate')\n  private get questionCountAndDurationDisplay(): IQuestionCountAndDurationDisplay | void {\n    if(!this.quizTemplate) return;\n    return {\n      count: this.quizTemplate.questionCount,\n      duration: this.quizTemplate.questionCount * TIME_TO_ANSWER_QUIZ_QUESTION_IN_MINUTES\n    };\n  }\n\n  @computedFrom('quizTemplate')\n  private get isSharedOrReleased(): boolean {\n    return !!this.quizTemplate?.shared || !!this.quizTemplate?.released;\n  }\n\n  @computedFrom('quizTemplate', 'userId', 'isProvider')\n  private get cardStyle(): QuizTemplateCardStyle {\n    const isOwn = (this.userId === this.quizTemplate?.creator.id || this.isProvider);\n    return isOwn && !this.quizTemplate?.released && !this.quizTemplate?.shared\n      ? QuizTemplateCardStyle.Own\n      : QuizTemplateCardStyle.Curated;\n  }\n\n  @computedFrom('quizTemplate', 'isSharedOrReleased', 'isProvider', 'userId')\n  private get creatorName(): string | null {\n    const isOwn = (this.userId === this.quizTemplate?.creator.id || this.isProvider);\n\n    if(\n      !this.quizTemplate?.creator\n      || !this.isSharedOrReleased\n      || isOwn\n    ) return null;\n    return this.quizTemplate.released\n      ? this.i18N.tr('globals.provider')\n      : NicknameUtils.getFullName(this.quizTemplate?.creator);\n  }\n\n  private handleReleaseClicked(): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'release-clicked',\n    });\n  }\n\n  private handlePlayClicked(): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'play-clicked',\n    });\n  }\n\n  private handleEditClicked(): void {\n    DomUtils.fireEvent(this.domElement, {\n      name: 'edit-clicked',\n    });\n  }\n}\n\nexport type ReleaseClickedEvent = CustomEvent<never>;\nexport type PlayClickedEvent = CustomEvent<never>;\nexport type EditClickedEvent = CustomEvent<never>;\n\ninterface IQuestionCountAndDurationDisplay {\n  count: number;\n  duration: number;\n}","import { FrameworkConfiguration, PLATFORM } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/recent-student-activity-table/recent-student-activity-table'));\n}\n","import { autoinject } from 'aurelia-framework';\nimport { IFilterDto } from 'common/dto/filter/filter.interface';\nimport { IRecentStudentActivityDto } from 'common/dto/user/user.activity.interface';\nimport { UserActivityApiService } from '../../../services/api/UserActivityApiService';\nimport { AvatarCell } from '../custom-table/avatar-cell/avatar-cell';\nimport { CustomTableCell } from '../custom-table/custom-table-cell/custom-table-cell';\nimport { IColumnDefinition } from '../custom-table/custom-table-interfaces';\n\n@autoinject()\nexport class RecentStudentActivityTable {\n  private filter: IFilterDto<IRecentStudentActivityDto<string>> = { };\n\n  private tableColumns: Array<IColumnDefinition<CustomTableCell<IRecentStudentActivityDto<string>> | AvatarCell<IRecentStudentActivityDto<string>>>> = [\n    {\n      id: 'avatar',\n      nameTk: 'pages.students.columns.avatar',\n      cellViewModel: AvatarCell,\n      cellOptions: {\n        avatarSvgDownloadLink: s => s.student?.avatarSvgDownloadLink ?? null,\n        points: s => s.points,\n        online: s => !!s.student?.isOnline\n      },\n    },\n    {\n      id: 'nickname',\n      nameTk: 'elements.recentStudentActivityTable.nickname',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => c.student.nickname ?? '',\n      },\n    },\n    {\n      id: 'firstname',\n      nameTk: 'elements.recentStudentActivityTable.firstname',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => c.student.firstname ?? '',\n      },\n    },\n    {\n      id: 'lastname',\n      nameTk: 'elements.recentStudentActivityTable.lastname',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => c.student.lastname ?? ''\n      },\n    },\n    {\n      id: 'courseTitle',\n      nameTk: 'globals.course',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => c.courseTitle,\n      },\n    },\n    {\n      id: 'courseGroupName',\n      nameTk: 'globals.courseGroup',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => c.courseGroupName,\n      },\n    },\n    {\n      id: 'todaysLearningTimeInMinutes',\n      nameTk: 'elements.recentStudentActivityTable.todaysLearningTimeInMinutes',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => `${Math.ceil(c.todaysLearningTimeInMinutes)}`,\n      },\n    },\n    {\n      id: 'todaysTrainingTimeInMinutes',\n      nameTk: 'elements.recentStudentActivityTable.todaysTrainingTimeInMinutes',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => `${Math.ceil(c.todaysTrainingTimeInMinutes)}`,\n      },\n    },\n    {\n      id: 'testExamsPassedToday',\n      nameTk: 'elements.recentStudentActivityTable.testExamsPassedToday',\n      cellViewModel: CustomTableCell,\n      cellOptions: {\n        transformer: c => `${c.testExamsPassedToday}/${c.testExamsPassedToday + c.testExamsFailedToday}`,\n      },\n    },\n  ];\n\n  constructor(private userActivityApiService: UserActivityApiService) { }\n\n  private async fetchTableData(limit: number, offset: number): Promise<{ total: number; data: Array<IRecentStudentActivityDto<string>> }> {\n    this.filter.limit = limit;\n    this.filter.offset = offset;\n    const response = await this.userActivityApiService.getRecentStudentActivity(this.filter);\n    if(response.success) {\n      return {\n        data: response.rows,\n        total: response.count,\n      };\n    }\n\n    return { data: [], total: 0 };\n  }\n}\n","import { autoinject, computedFrom } from 'aurelia-framework';\nimport { FadeAnimationPlayer } from 'common/aurelia-classes/animationPlayer/FadeAnimationPlayer';\nimport { FocusTrap } from 'common/aurelia-classes/FocusTrap';\nimport { CommonFullScreenContainer } from 'common/aurelia-components/common-full-screen-container/common-full-screen-container';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { UpdateRecommendation } from 'common/constants/http-headers.constants';\nimport { EaEvent } from 'common/enum/ea-event.enum';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\nimport { ButtonFillMode } from '../button-standard/button-standard';\n\n@autoinject()\nexport class RequiredUpdateOverlay {\n  private subscriptionManagerService: SubscriptionManagerService;\n  private subscriptionManager: SubscriptionManager;\n  private fullScreenContainer: CommonFullScreenContainer | null = null;\n  private isOpen = false;\n  private animationPlayer: FadeAnimationPlayer | null = null;\n  private overlayElement: HTMLElement | null = null;\n  private focusTrap: FocusTrap | null = null;\n\n  private ButtonFillMode = ButtonFillMode;\n\n  constructor(\n    subscriptionManagerService: SubscriptionManagerService,\n  ) {\n    this.subscriptionManagerService = subscriptionManagerService;\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  public open(): void {\n    assertNotNullOrUndefined(this.fullScreenContainer, 'can\\'t open without a fullScreenContainer');\n    assertNotNullOrUndefined(this.overlayElement, 'can\\'t open without an overlayElement');\n\n    if(this.isOpen) {\n      return;\n    }\n\n    this.focusTrap = new FocusTrap(this.overlayElement, this.subscriptionManagerService);\n    this.focusTrap.setup();\n\n    this.fullScreenContainer.show();\n    this.isOpen = true;\n  }\n\n  public close(): void {\n    assertNotNullOrUndefined(this.fullScreenContainer, 'can\\'t RequiredUpdateOverlay.close without a fullScreenContainer');\n\n    this.fullScreenContainer.hide();\n    this.isOpen = false;\n    this.focusTrap?.destroy();\n  }\n\n  protected bind(): void {\n    // we need to do this here because if the required-update-overlay is shown right as it is attached,\n    // then the animationPlayer bind wouldn't have propagated to the full-screen-container\n    assertNotNullOrUndefined(this.overlayElement, 'can\\'t RequiredUpdateOverlay.bind without a overlayElement');\n    this.animationPlayer = new FadeAnimationPlayer(this.overlayElement);\n  }\n\n  protected attached(): void {\n    this.subscriptionManager.subscribeToEvent(EaEvent.UpdateRecommended, level => {\n      if(level === UpdateRecommendation.Hard) this.open();\n    });\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n  }\n\n  @computedFrom('isOpen')\n  private get opacity(): number {\n    return this.isOpen\n      ? 1\n      : 0;\n  }\n\n  private handleReloadClick(): void {\n    window.location.reload();\n  }\n}\n","import { Editor, EditorStateObserver, EditorStateUnsubscriber, getNodeAttributes, TextEditorController } from 'common/aurelia-components/text-editor/TextEditorController';\nimport { autoinject, bindable } from 'aurelia-framework';\nimport { ISelectItem } from 'resources/elements/x-select/x-select';\nimport { IasJumpLinkStyle } from 'common/aurelia-components/rich-text/components/ias-jump-link';\n\n@autoinject()\nexport class IasJumpLinkEdit {\n  @bindable private editorContext = 'DefaultEditor';\n  private editor: Editor | null = null;\n  private editorStateUnsubscriber: EditorStateUnsubscriber | null = null;\n  private nodeName = 'iasJumpLinkNode';\n  private attributes: Record<string, any> = {};\n\n  protected jumpSlideOptions: Array<ISelectItem<string>> = [1, 2, 3, 4].map(i => ({\n    label: `${i}`,\n    value: `${i}`,\n  }));\n\n  protected typeOptions: Array<ISelectItem<IasJumpLinkStyle>> = Object.values(IasJumpLinkStyle).map(i => ({\n    label: `elements.rich-text-editor.ias-jump-link-edit.typeOptions.${i}`,\n    value: i,\n  }));\n\n  private visible = false;\n\n  private observerCallbacks: EditorStateObserver = {\n    editorStarted: editor => { this.editor = editor; },\n    editorClosed: () => { this.editor = null; },\n    stateChanged: () => { this.editorStateChanged(); },\n  };\n\n  constructor(private textEditorController: TextEditorController) {\n  }\n\n  private attached(): void {\n    this.editorStateUnsubscriber = this.textEditorController.observe(this.editorContext, this.observerCallbacks);\n  }\n\n  private detached(): void {\n    this.editorStateUnsubscriber?.();\n  }\n\n  private editorStateChanged(): void {\n    if(!this.editor) return;\n    this.visible = this.editor?.isActive(this.nodeName) ?? false;\n    if(this.visible) {\n      this.attributes = { \n        ...getNodeAttributes(this.editor.state, this.nodeName)\n      };\n    }\n  }\n\n  private onUserInput(event?: Event): void {\n    if(event && !event.target) return;\n    const pos =  this.editor?.state.selection.$anchor.pos ?? 0;\n    this.editor?.chain().updateAttributes(this.nodeName, this.attributes).setNodeSelection(pos).run();\n  }\n\n  private onJumpSlideOrdinalInput(event: CustomEvent): void {\n    this.attributes.jumpSlideOrdinal = event.detail.value;\n    this.onUserInput();\n  }\n\n  private onTypeInput(event: CustomEvent): void {\n    this.attributes.type = event.detail.value;\n    this.onUserInput();\n  }\n}\n","import { Editor, EditorStateObserver, EditorStateUnsubscriber, getNodeAttributes, TextEditorController } from 'common/aurelia-components/text-editor/TextEditorController';\nimport { autoinject, bindable, ObserverLocator, Parser } from 'aurelia-framework';\nimport { CustomDialogService } from 'services/CustomDialogService';\nimport { courseIdSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { selectState, SelectStateObservableComponent } from 'common/aurelia-decorators/select-state';\nimport { assertNotNullOrUndefined } from 'common/utils/assert';\n\n@autoinject()\nexport class InternalLinkEdit extends SelectStateObservableComponent {\n  @bindable() public contextIdentifier: string | null = null;\n\n  @bindable() private editorContext = 'DefaultEditor';\n  private editor: Editor | null = null;\n  private editorStateUnsubscriber: EditorStateUnsubscriber | null = null;\n  private nodeName = 'internalLink';\n  private attributes: Record<string, any> = {};\n\n  private visible = false;\n\n  @selectState(InternalLinkEdit, courseIdSelectorCreator, ['contextIdentifier']) private courseId?: number | null;\n\n  private observerCallbacks: EditorStateObserver = {\n    editorStarted: editor => { this.editor = editor; },\n    editorClosed: () => { this.editor = null; },\n    stateChanged: () => { this.editorStateChanged(); },\n  };\n\n  constructor(\n    private textEditorController: TextEditorController,\n    private customDialogService: CustomDialogService,\n    parser: Parser,\n    observerLocator: ObserverLocator,\n  ) {\n    super(parser, observerLocator);\n  }\n\n  private attached(): void {\n    this.editorStateUnsubscriber = this.textEditorController.observe(this.editorContext, this.observerCallbacks);\n  }\n\n  private detached(): void {\n    this.editorStateUnsubscriber?.();\n  }\n  \n  private editorStateChanged(): void {\n    if(!this.editor) return;\n    this.visible = this.editor?.isActive(this.nodeName) ?? false;\n    if(this.visible) {\n      this.attributes = getNodeAttributes(this.editor.state, this.nodeName).value ?? '';\n    }\n  }\n\n  protected async handleEditClick(): Promise<void> {\n    assertNotNullOrUndefined(this.courseId, 'Cannot link to content without course');\n    this.visible = false;\n    const link = await this.customDialogService.showChatLinkSearchDialog(undefined, this.courseId);\n    this.visible = true;\n    if(link == null) return;\n    const pos =  this.editor?.state.selection.$anchor.pos ?? 0;\n    let type = null;\n    let targetId = null;\n    if('question' in link) {\n      type = 'question';\n      targetId = link.question.id;\n    }\n    if('page' in link) {\n      type = 'page';\n      targetId = link.page.id;\n    }\n    if(type && targetId) {\n      this.editor?.chain().focus().updateAttributes(this.nodeName, { type, value: targetId }).setNodeSelection(pos).run();\n    }\n  }\n}\n","import { PLATFORM } from 'aurelia-pal';\nimport { FrameworkConfiguration } from 'aurelia-framework';\n\nexport function configure(config: FrameworkConfiguration): void {\n  config.globalResources(PLATFORM.moduleName('resources/elements/rich-text-editor/components/internal-link-edit/internal-link-edit'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/rich-text-editor/components/ias-jump-link-edit/ias-jump-link-edit'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar-entry'));\n  config.globalResources(PLATFORM.moduleName('resources/elements/rich-text-editor/rich-text-editor-toolbar/rich-text-editor-toolbar'));\n}\n","import { GalleryMediaType } from 'common/enum/gallery.enum';\nimport { IGalleryPictureItem } from '../../../gallery-widget/gallery-item/gallery-item';\nimport { MenuEntry, RichTextEditorToolbar } from '../rich-text-editor-toolbar';\n\nexport function createAdvancedRow(toolbar: RichTextEditorToolbar): Array<MenuEntry> {\n  return [\n    {\n      iconName: 'Uce/TextUnderline',\n      tooltipTk: 'pages.content-editor.tooltips.textUnderline',\n      matcher: {\n        name: 'underline',\n      },\n      callback: () => { toolbar.editor?.chain().focus().toggleUnderline().run(); },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/TextStrikeThrough',\n      tooltipTk: 'pages.content-editor.tooltips.textStrikeThrough',\n      matcher: {\n        name: 'strike',\n      },\n      callback: () => { toolbar.editor?.chain().focus().toggleStrike().run(); },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/SuperScript',\n      tooltipTk: 'pages.content-editor.tooltips.superScript',\n      matcher: {\n        name: 'superscript',\n      },\n      callback: () => {\n        toolbar.editor?.chain().focus().toggleSuperscript().unsetSubscript().run();\n      },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/SubScript',\n      tooltipTk: 'pages.content-editor.tooltips.subScript',\n      matcher: {\n        name: 'subscript',\n      },\n      callback: () => {\n        toolbar.editor?.chain().focus().toggleSubscript().unsetSuperscript().run();\n      },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/BlockQuote',\n      tooltipTk: 'pages.content-editor.tooltips.blockQuote',\n      matcher: {\n        name: 'blockquote',\n      },\n      callback: () => {\n        toolbar.editor?.chain().focus().toggleBlockquote().run();\n      },\n    },\n    {\n      iconName: 'Uce/SinkList',\n      tooltipTk: 'pages.content-editor.tooltips.liftList',\n      callback: () => {\n        toolbar.editor?.chain().focus().liftListItem('listItem').run();\n      },\n    },\n    {\n      iconName: 'Uce/LiftList',\n      tooltipTk: 'pages.content-editor.tooltips.sinkList',\n      callback: () => {\n        toolbar.editor?.chain().focus().sinkListItem('listItem').run();\n      },\n    },\n    {\n      iconName: 'Uce/AddTable',\n      tooltipTk: 'pages.content-editor.tooltips.addTable',\n      matcher: {\n        name: 'table',\n      },\n      callback: () => {\n        if(!toolbar.editor?.isActive('table')) {\n          toolbar.editor?.chain().focus().insertTable().run();\n        }\n      },\n    },\n    {\n      iconName: 'Uce/Math',\n      tooltipTk: 'pages.content-editor.tooltips.math',\n      matcher: {\n        name: 'katex',\n      },\n      callback: () => {\n        toolbar.editor?.chain().focus().insertContent({ type: 'katex', attrs: { value: 'e = mc^2' } }).run();\n      },\n    },\n    {\n      iconName: 'Uce/Link',\n      tooltipTk: 'pages.content-editor.tooltips.linkPageOrQuestion',\n      matcher: {\n        name: 'internalLink',\n      },\n      hideInPresentation: true,\n      hideInHelpText: true,\n      callback: () => {\n        toolbar.editor?.chain().focus().insertContent({ type: 'internalLink', attrs: { type: 'page', value: '' } }).run();\n      },\n    },\n    {\n      iconName: 'System/Tabler/Image',\n      tooltipTk: 'pages.content-editor.tooltips.image',\n      matcher: {\n        name: 'image',\n      },\n      // only show in help text editor for now\n      helpTextOnly: true,\n      callback: async () => {\n        const picture = await toolbar.dialogService.showMediaSelect({ \n          selectedMediaType: GalleryMediaType.Picture, \n          showMediaTypeFilter: false,\n        }) as IGalleryPictureItem | null;\n        if(picture == null) return;\n\n        const { url, ratio } = await toolbar.galleryApi.getPictureDownloadUrl(picture.value.id);\n        toolbar.editor?.chain().focus().insertContent({ \n          type: 'image',\n          attrs: {\n            pictureId: picture.value.id,\n            originalSrc: url,\n            src: url,\n            placeholderRatio: ratio,\n          },\n        }).run();\n      },\n    },\n    {\n      iconName: 'Uce/ClearFormat',\n      tooltipTk: 'pages.content-editor.tooltips.clearFormat',\n      callback: () => {\n        toolbar.editor?.chain().focus().clearNodes().unsetAllMarks().run();\n      },\n    },\n  ];\n}\n","import { MenuEntry, RichTextEditorToolbar, TipTapNodeAttributes } from '../rich-text-editor-toolbar';\nimport { \n  createAlignmentEntry,\n  createFontSizeEntry,\n  createHeadingEntries,\n  createLineHeightEntry,\n} from '../rich-text-editor-toolbar-utils';\n\nexport function createFirstRow(toolbar: RichTextEditorToolbar): Array<MenuEntry> {\n  return [\n    {\n      extraClasses: 'rich-text-editor-toolbar__paragraph',\n      children: [\n        {\n          textTk: 'Text',\n          matcher: {\n            name: 'paragraph',\n          },\n          callback: () => { toolbar.editor?.chain().focus().setParagraph().run(); },\n        },\n        ...createHeadingEntries(toolbar),\n      ],\n    },\n    {\n      highlightOnActive: true,\n      fallbackIndex: 4,\n      children: [\n        createFontSizeEntry(toolbar, 88),\n        createFontSizeEntry(toolbar, 56),\n        createFontSizeEntry(toolbar, 40),\n        createFontSizeEntry(toolbar, 32),\n        {\n          textTk: 'elements.richTextEditorToolbar.headingOptionHeadingUnset',\n          callback: () => { toolbar.editor?.chain().focus().unsetMark('textStyle').run(); },\n        },\n      ],\n    },\n    {\n      children: [\n        createAlignmentEntry(toolbar, 'Uce/TextAlignLeft', 'left'),\n        createAlignmentEntry(toolbar, 'Uce/TextAlignCenter', 'center'),\n        createAlignmentEntry(toolbar, 'Uce/TextAlignRight', 'right'),\n      ],\n    },\n    {\n      iconName: 'Uce/LineHeight',\n      tooltipTk: 'pages.content-editor.tooltips.lineHeight',\n      showIconInsteadOfSelectedChild: true,\n      extraChildClasses: 'rich-text-editor-toolbar__option-select-lineheight',\n      highlightOnActive: false,\n      fallbackIndex: 4,\n      children: [\n        createLineHeightEntry(toolbar, '2.48'),\n        createLineHeightEntry(toolbar, '1.86'),\n        createLineHeightEntry(toolbar, '1.55'),\n        createLineHeightEntry(toolbar, 'normal'),\n        createLineHeightEntry(toolbar, '0.93'),\n      ],\n    },\n    {\n      iconName: 'Uce/TextBold',\n      tooltipTk: 'pages.content-editor.tooltips.textBold',\n      matcher: {\n        name: 'bold',\n      },\n      callback: () => { toolbar.editor?.chain().focus().toggleBold().run(); },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/TextItalics',\n      tooltipTk: 'pages.content-editor.tooltips.textItalics',\n      matcher: {\n        name: 'italic',\n      },\n      callback: () => { toolbar.editor?.chain().focus().toggleItalic().run(); },\n      isActive: false,\n    },\n    {\n      iconName: 'Uce/ListBullets',\n      tooltipTk: 'pages.content-editor.tooltips.listBullets',\n      matcher: {\n        name: 'bulletList',\n      },\n      callback: () => { \n        toolbar.editor?.chain().focus().toggleBulletList().run();\n      },\n    },\n    {\n      iconName: 'Uce/ListBullets',\n      tooltipTk: 'pages.content-editor.tooltips.listBullets',\n      matcher: {\n        name: 'orderedList',\n      },\n      callback: () => { \n        toolbar.editor?.chain().focus().toggleOrderedList().run();\n      },\n    },\n    {\n      fallbackIndex: 4,\n      iconOnlyInSelect: true,\n      highlightOnActive: true,\n      keepLastActiveChildSelected: true,\n      extraChildClasses: 'rich-text-editor-toolbar__option-select-highlight',\n      children: [\n        {\n          iconName: 'Uce/Highlight',\n          textTk: 'elements.richTextEditorToolbar.highlight.highlight',\n          matcher: {\n            name: 'annotation',\n            attributes: {\n              type: 'highlight',\n            },\n          },\n          callback: () => {\n            toolbar.editor?.chain().focus().toggleMark('annotation', { type: 'highlight' }).run();\n          },\n        },\n        {\n          iconName: 'Uce/HighlightCircle',\n          textTk: 'elements.richTextEditorToolbar.highlight.circle',\n          matcher: {\n            name: 'annotation',\n            attributes: {\n              type: 'circle',\n            },\n          },\n          callback: () => {\n            toolbar.editor?.chain().focus().toggleMark('annotation', { type: 'circle' }).run();\n          },\n        },\n        {\n          iconName: 'Uce/HighlightBox',\n          textTk: 'elements.richTextEditorToolbar.highlight.box',\n          matcher: {\n            name: 'annotation',\n            attributes: {\n              type: 'box',\n            },\n          },\n          callback: () => {\n            toolbar.editor?.chain().focus().toggleMark('annotation', { type: 'box' }).run();\n          },\n        },\n        {\n          iconName: 'Uce/TextStrikeThrough',\n          textTk: 'elements.richTextEditorToolbar.highlight.strike',\n          matcher: {\n            name: 'annotation',\n            attributes: {\n              type: 'strike-through',\n            },\n          },\n          callback: () => {\n            toolbar.editor?.chain().focus().toggleMark('annotation', { type: 'strike-through' }).run();\n          },\n        },\n        {\n          iconName: 'Uce/Highlight',\n          textTk: 'elements.richTextEditorToolbar.highlight.remove',\n          callback: () => {\n            toolbar.editor?.chain().focus().unsetMark('annotation').run();\n          },\n        },\n      ],\n    },\n    {\n      iconName: 'Uce/ContainerColor',\n      tooltipTk: 'pages.content-editor.tooltips.textColor',\n      type: 'colorSelect',\n      matcher: {\n        name: 'textStyle',\n        attributes: {\n          color: /.*/,\n        },\n      },\n      callback: (color: string | null) => {\n        toolbar.editor?.chain().focus().setMark('textStyle', { color }).removeEmptyTextStyle().run();\n      },\n      convertAttributes: (attributes: TipTapNodeAttributes) => {\n        return { color: attributes.color };\n      },\n    },\n    {\n      iconName: 'Uce/TextBackground',\n      tooltipTk: 'pages.content-editor.tooltips.textBackgroundColor',\n      type: 'colorSelect',\n      matcher: {\n        name: 'textStyle',\n        attributes: {\n          backgroundColor: /.*/,\n        },\n      },\n      callback: (color: string | null) => {\n        toolbar.editor?.chain().focus().setMark('textStyle', { backgroundColor: color }).removeEmptyTextStyle().run();\n      },\n      convertAttributes: (attributes: TipTapNodeAttributes) => {\n        return { color: attributes.backgroundColor };\n      },\n    },\n    {\n      iconName: 'Uce/Letters',\n      tooltipTk: 'pages.content-editor.tooltips.mpLetters',\n      matcher: {\n        name: 'mp-letter-input',\n      },\n      interactiveTextOnly: true,\n      callback: () => {\n        if(!toolbar.editor) return;\n        const { state } = toolbar.editor;\n        const { from, to } = state.selection;\n        const text = state.doc.textBetween(from, to);\n        const pos = state.selection.$anchor.pos ?? 0;\n        toolbar.editor?.chain().focus().insertContent({ type: 'mp-letter-input', attrs: { value: text || 'Buchstabensalat' } }).setNodeSelection(pos).run();\n      },\n    },\n    {\n      iconName: 'Uce/InteractiveText',\n      tooltipTk: 'pages.content-editor.tooltips.mpInteractiveText',\n      matcher: {\n        name: 'mp-fill-in-the-blank-input',\n      },\n      interactiveTextOnly: true,\n      callback: () => {\n        if(!toolbar.editor) return;\n        const { state } = toolbar.editor;\n        const { from, to } = state.selection;\n        const text = state.doc.textBetween(from, to);\n        const pos = state.selection.$anchor.pos ?? 0;\n        toolbar.editor?.chain().focus().insertContent({ type: 'mp-fill-in-the-blank-input', attrs: { value: text || 'Option 1;Option 2' } }).setNodeSelection(pos).run();\n      },\n    },\n    {\n      iconName: 'Uce/Sequence',\n      tooltipTk: 'pages.content-editor.tooltips.iasJumpLink',\n      matcher: {\n        name: 'iasJumpLinkNode',\n      },\n      iasOnly: true,\n      callback: () => {\n        toolbar.editor?.chain().focus().insertContent({ type: 'iasJumpLinkNode', attrs: { jumpSlideOrdinal: 2 } }).run();\n      },\n    },\n  ];\n}\n","import { MenuEntry, RichTextEditorToolbar } from '../rich-text-editor-toolbar';\n\nexport function createImageRow(toolbar: RichTextEditorToolbar): Array<MenuEntry> {\n  return [\n    {\n      iconName: 'Uce/Crop',\n      tooltipTk: 'pages.content-editor.tooltips.crop',\n      callback: async () => {\n        const attrs = toolbar.editor?.getAttributes('image');\n        if(attrs == null) return;\n\n        const cropData = await toolbar.dialogService.showCropImageDialog({ imageUrl: attrs['originalSrc'] });\n        const { url, ratio } = await toolbar.galleryApi.getPictureDownloadUrl(attrs['pictureId'], cropData != null ? {\n          bottom: cropData.bottom,\n          flipped: cropData.flipped,\n          left: cropData.left,\n          orientation: cropData.angle,\n          right: cropData.right,\n          top: cropData.top,\n        } : undefined);\n\n        toolbar.editor?.chain().focus().insertContent({\n          type: 'image',\n          attrs: {\n            pictureId: attrs['pictureId'],\n            src: url,\n            originalSrc: attrs['originalSrc'],\n            placeholderRatio: ratio,\n          },\n        }).run();\n      },\n    },\n  ];\n}\n","import { MenuEntry, RichTextEditorToolbar } from '../rich-text-editor-toolbar';\n\nexport function createTableRow(toolbar: RichTextEditorToolbar): Array<MenuEntry> {\n  return [\n    {\n      iconName: 'Uce/AddTable',\n      tooltipTk: 'pages.content-editor.tooltips.toggleHeaderCell',\n      callback: () => { toolbar.editor?.chain().focus().toggleHeaderCell().run(); },\n    },\n    {\n      iconName: 'Uce/MergeCell',\n      tooltipTk: 'pages.content-editor.tooltips.mergeCells',\n      callback: () => { toolbar.editor?.chain().focus().mergeCells().run(); },\n    },\n    {\n      iconName: 'Uce/SplitCell',\n      tooltipTk: 'pages.content-editor.tooltips.splitCell',\n      callback: () => { toolbar.editor?.chain().focus().splitCell().run(); },\n    },\n    {\n      iconName: 'Uce/AddColumn',\n      tooltipTk: 'pages.content-editor.tooltips.addColumn',\n      callback: () => { toolbar.editor?.chain().focus().addColumnAfter().run(); },\n    },\n    {\n      iconName: 'Uce/AddRow',\n      tooltipTk: 'pages.content-editor.tooltips.addRow',\n      callback: () => { toolbar.editor?.chain().focus().addRowAfter().run(); },\n    },\n    {\n      iconName: 'Uce/RemoveColumn',\n      tooltipTk: 'pages.content-editor.tooltips.removeColumn',\n      callback: () => { toolbar.editor?.chain().focus().deleteColumn().run(); },\n    },\n    {\n      iconName: 'Uce/RemoveRow',\n      tooltipTk: 'pages.content-editor.tooltips.removeRow',\n      callback: () => { toolbar.editor?.chain().focus().deleteRow().run(); },\n    },\n  ];\n}\n","import { autoinject, bindable, computedFrom, PLATFORM, useView } from 'aurelia-framework';\nimport { SubscriptionManager, SubscriptionManagerService } from 'common/aurelia-services/SubscriptionManagerService';\nimport { MenuEntry } from './rich-text-editor-toolbar';\n\n@autoinject\n@useView(PLATFORM.moduleName('./rich-text-editor-toolbar-entry.html'))\nexport class RichTextEditorToolbarEntry {\n  @bindable entry?: MenuEntry;\n  @bindable expanded = false;\n  @bindable flat = false;\n\n  private subscriptionManager: SubscriptionManager;\n  private lastActiveChild: MenuEntry | undefined;\n\n  constructor(private element: Element, subscriptionManagerService: SubscriptionManagerService) {\n    this.subscriptionManager = subscriptionManagerService.createSubscriptionManager();\n  }\n\n  private handleChildClick(child: MenuEntry): void {\n    if(!child.children) {\n      child.callback();\n    }\n    this.expanded = false;\n  }\n\n  protected attached(): void {\n    this.subscriptionManager.subscribeToClickOutside(this.element as HTMLElement, () => { this.expanded = false; });\n    if(this.entry && this.entry.children && this.entry.keepLastActiveChildSelected) {\n      this.lastActiveChild = this.entry.activeChild;\n    }\n  }\n\n  protected detached(): void {\n    this.subscriptionManager.disposeSubscriptions();\n    if(this.entry && this.entry.children && this.entry.keepLastActiveChildSelected) {\n      this.entry.activeChild = this.selectEntry;\n    }\n  }\n\n  @computedFrom('entry.children', 'entry.activeChild', 'entry.fallbackIndex')\n  protected get selectEntry(): MenuEntry | undefined {\n    if(!this.entry?.children) return;\n    if(this.entry.activeChild) {\n      this.lastActiveChild = this.entry.activeChild;\n    }\n\n    return (this.entry.keepLastActiveChildSelected ? this.lastActiveChild : this.entry.activeChild)  || this.entry.children[this.entry.fallbackIndex ?? 0];\n  }\n\n  protected handleDropDownMainButtonClick(): void {\n    if(!this.entry?.children) return;\n    if(this.entry.keepLastActiveChildSelected && this.lastActiveChild && !this.lastActiveChild.children) {\n      this.lastActiveChild.callback();\n      this.expanded = false;\n      return;\n    }\n    this.expanded = !this.expanded;\n  }\n\n  protected handleColorSelected(event: CustomEvent<{ color: string }>): void {\n    if(this.entry?.children) return;\n    this.entry?.callback(event.detail.color);\n  }\n}\n","import { MenuEntry, RichTextEditorToolbar } from './rich-text-editor-toolbar';\n\nexport function createFontSizeEntry(toolbar: RichTextEditorToolbar, fontSize: number): MenuEntry {\n  return {\n    textTk: `${fontSize}`,\n    matcher: {\n      name: 'textStyle',\n      attributes: { fontSize },\n    },\n    callback: () => { toolbar.editor?.chain().focus().toggleMark('textStyle', { fontSize }).run(); },\n    isActive: false,\n  };\n}\n\nexport function createHeadingEntries(toolbar: RichTextEditorToolbar): Array<MenuEntry> {\n  return [1, 2, 3, 4].map(level => {\n    return {\n      textTk: `elements.richTextEditorToolbar.headingOptionHeading${level}`,\n      matcher: {\n        name: 'heading',\n        attributes: { level },\n      },\n      callback: () => { toolbar.editor?.chain().focus().setHeading({ level: level as any }).run(); },\n    };\n  });\n}\n\nexport function createAlignmentEntry(\n  toolbar: RichTextEditorToolbar,\n  iconName: string,\n  alignment: string,\n): MenuEntry {\n  return         {\n    iconName,\n    matcher: {\n      attributes: { textAlign: alignment },\n    },\n    callback: () => { toolbar.editor?.chain().focus().setTextAlign(alignment).run(); },\n    isActive: false,\n  };\n}\n\nexport function createLineHeightEntry(\n  toolbar: RichTextEditorToolbar, \n  lineHeight: string,\n  isActive = false,\n): MenuEntry {\n  return {\n    textTk: getLineHeightPercentage(lineHeight),\n    matcher: {\n      attributes: { lineHeight },\n    },\n    callback: () => { toolbar.editor?.chain().focus().setLineHeight(lineHeight).run(); },\n    isActive,\n  };\n}\n\nfunction getLineHeightPercentage(lineHeight: string): string {\n  return lineHeightMap[lineHeight] || '100%';\n}\n\nconst lineHeightMap: { [key: string]: string } = {\n  '0.93': '75%',\n  'normal': '100%',\n  '1.55': '125%',\n  '1.86': '150%',\n  '2.48': '200%',\n};\n","import { autoinject, bindable, computedFrom, observable, PLATFORM, useView } from 'aurelia-framework';\nimport { TextEditorAnimationRequestedEvent } from 'common/aurelia-components/text-editor/text-editor.types';\nimport { Editor, EditorStateObserver, EditorStateUnsubscriber, TextEditorController } from 'common/aurelia-components/text-editor/TextEditorController';\nimport { selectState } from 'common/aurelia-decorators/select-state';\nimport { StoreService } from 'common/aurelia-services/StoreService';\nimport { UniversalBlock } from 'common/dto/content-editor/content.editor.interface';\nimport { BlockType } from 'common/dto/course/content/course.types';\nimport { ICoursePageDto } from 'common/dto/course/content/page.interface';\nimport { ICourseEditResponseDto } from 'common/dto/course/management/course.management.interface';\nimport { ErrorType } from 'common/enum/errortype.enum';\nimport { RoleName } from 'common/enum/role.enums';\nimport { blockByIdSelectorCreator, isPresentationSelectorCreator } from 'common/store/selectors/content.context.selectors';\nimport { courseIdSelectorCreator, isIasSelectorCreator } from 'common/store/selectors/course.context.selectors';\nimport { CourseUtils } from 'common/utils/CourseUtils';\nimport { DomUtils } from 'common/utils/DomUtils';\nimport { LogUtils } from 'common/utils/LogUtils';\nimport { ContentEditorApiService } from '../../../../services/api/ContentEditorApiService';\nimport { GalleryApiService } from '../../../../services/api/GalleryApiService';\nimport { CustomDialogService } from '../../../../services/CustomDialogService';\nimport { IState } from '../../../../store/state';\nimport { createAdvancedRow } from './config/advancedRow';\nimport { createFirstRow } from './config/firstRow';\nimport { createImageRow } from './config/imageRow';\nimport { createTableRow } from './config/tableRow';\n\ntype NodeOrMarkMatcher = {\n  name?: string;\n  attributes?: Record<string, unknown>;\n};\n\nexport type TipTapNodeAttributes = Record<string, string | number>;\n\nexport type MenuEntry = {\n  iconName?: string;\n  tooltipTk?: string;\n  textTk?: string;\n  matcher?: NodeOrMarkMatcher;\n  callback: (...args: any[]) => void;\n  isActive?: boolean;\n  children?: undefined;\n  hideInPresentation?: boolean;\n  interactiveTextOnly?: boolean;\n  helpTextOnly?: boolean;\n  hideInHelpText?: boolean;\n  iasOnly?: boolean;\n  type?: 'colorSelect';\n  attributes?: TipTapNodeAttributes;\n  convertAttributes?: (attributes: TipTapNodeAttributes) => TipTapNodeAttributes;\n} | {\n  iconName?: string;\n  tooltipTk?: string;\n  extraClasses?: string;\n  extraChildClasses?: string;\n  children: Array<MenuEntry>;\n  activeChild?: MenuEntry;\n  keepLastActiveChildSelected?: boolean;\n  iconOnlyInSelect?: boolean;\n  showIconInsteadOfSelectedChild?: boolean;\n  highlightOnActive?: boolean;\n  fallbackIndex?: number;\n};\n\nexport enum RichTextEditorToolbarMode {\n  Uce = 'uce',\n  SystemText = 'system-text',\n}\n\n@autoinject()\n@useView(PLATFORM.moduleName('./rich-text-editor-toolbar.html'))\nexport class RichTextEditorToolbar {\n  private static instance: RichTextEditorToolbar | null;\n\n  /**\n   * Allow an external component to show the AI tab.\n   * Currently used for the \"discoverability\" block \"AI Text\"\n   */\n  public static showAiTray(): void {\n    if(!this.instance?.showAi) this.instance?.toggleAi();\n  }\n\n  @bindable() public contextIdentifier: string | null = null;\n  @bindable() public mode = RichTextEditorToolbarMode.Uce;\n  @bindable() public editorContext = 'DefaultEditor';\n\n  @selectState(RichTextEditorToolbar, isIasSelectorCreator, ['contextIdentifier']) private isIas?: boolean = false;\n  @selectState(RichTextEditorToolbar, isPresentationSelectorCreator, ['contextIdentifier']) private isPresentation?: boolean = false;\n  @selectState(RichTextEditorToolbar, courseIdSelectorCreator, ['contextIdentifier']) private courseId?: number | null;\n\n  public editor: Editor | null = null;\n  private showAdvanced = false;\n  private showTableOptions = false;\n  private showImageOptions = false;\n  @observable() private editBlockId: number | null = null;\n  private block: UniversalBlock | null = null;\n  private blockScope = {};\n  private isProvider: boolean = false;\n\n  // Search and Replace\n  private showSearchAndReplace = false;\n  @observable() private searchTerm = '';\n  @observable() private replaceTerm = '';\n\n  // AI\n  private showAi = false;\n  @observable() private aiWordCount = '100';\n  private availablePages: Array<ICoursePageDto> = [];\n  private selectedPageId: number | null = null;\n  private selectedPage: ICourseEditResponseDto | null = null;\n  private pendingActionState: { editor: Editor } | undefined = undefined;\n  private errorTk: string | null = null;\n\n  private entries: Array<MenuEntry> = createFirstRow(this);\n  private advancedRow: Array<MenuEntry> = createAdvancedRow(this);\n  private tableRow: Array<MenuEntry> = createTableRow(this);\n  private imageRow: Array<MenuEntry> = createImageRow(this);\n\n  private observerCallbacks: EditorStateObserver = {\n    editorStarted: editor => { this.cancelAiGeneration(); this.editor = editor; },\n    editorClosed: () => { this.cancelAiGeneration(); this.editor = null; },\n    stateChanged: () => { this.editorStateChanged(); },\n  };\n  textEditorUnsubscribe: EditorStateUnsubscriber | null = null;\n\n  private Mode = RichTextEditorToolbarMode;\n\n  constructor(\n    // public to be accessed by row creation helpers\n    public dialogService: CustomDialogService,\n    public galleryApi: GalleryApiService,\n    private textEditorController: TextEditorController,\n    private storeService: StoreService<IState>,\n    private contentEditorApiService: ContentEditorApiService,\n  ) {\n    RichTextEditorToolbar.instance = this;\n  }\n\n  private attached(): void {\n    this.textEditorUnsubscribe = this.textEditorController.observe(this.editorContext, this.observerCallbacks);\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.currentRole?.role === RoleName.Provider\n    ], (_state, isProvider) => {\n      this.isProvider = isProvider;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => state.contentEditor.editBlockId,\n    ], (_state, editBlockId) => {\n      this.editBlockId = editBlockId;\n    });\n\n    this.storeService.subscribeToDetailedChanges(this, [\n      state => (this.courseId != null && state.courseContentById[this.courseId]) || null,\n    ], (_state, course) => {\n      this.availablePages = course ? CourseUtils.getFlatPageList(course) : [];\n      this.selectedPageId = this.availablePages[0]?.id;\n    });\n  }\n\n  private editBlockIdChanged(): void {\n    this.storeService.disposeAllSubscriptions(this.blockScope);\n    const { contextIdentifier, editBlockId } = this;\n    if(!contextIdentifier) return;\n    if(!editBlockId) return;\n    this.storeService.subscribeMemoized(\n      this.blockScope,\n      blockByIdSelectorCreator,\n      block => {\n        this.block = block;\n      },\n      { contextIdentifier, blockId: editBlockId }\n    );\n  }\n\n  private detached(): void {\n    this.textEditorUnsubscribe?.();\n    this.storeService.disposeAllSubscriptions(this);\n    this.storeService.disposeAllSubscriptions(this.blockScope);\n  }\n\n  private editorStateChanged(): void {\n    if(!this.editor) return;\n    for(const entry of this.entries) {\n      this.updateEntryStatus(entry);\n    }\n    for(const entry of this.advancedRow) {\n      this.updateEntryStatus(entry);\n    }\n    this.showTableOptions = this.editor.isActive('table');\n    this.showImageOptions = this.editor.isActive('image');\n  }\n\n  private updateEntryStatus(entry: MenuEntry): void {\n    if(!this.editor) return;\n    if(entry.children) {\n      entry.activeChild = undefined;\n      for(const child of entry.children) {\n        this.updateEntryStatus(child);\n        if(!child.children && child.isActive) {\n          entry.activeChild = child;\n        }\n      }\n      return;\n    }\n    if(!entry.matcher) {\n      entry.isActive = false;\n    } else if(entry.matcher.name) {     \n      entry.isActive = this.editor.isActive(entry.matcher.name, entry.matcher.attributes);\n      if(entry.convertAttributes) {\n        entry.attributes = entry.convertAttributes(this.editor.getAttributes(entry.matcher.name));\n      } else {\n        entry.attributes = this.editor.getAttributes(entry.matcher.name);\n      }\n    } else {\n      if(entry.matcher.attributes) {        \n        entry.isActive = this.editor.isActive(entry.matcher.attributes);\n      }\n    }\n  }\n\n  private toggleAdvanced(): void {\n    this.showAdvanced = !this.showAdvanced;\n  }\n\n  private toggleSearchAndReplace(): void {\n    this.showAi = false;\n    this.showSearchAndReplace = !this.showSearchAndReplace;\n    if(!this.showSearchAndReplace) {\n      this.searchTerm = '';\n      this.replaceTerm = '';\n    }\n  }\n\n  private toggleAi(): void {\n    this.showSearchAndReplace = false;\n    this.showAi = !this.showAi;\n  }\n\n  private searchTermChanged(): void {\n    this.editor?.chain().setSearchTerm(this.searchTerm).run();\n  }\n\n  private replaceTermChanged(): void {\n    this.editor?.chain().setReplaceTerm(this.replaceTerm).run();\n  }\n\n  private handleReplace(): void {\n    this.editor?.chain().replace().run();\n  }\n\n  private handleReplaceAll(): void {\n    this.editor?.chain().replaceAll().run();\n  }\n\n  private async handleRegenerateWithAi(): Promise<void> {\n    if(this.editor && this.selectedPageId) {\n      const pendingActionState = { editor: this.editor };\n      this.errorTk = null;\n      this.pendingActionState = pendingActionState;\n\n      try {\n        this.fireEditorAnimationEvent(pendingActionState.editor, 'think');\n        const response = await this.contentEditorApiService.generateSummaryFromContent({\n          pageId: this.selectedPageId,\n          wordCount: Number.parseInt(this.aiWordCount, 10),\n          excludeBlockId: this.block?.id,\n        });\n\n        if(pendingActionState === this.pendingActionState) {\n          pendingActionState.editor.chain().setContent(response).run();\n          this.fireEditorAnimationEvent(pendingActionState.editor, 'write');\n          this.pendingActionState = undefined;\n        }\n      } catch(e) {\n        LogUtils.exception(e);\n\n        if(pendingActionState === this.pendingActionState) {\n          this.errorTk = this.getErrorMessageTk(e.message);\n          this.fireEditorAnimationEvent(pendingActionState.editor, 'cancel');\n          this.pendingActionState = undefined;\n        }\n      }\n    }\n  }\n\n  private cancelAiGeneration(): void {\n    const editor = this.pendingActionState?.editor;\n    this.pendingActionState = undefined;\n    if(editor) this.fireEditorAnimationEvent(editor, 'cancel');\n  }\n\n  private getErrorMessageTk(message: string): string {\n    switch(message) {\n      case ErrorType.AI_CONTENT_POLICY_VIOLATION: return 'elements.questionsForm.errors.aiContentPolicyViolation';\n      case ErrorType.AI_INSUFFICIENT_INFORMATION: return 'elements.questionsForm.errors.aiInsufficientInformation';\n      case ErrorType.AI_PROMPT_TOO_LONG: return 'elements.questionsForm.errors.aiPromptTooLong';\n      case ErrorType.AI_RESPONSE_FORMAT_ERROR: return 'elements.questionsForm.errors.aiResponseFormat';\n      case ErrorType.AI_TOO_MANY_REQUESTS: return 'elements.questionsForm.errors.aiTooManyRequests';\n      default: return 'elements.questionsForm.errors.unknownAiError';\n    }\n  }\n\n  @computedFrom('pendingActionState')\n  private get isGenerating(): boolean {\n    return !!this.pendingActionState;\n  }\n\n  private fireEditorAnimationEvent(editor: Editor, type: TextEditorAnimationRequestedEvent['detail']['type']): void {\n    DomUtils.fireEvent<TextEditorAnimationRequestedEvent['detail']>(editor.view.dom, {\n      name: 'animation-requested',\n      bubbles: true,\n      detail: { type },\n    });\n  }\n\n  @computedFrom('block.type')\n  private get interactiveTextControls(): boolean {\n    return this.block?.type === BlockType.MP_INTERACTIVE_TEXT;\n  }\n\n  private showEntry(entry: MenuEntry, isPresentation: boolean, interactiveTextControls: boolean, isIas: boolean, isHelpText: boolean): boolean {\n    if(entry.children) return true;\n\n    if(entry.interactiveTextOnly && !interactiveTextControls) {\n      return false;\n    }\n\n    if(entry.hideInPresentation && isPresentation) {\n      return false;\n    }\n\n    if(entry.hideInHelpText && isHelpText) {\n      return false;\n    }\n\n    if(entry.iasOnly && !isIas) {\n      return false;\n    }\n\n    if(entry.helpTextOnly && !isHelpText) {\n      return false;\n    }\n\n    return true;\n  }\n\n  private changePage(): void {\n    this.selectedPageId = this.selectedPage?.id ?? null;\n  }\n}\n"],"names":[],"sourceRoot":""}