Merge branch 'MDL-81766-main-clr02' of https://github.com/ferranrecio/moodle

This commit is contained in:
Huong Nguyen
2024-07-25 11:08:54 +07:00
41 changed files with 559 additions and 130 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1004,7 +1004,7 @@ define(
}
}
});
$('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-inplaceeditable]`, function(e) {
$('body').on('updated', `${SELECTOR.ACTIVITYLI} [data-itemtype="activityname"][data-inplaceeditable]`, function(e) {
if (e.ajaxreturn && e.ajaxreturn.itemid) {
courseeditor.dispatch('cmState', [e.ajaxreturn.itemid]);
}
+17 -15
View File
@@ -52,12 +52,11 @@ let initialized = false;
* @method init
* @param {Number} courseId Course ID to use later on in fetchModules()
* @param {Object} chooserConfig Any PHP config settings that we may need to reference
* @param {Number} sectionNum Section number to use later on in fetchModules()
*/
export const init = (courseId, chooserConfig, sectionNum) => {
export const init = (courseId, chooserConfig) => {
const pendingPromise = new Pending();
registerListenerEvents(courseId, chooserConfig, sectionNum);
registerListenerEvents(courseId, chooserConfig);
pendingPromise.resolve();
};
@@ -68,9 +67,8 @@ export const init = (courseId, chooserConfig, sectionNum) => {
* @method registerListenerEvents
* @param {Number} courseId
* @param {Object} chooserConfig Any PHP config settings that we may need to reference
* @param {Number} sectionNum Section number to use later on in fetchModules()
*/
const registerListenerEvents = (courseId, chooserConfig, sectionNum) => {
const registerListenerEvents = (courseId, chooserConfig) => {
// Ensure we only add our listeners once.
if (initialized) {
@@ -84,26 +82,30 @@ const registerListenerEvents = (courseId, chooserConfig, sectionNum) => {
];
const fetchModuleData = (() => {
let innerPromise = null;
let innerPromises = new Map();
return () => {
if (!innerPromise) {
innerPromise = new Promise((resolve) => {
resolve(Repository.activityModules(courseId, sectionNum));
});
return (sectionNum) => {
if (innerPromises.has(sectionNum)) {
return innerPromises.get(sectionNum);
}
return innerPromise;
innerPromises.set(
sectionNum,
new Promise((resolve) => {
resolve(Repository.activityModules(courseId, sectionNum));
})
);
return innerPromises.get(sectionNum);
};
})();
const fetchFooterData = (() => {
let footerInnerPromise = null;
return (sectionnum) => {
return (sectionNum) => {
if (!footerInnerPromise) {
footerInnerPromise = new Promise((resolve) => {
resolve(Repository.fetchFooterData(courseId, sectionnum));
resolve(Repository.fetchFooterData(courseId, sectionNum));
});
}
@@ -157,7 +159,7 @@ const registerListenerEvents = (courseId, chooserConfig, sectionNum) => {
// Now we have a modal we should start fetching data.
// If an error occurs while fetching the data, display the error within the modal.
const data = await fetchModuleData().catch(async(e) => {
const data = await fetchModuleData(sectionnum).catch(async(e) => {
const errorTemplateData = {
'errormessage': e.message
};
+1 -1
View File
@@ -6,6 +6,6 @@ define("core_courseformat/local/content/section",["exports","core_courseformat/l
* @class core_courseformat/local/content/section
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden",CURRENT:"current"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);return cms&&0!==cms.length?cms[cms.length-1]:null}getLastCmFallback(){return this.getElement(this.selectors.SECTIONINFO)}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible,_element$current;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.element.classList.toggle(this.classes.CURRENT,null!==(_element$current=element.current)&&void 0!==_element$current&&_element$current),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&&sectioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this._getActionMenu(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(affectedAction.dataset.swapicon=affectedAction.dataset.icon,affectedAction.dataset.icon=newIcon,newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return this.getElement(".section_action_menu")?this.getElement(selector):document.querySelector(selector)}}return _exports.default=_default,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_header=_interopRequireDefault(_header),_dndsection=_interopRequireDefault(_dndsection),_templates=_interopRequireDefault(_templates),_pending=_interopRequireDefault(_pending);class _default extends _dndsection.default{create(){this.name="content_section",this.selectors={ACTIONMENU:".section-actions",SECTION_ITEM:"[data-for='section_title']",CM:'[data-for="cmitem"]',SECTIONINFO:'[data-for="sectioninfo"]',SECTIONBADGES:'[data-region="sectionbadges"]',SHOWSECTION:'[data-action="sectionShow"]',HIDESECTION:'[data-action="sectionHide"]',ACTIONTEXT:".menu-action-text",ICON:".icon"},this.classes={LOCKED:"editinprogress",HASDESCRIPTION:"description",HIDE:"d-none",HIDDEN:"hidden",CURRENT:"current"},this.id=this.element.dataset.id}stateReady(state){if(this.configState(state),this.reactive.isEditing&&this.reactive.supportComponents){const sectionItem=this.getElement(this.selectors.SECTION_ITEM);if(sectionItem){const headerComponent=new _header.default({...this,element:sectionItem,fullregion:this.element});this.configDragDrop(headerComponent)}}this._openSectionIfNecessary()}async _openSectionIfNecessary(){const pageCmInfo=this.reactive.getPageAnchorCmInfo();if(!pageCmInfo||pageCmInfo.sectionid!==this.id)return;await this.reactive.dispatch("sectionContentCollapsed",[this.id],!1);const pendingOpen=new _pending.default("courseformat/section:openSectionIfNecessary");this.element.scrollIntoView({block:"center"}),setTimeout((()=>{this.reactive.dispatch("setPageItem","cm",pageCmInfo.id),pendingOpen.resolve()}),250)}getWatchers(){return[{watch:"section[".concat(this.id,"]:updated"),handler:this._refreshSection}]}validateDropData(dropdata){return("section"!==(null==dropdata?void 0:dropdata.type)||null===this.reactive.sectionReturn)&&super.validateDropData(dropdata)}getLastCm(){const cms=this.getElements(this.selectors.CM);if(!cms||0===cms.length)return null;const lastCm=cms[cms.length-1];if(null!==this.section.component)return lastCm;const parentSection=lastCm.parentNode.closest(this.selectors.CM);return null!=parentSection?parentSection:lastCm}getLastCmFallback(){return this.getElement(this.selectors.SECTIONINFO)}_refreshSection(_ref){var _element$dragging,_element$locked,_element$visible,_element$current;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.element.classList.toggle(this.classes.HIDDEN,null!==(_element$visible=!element.visible)&&void 0!==_element$visible&&_element$visible),this.element.classList.toggle(this.classes.CURRENT,null!==(_element$current=element.current)&&void 0!==_element$current&&_element$current),this.locked=element.locked;const sectioninfo=this.getElement(this.selectors.SECTIONINFO);sectioninfo&&sectioninfo.classList.toggle(this.classes.HASDESCRIPTION,element.hasrestrictions),this._updateBadges(element),this._updateActionsMenu(element)}_updateBadges(section){const current=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='iscurrent']"));null==current||current.classList.toggle(this.classes.HIDE,!section.current);const hiddenFromStudents=this.getElement("".concat(this.selectors.SECTIONBADGES," [data-type='hiddenfromstudents']"));null==hiddenFromStudents||hiddenFromStudents.classList.toggle(this.classes.HIDE,section.visible)}async _updateActionsMenu(section){var _affectedAction$datas,_affectedAction$datas2;let selector,newAction;section.visible?(selector=this.selectors.SHOWSECTION,newAction="sectionHide"):(selector=this.selectors.HIDESECTION,newAction="sectionShow");const affectedAction=this._getActionMenu(selector);if(!affectedAction)return;affectedAction.dataset.action=newAction;const actionText=affectedAction.querySelector(this.selectors.ACTIONTEXT);if(null!==(_affectedAction$datas=affectedAction.dataset)&&void 0!==_affectedAction$datas&&_affectedAction$datas.swapname&&actionText){const oldText=null==actionText?void 0:actionText.innerText;actionText.innerText=affectedAction.dataset.swapname,affectedAction.dataset.swapname=oldText}const icon=affectedAction.querySelector(this.selectors.ICON);if(null!==(_affectedAction$datas2=affectedAction.dataset)&&void 0!==_affectedAction$datas2&&_affectedAction$datas2.swapicon&&icon){const newIcon=affectedAction.dataset.swapicon;if(affectedAction.dataset.swapicon=affectedAction.dataset.icon,affectedAction.dataset.icon=newIcon,newIcon){const pixHtml=await _templates.default.renderPix(newIcon,"core");_templates.default.replaceNode(icon,pixHtml,"")}}}_getActionMenu(selector){return document.querySelector("".concat(this.selectors.ACTIONMENU,"[data-sectionid='").concat(this.id,"'] ").concat(selector))}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=section.min.js.map
File diff suppressed because one or more lines are too long
@@ -8,6 +8,6 @@ define("core_courseformat/local/content/section/cmitem",["exports","core_coursef
* @class core_courseformat/local/content/section/cmitem
* @copyright 2021 Ferran Recio <ferran@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:"[data-region='activity-card']",DRAGICON:".editing_move",INPLACEEDITABLE:"[data-inplaceeditablelink]"},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state})}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}setDragImage(){return this.getElement(this.selectors.CARD)}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),bulk.enabled?(this.element.dataset.action="toggleSelectionCm",this.element.dataset.preventDefault=1):(this.element.removeAttribute("data-action"),this.element.removeAttribute("data-preventDefault")),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default}));
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_dndcmitem=(obj=_dndcmitem)&&obj.__esModule?obj:{default:obj};class _default extends _dndcmitem.default{create(){this.name="content_section_cmitem",this.selectors={BULKSELECT:"[data-for='cmBulkSelect']",BULKCHECKBOX:"[data-bulkcheckbox]",CARD:"[data-region='activity-card']",DRAGICON:".editing_move",INPLACEEDITABLE:'[data-itemtype="activityname"] > [data-inplaceeditablelink]'},this.classes={LOCKED:"editinprogress",HIDE:"d-none",SELECTED:"selected"},this.id=this.element.dataset.id}stateReady(state){var _this$getElement;this.configDragDrop(this.id),null===(_this$getElement=this.getElement(this.selectors.DRAGICON))||void 0===_this$getElement||_this$getElement.classList.add(this.classes.DRAGICON),this._refreshBulk({state:state})}getWatchers(){return[{watch:"cm[".concat(this.id,"]:deleted"),handler:this.unregister},{watch:"cm[".concat(this.id,"]:updated"),handler:this._refreshCm},{watch:"bulk:updated",handler:this._refreshBulk}]}setDragImage(){return this.getElement(this.selectors.CARD)}_refreshCm(_ref){var _element$dragging,_element$locked;let{element:element}=_ref;this.element.classList.toggle(this.classes.DRAGGING,null!==(_element$dragging=element.dragging)&&void 0!==_element$dragging&&_element$dragging),this.element.classList.toggle(this.classes.LOCKED,null!==(_element$locked=element.locked)&&void 0!==_element$locked&&_element$locked),this.locked=element.locked}_refreshBulk(_ref2){var _this$getElement2;let{state:state}=_ref2;const bulk=state.bulk;this.setDraggable(!bulk.enabled),bulk.enabled?(this.element.dataset.action="toggleSelectionCm",this.element.dataset.preventDefault=1):(this.element.removeAttribute("data-action"),this.element.removeAttribute("data-preventDefault")),null===(_this$getElement2=this.getElement(this.selectors.BULKSELECT))||void 0===_this$getElement2||_this$getElement2.classList.toggle(this.classes.HIDE,!bulk.enabled);const disabled=!this._isCmBulkEnabled(bulk),selected=this._isSelected(bulk);this._refreshActivityCard(bulk,selected),this._setCheckboxValue(selected,disabled)}_refreshActivityCard(bulk,selected){var _this$getElement3,_this$getElement4;null===(_this$getElement3=this.getElement(this.selectors.INPLACEEDITABLE))||void 0===_this$getElement3||_this$getElement3.classList.toggle(this.classes.HIDE,bulk.enabled),null===(_this$getElement4=this.getElement(this.selectors.CARD))||void 0===_this$getElement4||_this$getElement4.classList.toggle(this.classes.SELECTED,selected),this.element.classList.toggle(this.classes.SELECTED,selected)}_setCheckboxValue(checked,disabled){const checkbox=this.getElement(this.selectors.BULKCHECKBOX);checkbox&&(checkbox.checked=checked,checkbox.disabled=disabled,disabled?checkbox.removeAttribute("data-is-selectable"):checkbox.dataset.isSelectable=1)}_isCmBulkEnabled(bulk){return!!bulk.enabled&&(""===bulk.selectedType||"cm"===bulk.selectedType)}_isSelected(bulk){return"cm"===bulk.selectedType&&bulk.selection.includes(this.id)}}return _exports.default=_default,_exports.default}));
//# sourceMappingURL=cmitem.min.js.map
File diff suppressed because one or more lines are too long
+10 -6
View File
@@ -37,6 +37,7 @@ export default class extends DndSection {
this.name = 'content_section';
// Default query selectors.
this.selectors = {
ACTIONMENU: '.section-actions',
SECTION_ITEM: `[data-for='section_title']`,
CM: `[data-for="cmitem"]`,
SECTIONINFO: `[data-for="sectioninfo"]`,
@@ -136,7 +137,14 @@ export default class extends DndSection {
if (!cms || cms.length === 0) {
return null;
}
return cms[cms.length - 1];
const lastCm = cms[cms.length - 1];
// If it is a delegated section return the last item overall.
if (this.section.component !== null) {
return lastCm;
}
// If it is a regular section and the last item overall has a parent cm, return the parent instead.
const parentSection = lastCm.parentNode.closest(this.selectors.CM);
return parentSection ?? lastCm;
}
/**
@@ -234,10 +242,6 @@ export default class extends DndSection {
* @returns The action menu element.
*/
_getActionMenu(selector) {
if (this.getElement('.section_action_menu')) {
return this.getElement(selector);
}
return document.querySelector(selector);
return document.querySelector(`${this.selectors.ACTIONMENU}[data-sectionid='${this.id}'] ${selector}`);
}
}
@@ -40,7 +40,7 @@ export default class extends DndCmItem {
BULKCHECKBOX: `[data-bulkcheckbox]`,
CARD: `[data-region='activity-card']`,
DRAGICON: `.editing_move`,
INPLACEEDITABLE: `[data-inplaceeditablelink]`,
INPLACEEDITABLE: `[data-itemtype="activityname"] > [data-inplaceeditablelink]`,
};
// Most classes will be loaded later by DndCmItem.
this.classes = {
+13
View File
@@ -2121,4 +2121,17 @@ abstract class base {
public function can_sections_be_removed_from_navigation(): bool {
return false;
}
/**
* Determines whether the course module should display the activity editor options.
*
* @param cm_info $cm The activity module.
* @return bool True if the activity editor options are displayed, false otherwise.
*/
public function show_activity_editor_options(cm_info $cm): bool {
if ($cm->get_delegated_section_info() && component_callback_exists('mod_' . $cm->modname, 'cm_info_view')) {
return false;
}
return true;
}
}
@@ -73,9 +73,9 @@ abstract class basecontrolmenu implements named_templatable, renderable {
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return array data context for a mustache template
* @return null|array data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
public function export_for_template(\renderer_base $output): ?stdClass {
$menu = $this->get_action_menu($output);
if (empty($menu)) {
return new stdClass();
@@ -123,6 +123,7 @@ class cm implements named_templatable, renderable {
'cmid' => $mod->id,
'editing' => $PAGE->user_is_editing(),
'sectionnum' => $this->section->section,
'cmbulk' => !$mod->get_delegated_section_info(),
];
// Add partial data segments.
@@ -185,7 +186,7 @@ class cm implements named_templatable, renderable {
$this->displayoptions
);
$modavailability = $availability->export_for_template($output);
$data->modavailability = $modavailability;
$data->modavailability = $modavailability ?? false;
return $availability->has_availability($output);
}
@@ -319,7 +320,8 @@ class cm implements named_templatable, renderable {
$this->mod,
$this->displayoptions
);
$data->controlmenu = $controlmenu->export_for_template($output);
$data->controlmenu = $controlmenu->export_for_template($output) ?? false;
if (!$this->format->supports_components()) {
// Add the legacy YUI move link.
$data->moveicon = course_get_cm_move($this->mod, $returnsection);
@@ -350,8 +352,8 @@ class cm implements named_templatable, renderable {
protected function add_visibility_data(stdClass &$data, renderer_base $output): bool {
$visibility = new $this->visibilityclass($this->format, $this->section, $this->mod);
$templatedata = $visibility->export_for_template($output);
$data->visibility = $templatedata ?? false;
if ($templatedata) {
$data->visibility = $templatedata;
return true;
}
return false;
@@ -75,6 +75,19 @@ class availability extends section_avalability {
$this->hasavailabilityname = 'hasmodavailability';
}
/**
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
if (!$this->format->show_activity_editor_options($this->mod)) {
return null;
}
return parent::export_for_template($output);
}
/**
* Get the availability data to be used as the context for a mustache template.
*
@@ -56,11 +56,15 @@ class completion implements named_templatable, renderable {
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): ?stdClass {
global $USER;
if (!$this->format->show_activity_editor_options($this->mod)) {
return null;
}
$course = $this->mod->get_course();
$showcompletionconditions = $course->showcompletionconditions == COMPLETION_SHOW_CONDITIONS;
@@ -62,12 +62,16 @@ class controlmenu extends basecontrolmenu {
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
public function export_for_template(\renderer_base $output): ?stdClass {
$mod = $this->mod;
if (!$this->format->show_activity_editor_options($this->mod)) {
return null;
}
$menu = $this->get_action_menu($output);
if (empty($menu)) {
@@ -78,6 +78,9 @@ class visibility implements named_templatable, renderable {
if (!$this->show_visibility()) {
return null;
}
if (!$this->format->show_activity_editor_options($this->mod)) {
return null;
}
$format = $this->format;
// In rare legacy cases, the section could be stealth (orphaned) but they are not editable.
if (!$format->show_editor()
@@ -0,0 +1,35 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contains the default delegated section course format output class.
*
* @package core_courseformat
* @copyright 2024 Mikel Martín <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_courseformat\output\local\content;
/**
* Base class to render a delegated section.
*
* @package core_courseformat
* @copyright 2024 Mikel Martín <[email protected]>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class delegatedsection extends section {
}
@@ -152,7 +152,7 @@ class section implements named_templatable, renderable {
'highlightedlabel' => $format->get_section_highlighted_name(),
'sitehome' => $course->id == SITEID,
'editing' => $PAGE->user_is_editing(),
'displayonesection' => ($course->id != SITEID && !is_null($format->get_sectionid())),
'displayonesection' => ($course->id != SITEID && $format->get_sectionid() == $section->id),
];
$haspartials = [];
@@ -77,9 +77,9 @@ class availability implements named_templatable, renderable {
* Export this data so it can be used as the context for a mustache template.
*
* @param \renderer_base $output typically, the renderer that's calling this function
* @return stdClass data context for a mustache template
* @return stdClass|null data context for a mustache template
*/
public function export_for_template(\renderer_base $output): stdClass {
public function export_for_template(\renderer_base $output): ?stdClass {
$this->build_export_data($output);
return $this->data;
}
@@ -82,7 +82,7 @@ class header implements named_templatable, renderable {
$data->title = $output->section_title_without_link($section, $course);
$data->sitehome = true;
} else {
if (is_null($format->get_sectionid())) {
if (is_null($format->get_sectionid()) || $format->get_sectionid() != $section->id) {
// All sections are displayed.
if (!$data->editing) {
$data->title = $output->section_title($section, $course);
@@ -114,10 +114,13 @@ class header implements named_templatable, renderable {
$data->name = get_section_name($course, $section);
$data->selecttext = $format->get_format_string('selectsection', $data->name);
if (!$format->get_sectionnum()) {
if (!$format->get_sectionnum() && !$section->is_delegated()) {
$data->sectionbulk = true;
}
// Delegated sections in main course page need to have h4 tag, h3 otherwise.
$data->headinglevel = ($section->is_delegated() && is_null($format->get_sectionid())) ? 4 : 3;
return $data;
}
}
@@ -65,9 +65,11 @@
<div class="activity-item focus-control {{#modstealth}}hiddenactivity{{/modstealth}}{{!
}}{{#modhiddenfromstudents}}hiddenactivity{{/modhiddenfromstudents}}{{!
}}{{#modinline}}activityinline{{/modinline}}" data-activityname="{{activityname}}" data-region="activity-card">
{{$ core_courseformat/local/content/cm/bulkselect }}
{{> core_courseformat/local/content/cm/bulkselect }}
{{/ core_courseformat/local/content/cm/bulkselect }}
{{#cmbulk}}
{{$ core_courseformat/local/content/cm/bulkselect }}
{{> core_courseformat/local/content/cm/bulkselect }}
{{/ core_courseformat/local/content/cm/bulkselect }}
{{/cmbulk}}
{{!
Place the actual content of the activity-item in a separate template to make it easier for other formats to add
additional content to the activity wrapper.
@@ -139,7 +139,7 @@
{{! Description }}
{{#altcontent}}
<div class="activity-altcontent d-flex text-break {{#hasname}}activity-description{{/hasname}}">
<div class="activity-altcontent text-break {{#hasname}}activity-description{{/hasname}}">
{{{altcontent}}}
</div>
{{/altcontent}}
@@ -0,0 +1,93 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template core_courseformat/local/content/delegatedsection
Displays a course section.
Note: This template is a wrapper around the section/content template to allow course formats and theme designers to
modify parts of the wrapper without having to copy/paste the entire template.
Example context (json):
{
"num": 3,
"id": 35,
"controlmenu": "[tools menu]",
"header": {
"name": "Section title",
"title": "<a href=\"http://moodle/course/view.php?id=5#section-0\">Section title</a>",
"url": "#",
"ishidden": true,
"headinglevel": 3
},
"cmlist": {
"cms": [
{
"cmitem": {
"cmformat": {
"cmname": "<a class=\"aalink\" href=\"#\"><span class=\"instancename\">Assign example</span></a>",
"hasname": "true"
},
"id": 4,
"anchor": "activity-4",
"module": "assign",
"extraclasses": ""
}
}
],
"hascms": true
},
"ishidden": false,
"iscurrent": true,
"currentlink": "<span class=\"accesshide\">This topic</span>",
"availability": {
"info": "<span class=\"badge bg-info\">Hidden from students</span>",
"hasavailability": true
},
"summary": {
"summarytext": "Summary text!"
},
"controlmenu": {
"menu": "<a href=\"#\" class=\"d-inline-block dropdown-toggle icon-no-margin\">Edit<b class=\"caret\"></b></a>",
"hasmenu": true
},
"cmcontrols": "[Add an activity or resource]",
"iscoursedisplaymultipage": true,
"sectionreturnnum": 0,
"contentcollapsed": false,
"sitehome": false,
"highlightedlabel" : "Highlighted"
}
}}
<ul class="p-0">
<li id="section-{{num}}"
class="section course-section main {{#onlysummary}} section-summary {{/onlysummary}} clearfix
{{#ishidden}} hidden {{/ishidden}} {{#iscurrent}}{{^displayonesection}} current {{/displayonesection}}{{/iscurrent}}
{{#isstealth}} orphaned {{/isstealth}} delegated-section"
data-sectionid="{{num}}"
data-sectionreturnnum="{{sectionreturnnum}}"
data-for="section"
data-id="{{id}}"
data-number="{{num}}"
>
<div class="section-item">
{{$ core_courseformat/local/content/section/content }}
{{> core_courseformat/local/content/section/content }}
{{/ core_courseformat/local/content/section/content }}
</div>
</li>
</ul>
@@ -28,7 +28,8 @@
"name": "Section title",
"title": "<a href=\"http://moodle/course/view.php?id=5#section-0\">Section title</a>",
"url": "#",
"ishidden": true
"ishidden": true,
"headinglevel": 3
},
"cmlist": {
"cms": [
@@ -27,7 +27,8 @@
"url": "#",
"headerdisplaymultipage": true,
"sectionbulk": true,
"editing": 0
"editing": 0,
"headinglevel": 3
}
}}
{{#sectionbulk}}
@@ -50,7 +51,7 @@
{{/sitehome}}
{{^sitehome}}
{{^displayonesection}}
<div class="d-flex align-items-start position-relative">
<div class="d-flex align-items-center position-relative">
<a role="button"
data-toggle="collapse"
data-for="sectiontoggler"
@@ -63,16 +64,18 @@
aria-label="{{name}}">
<span class="expanded-icon icon-no-margin p-2" title="{{#str}} collapse, core {{/str}}">
{{#pix}} t/expandedchevron, core {{/pix}}
<span class="sr-only">{{#str}} collapse, core {{/str}}</span>
</span>
<span class="collapsed-icon icon-no-margin p-2" title="{{#str}} expand, core {{/str}}">
<span class="dir-rtl-hide">{{#pix}} t/collapsedchevron, core {{/pix}}</span>
<span class="dir-ltr-hide">{{#pix}} t/collapsedchevron_rtl, core {{/pix}}</span>
<span class="sr-only">{{#str}} expand, core {{/str}}</span>
</span>
</a>
<h3 class="h4 sectionname course-content-item d-flex align-self-stretch align-items-center mb-0"
<h{{headinglevel}} class="h4 sectionname course-content-item d-flex align-self-stretch align-items-center mb-0"
id="sectionid-{{id}}-title" data-for="section_title" data-id="{{id}}" data-number="{{num}}">
{{{title}}}
</h3>
</h{{headinglevel}}>
</div>
{{/displayonesection}}
{{/sitehome}}
@@ -45,7 +45,7 @@ class section extends section_base {
$data = parent::export_for_template($output);
if (!$this->format->get_sectionnum()) {
if (!$this->format->get_sectionnum() && !$this->section->is_delegated()) {
$addsectionclass = $format->get_output_classname('content\\addsection');
$addsection = new $addsectionclass($format);
$data->numsections = $addsection->export_for_template($output);
+3 -4
View File
@@ -141,10 +141,9 @@ class core_course_renderer extends plugin_renderer_base {
* Build the HTML for the module chooser javascript popup.
*
* @param int $courseid The course id to fetch modules for.
* @param int|null $sectionnum The section number to fetch modules for.
* @return string
*/
public function course_activitychooser($courseid, ?int $sectionnum = null) {
public function course_activitychooser($courseid) {
if (!$this->page->requires->should_create_one_time_item_now('core_course_modchooser')) {
return '';
@@ -154,7 +153,7 @@ class core_course_renderer extends plugin_renderer_base {
$chooserconfig = (object) [
'tabmode' => get_config('core', 'activitychoosertabmode'),
];
$this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid, $chooserconfig, $sectionnum]);
$this->page->requires->js_call_amd('core_course/activitychooser', 'init', [$courseid, $chooserconfig]);
return '';
}
@@ -232,7 +231,7 @@ class core_course_renderer extends plugin_renderer_base {
$ajaxcontrol = $this->render_from_template('course/activitychooserbutton', $data);
// Load the JS for the modal.
$this->course_activitychooser($course->id, $section);
$this->course_activitychooser($course->id);
return $ajaxcontrol;
}
+21
View File
@@ -19,8 +19,10 @@ namespace mod_subsection;
use cm_info;
use context_module;
use completion_info;
use core_courseformat\formatactions;
use mod_subsection\event\course_module_viewed;
use moodle_page;
use section_info;
use stdClass;
/**
@@ -180,4 +182,23 @@ class manager {
$completion = new completion_info($course);
$completion->set_module_viewed($this->cm);
}
/**
* Get the delegated section info.
*
* @return section_info the delegated section info
*/
public function get_delegated_section_info(): section_info {
$delegatedsection = $this->cm->get_delegated_section_info();
if (!$delegatedsection) {
// Some restorations can produce a situation where the section is not found.
// In that case, we create a new one.
$delegatedsection = formatactions::section($this->cm->course)->create_delegated(
self::PLUGINNAME,
$this->cm->id,
(object) ['name' => $this->instance->name],
);
}
return $delegatedsection;
}
}
+36
View File
@@ -207,3 +207,39 @@ function subsection_extend_navigation($subsectionnode, $course, $module, $cm) {
*/
function subsection_extend_settings_navigation($settingsnav, $subsectionnode = null) {
}
/**
* Sets dynamic information about a course module
*
* This function is called from cm_info when displaying the module
* mod_subsection can be displayed inline on course page and therefore have no course link
*
* @param cm_info $cm
*/
function subsection_cm_info_dynamic(cm_info $cm) {
$cm->set_no_view_link();
}
/**
* Sets the special subsection display on course page.
*
* @param cm_info $cm Course-module object
*/
function subsection_cm_info_view(cm_info $cm) {
global $DB, $PAGE;
$cm->set_custom_cmlist_item(true);
$course = $DB->get_record('course', ['id' => $cm->course], '*', MUST_EXIST);
// Get the section info.
$delegatedsection = manager::create_from_coursemodule($cm)->get_delegated_section_info();
// Render the delegated section.
$format = course_get_format($course);
$renderer = $PAGE->get_renderer('format_' . $course->format);
$outputclass = $format->get_output_classname('content\\delegatedsection');
/** @var \core_courseformat\output\local\content\delegatedsection $delegatedsectionoutput */
$delegatedsectionoutput = new $outputclass($format, $delegatedsection);
$cm->set_content($renderer->render($delegatedsectionoutput), true);
}
@@ -101,7 +101,7 @@ Feature: The module menu replaces the delegated section menu
When I choose "Edit settings" in the open action menu
And the field "Section name" matches value "Subsection1"
And I click on "Cancel" "button"
And I am on the "Subsection1" "subsection activity" page
And I am on the "C1 > Subsection1" "course > section" page
# Subsection page. Open the section header action menu.
And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element"
And I choose "Edit settings" in the open action menu
@@ -120,7 +120,7 @@ Feature: The module menu replaces the delegated section menu
When I choose "Permalink" in the open action menu
Then I click on "Copy to clipboard" "link"
And I should see "Text copied to clipboard"
And I am on the "Subsection1" "subsection activity" page
And I am on the "C1 > Subsection1" "course > section" page
# Subsection page. Open the section header action menu.
And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element"
And I choose "Permalink" in the open action menu
@@ -153,7 +153,7 @@ Feature: The module menu replaces the delegated section menu
And I choose "Delete" in the open action menu
And I click on "Delete" "button" in the "Delete activity?" "dialogue"
And "Subsection2" "link" should not exist in the "#region-main-box" "css_element"
And I am on the "Subsection3" "subsection activity" page
And I am on the "C1 > Subsection3" "course > section" page
# Subsection page. Open the section header action menu.
And I click on "Edit" "icon" in the "[data-region='header-actions-container']" "css_element"
And "Delete" "link" should not exist in the "[data-region='header-actions-container']" "css_element"
@@ -0,0 +1,66 @@
@mod @mod_subsection
Feature: Users view subsections on course page
In order to use subsections
As an user
I need to view subsections on course page
Background:
Given I enable "subsection" "mod" plugin
And the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Teacher | 1 | teacher1@example.com |
| student1 | Student | 1 | student1@example.com |
And the following "courses" exist:
| fullname | shortname | category | numsections |
| Course 1 | C1 | 0 | 2 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following "activities" exist:
| activity | name | course | idnumber | section |
| subsection | Subsection1 | C1 | sub1 | 1 |
| page | Page1 in Subsection1 | C1 | page11 | 3 |
| subsection | Subsection2 | C1 | sub2 | 1 |
@javascript
Scenario: Student can view, expand and collapse subsections on course page
When I log in as "student1"
And I am on "Course 1" course homepage
Then I should see "Subsection1" in the "region-main" "region"
And I should see "Page1 in Subsection1" in the "Subsection1" "activity"
And I click on "Collapse" "link" in the "Subsection1" "activity"
And I should not see "Page1 in Subsection1" in the "Subsection1" "activity"
And I click on "Expand" "link" in the "Subsection1" "activity"
And I click on "Page1 in Subsection1" "link" in the "Subsection1" "activity"
@javascript
Scenario: Teacher can create activities inside subsections on course page
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
# Add an assignment to the top of Subsection1.
And I hover "Insert an activity or resource before 'Page1 in Subsection1'" "button"
And I press "Insert an activity or resource before 'Page1 in Subsection1'"
And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue"
And I set the following fields to these values:
| Assignment name | Assignment1 in Subsection1 |
And I press "Save and return to course"
Then I should see "Assignment1 in Subsection1" in the "Subsection1" "activity"
# Add an assignment to the empty Subsection2.
And I add an "assign" activity to course "Course 1" section "4" and I fill the form with:
| Assignment name | Assignment1 in Subsection2 |
And I should see "Assignment1 in Subsection2" in the "Subsection2" "activity"
@javascript
Scenario: Teacher can create activities between subsections on course page
When I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I hover "Insert an activity or resource before 'Subsection2'" "button"
And I press "Insert an activity or resource before 'Subsection2'"
And I click on "Add a new Assignment" "link" in the "Add an activity or resource" "dialogue"
And I set the following fields to these values:
| Assignment name | Assignment between subsections |
And I press "Save and return to course"
And I wait "5" seconds
And "Assignment between subsections" "link" should appear after "Page1 in Subsection1" "text"
And "Assignment between subsections" "link" should appear before "Subsection2" "text"
@@ -25,10 +25,11 @@ Feature: Teachers can rename subsections
@javascript
Scenario: Renaming the subsection activity changes the subsection name
Given I should see "Subsection activity" in the "page-content" "region"
When I set the field "Edit title" in the "Subsection activity" "activity" to "New name"
When I set the field "Edit section name" in the "Subsection activity" "activity" to "New name"
And I should not see "Subsection activity" in the "region-main" "region"
And I should see "New name" in the "page-content" "region"
Then I click on "New name" "link" in the "page-content" "region"
Then I open "New name" actions menu
And I choose "View" in the open action menu
And I should see "New name" in the "page" "region"
And I should see "Subactivity" in the "region-main" "region"
@@ -1,6 +1,6 @@
@mod @mod_subsection
Feature: Teachers create and destroy subsections
In order to use subsections
Feature: Teachers create and destroy subsections on section page
In order to use subsections on section page
As an teacher
I need to create and destroy subsections
@@ -17,18 +17,6 @@ Feature: Teachers create and destroy subsections
| teacher1 | C1 | editingteacher |
And I log in as "teacher1"
Scenario: Subsections are not listed as regular sections
Given the following "activities" exist:
| activity | name | course | idnumber | section |
| subsection | Subsection1 | C1 | forum1 | 1 |
| data | Subactivity1 | C1 | data1 | 3 |
When I am on "Course 1" course homepage
Then "Subsection1" "section" should not exist
And I should not see "Subactivity1" in the "region-main" "region"
And I click on "Subsection1" "link" in the "region-main" "region"
And I should see "Subsection1" in the "page" "region"
And I should see "Subactivity1" in the "region-main" "region"
Scenario: Activities can be created in a subsection
Given the following "activities" exist:
| activity | name | course | idnumber | section |
@@ -40,8 +28,6 @@ Feature: Teachers create and destroy subsections
And I am on "Course 1" course homepage
And I click on "Subsection1" "link" in the "region-main" "region"
Then I should see "Test assignment name" in the "region-main" "region"
And I am on "Course 1" course homepage
And I should not see "Test assignment name" in the "region-main" "region"
@javascript
Scenario: Teacher can create activities in a subsection page with the activity chooser
@@ -57,5 +43,3 @@ Feature: Teachers create and destroy subsections
| Description | Test assignment description |
And I press "Save and return to course"
Then I should see "Test assignment name" in the "region-main" "region"
And I am on "Course 1" course homepage
And I should not see "Test assignment name" in the "region-main" "region"
+1 -14
View File
@@ -39,19 +39,6 @@ require_login($course, true, $cm);
$modulecontext = $manager->get_context();
$manager->set_module_viewed($course);
$delegatesection = $manager->get_delegated_section_info();
$modinfo = get_fast_modinfo($course);
$delegatesection = $modinfo->get_section_info_by_component(manager::PLUGINNAME, $moduleinstance->id);
if (!$delegatesection) {
// Some restorations can produce a situation where the section is not found.
// In that case, we create a new one.
formatactions::section($course)->create_delegated(
manager::PLUGINNAME,
$id,
(object) [
'name' => $moduleinstance->name,
]
);
}
redirect(new moodle_url('/course/section.php', ['id' => $delegatesection->id]));
+72 -13
View File
@@ -1078,6 +1078,7 @@ span.editinstructions {
/* Variables definition*/
$activity-item-padding: 0.75rem !default;
$activity-border-radius: 1rem !default;
$activity-border-width: 2px !default;
$activity-hover-border-color: $primary !default;
@@ -1211,10 +1212,15 @@ $divider-hover-color: $primary !default;
}
// Override '.btn.btn-icon' styles from buttons.scss to make action menu buttons smaller.
.action-menu .btn.btn-icon {
height: map-get($iconsizes, 5);
width: map-get($iconsizes, 5);
@include border-radius();
.action-menu {
.btn.btn-icon {
height: map-get($iconsizes, 5);
width: map-get($iconsizes, 5);
@include border-radius();
}
&.section-actions .btn.btn-icon {
font-size: $font-size-lg;
}
}
.section-summary-activities {
@@ -1299,8 +1305,9 @@ $divider-hover-color: $primary !default;
padding-top: map-get($spacers, 1);
margin-top: map-get($spacers, 1);
border-top: $border-width solid $border-color;
// Custom styles for activity while editing.
.editing & {
// Custom styles for activity while editing and for the first activity card in the section.
.editing &,
.section &:first-child {
// Remove activity top border and spacing, while editing a separator is displayed.
padding-top: 0;
margin-top: 0;
@@ -1310,10 +1317,10 @@ $divider-hover-color: $primary !default;
.activity-item {
position: relative;
padding: .75rem;
background-color: $white;
padding: $activity-item-padding;
@include border-radius($activity-border-radius);
&.activityinline {
padding: .75rem 0;
padding: $activity-item-padding 0;
}
&.hiddenactivity {
background-color: $gray-100;
@@ -1325,14 +1332,13 @@ $divider-hover-color: $primary !default;
// Custom styles for activity cards while editing.
.editing & {
cursor: move;
border: $activity-border-width solid transparent;
@include border-radius($activity-border-radius);
.a {
cursor: pointer;
}
&:hover,
// Add a border on hover to the activity card (avoiding hover effect in inner activities).
&:hover:not(:has(.activity:hover)),
&.selected {
border: $activity-border-width solid $activity-hover-border-color;
outline: $activity-border-width solid $activity-hover-border-color;
box-shadow: $box-shadow-sm;
.activityiconcontainer,
.badge {
@@ -1677,6 +1683,11 @@ $divider-hover-color: $primary !default;
position: absolute;
left: -2rem;
}
// Delegated sections are not available for bulk editing.
&:has(.delegated-section):hover {
outline: none !important; // stylelint-disable-line declaration-no-important
box-shadow: none !important; // stylelint-disable-line declaration-no-important
}
}
.course-section-header .bulkselect {
left: -2.75rem;
@@ -1737,6 +1748,7 @@ $divider-hover-color: $primary !default;
}
/* Styles for the Move activity and Move section modals */
.move-activity-tree {
.collapse-list-item {
@include border-radius();
@@ -1776,3 +1788,50 @@ $divider-hover-color: $primary !default;
font-weight: bold;
}
}
/* Subsection specific styles */
.activity.subsection {
border-top: none;
> .activity-item {
border: $border-width solid $border-color;
padding: 0;
.activity-altcontent {
margin-top: 0;
}
}
.section {
margin-top: 0;
.section-item {
border: none;
padding: $activity-item-padding;
}
.icons-collapse-expand:has(+ h4) {
height: map-get($iconsizes, 4);
width: map-get($iconsizes, 4);
font-size: $font-size-sm;
}
h4 {
font-size: $h5-font-size;
}
.section-actions .btn.btn-icon {
font-size: inherit;
}
}
+ .activity {
border-top: none;
}
// Allow v-parent-focus only for closest acvitity (avoiding subsection).
.focus-control:not(:has(.focus-control)) {
&:focus-within .v-parent-focus,
&:hover .v-parent-focus {
opacity: 1 !important; // stylelint-disable-line declaration-no-important
visibility: visible !important; // stylelint-disable-line declaration-no-important
}
}
.focus-control:focus-within .focus-control .v-parent-focus,
.focus-control:hover .focus-control .v-parent-focus {
opacity: 0;
visibility: hidden;
}
}
+2 -2
View File
@@ -11,7 +11,6 @@ li.activity.label,
.file-picker td.label {
background: inherit;
color: inherit;
border: inherit;
text-shadow: none;
white-space: normal;
display: block;
@@ -21,6 +20,7 @@ li.activity.label,
}
.file-picker td.label {
border: inherit;
display: table-cell;
text-align: right;
padding: 8px;
@@ -55,7 +55,7 @@ li.activity.label,
* it to white?
*/
li.section.hidden,
.section.hidden,
.block.hidden,
.block.invisible {
visibility: visible;
+57 -10
View File
@@ -29068,6 +29068,9 @@ span.editinstructions .alert-link {
width: 32px;
border-radius: 0.5rem;
}
.course-section .action-menu.section-actions .btn.btn-icon {
font-size: 1.171875rem;
}
.course-section .section-summary-activities .icon {
width: inherit;
color: #0f6cbf;
@@ -29133,7 +29136,7 @@ span.editinstructions .alert-link {
margin-top: 0.25rem;
border-top: 1px solid #dee2e6;
}
.editing .activity {
.editing .activity, .section .activity:first-child {
padding-top: 0;
margin-top: 0;
border-top: none;
@@ -29142,7 +29145,7 @@ span.editinstructions .alert-link {
.activity-item {
position: relative;
padding: 0.75rem;
background-color: #fff;
border-radius: 1rem;
}
.activity-item.activityinline {
padding: 0.75rem 0;
@@ -29156,18 +29159,16 @@ span.editinstructions .alert-link {
}
.editing .activity-item {
cursor: move;
border: 2px solid transparent;
border-radius: 1rem;
}
.editing .activity-item .a {
cursor: pointer;
}
.editing .activity-item:hover, .editing .activity-item.selected {
border: 2px solid #0f6cbf;
.editing .activity-item:hover:not(:has(.activity:hover)), .editing .activity-item.selected {
outline: 2px solid #0f6cbf;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editing .activity-item:hover .activityiconcontainer,
.editing .activity-item:hover .badge, .editing .activity-item.selected .activityiconcontainer,
.editing .activity-item:hover:not(:has(.activity:hover)) .activityiconcontainer,
.editing .activity-item:hover:not(:has(.activity:hover)) .badge, .editing .activity-item.selected .activityiconcontainer,
.editing .activity-item.selected .badge {
mix-blend-mode: multiply;
}
@@ -29863,6 +29864,10 @@ span.editinstructions .alert-link {
position: absolute;
left: -2rem;
}
.bulkenabled .activity-item:has(.delegated-section):hover {
outline: none !important;
box-shadow: none !important;
}
.bulkenabled .course-section-header .bulkselect {
left: -2.75rem;
position: relative;
@@ -29941,6 +29946,48 @@ span.editinstructions .alert-link {
font-weight: bold;
}
/* Subsection specific styles */
.activity.subsection {
border-top: none;
}
.activity.subsection > .activity-item {
border: 1px solid #dee2e6;
padding: 0;
}
.activity.subsection > .activity-item .activity-altcontent {
margin-top: 0;
}
.activity.subsection .section {
margin-top: 0;
}
.activity.subsection .section .section-item {
border: none;
padding: 0.75rem;
}
.activity.subsection .section .icons-collapse-expand:has(+ h4) {
height: 24px;
width: 24px;
font-size: 0.8203125rem;
}
.activity.subsection .section h4 {
font-size: 1.171875rem;
}
.activity.subsection .section .section-actions .btn.btn-icon {
font-size: inherit;
}
.activity.subsection + .activity {
border-top: none;
}
.activity.subsection .focus-control:not(:has(.focus-control)):focus-within .v-parent-focus, .activity.subsection .focus-control:not(:has(.focus-control)):hover .v-parent-focus {
opacity: 1 !important;
visibility: visible !important;
}
.activity.subsection .focus-control:focus-within .focus-control .v-parent-focus,
.activity.subsection .focus-control:hover .focus-control .v-parent-focus {
opacity: 0;
visibility: hidden;
}
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
:target,
:focus {
@@ -36146,7 +36193,6 @@ li.activity.label,
.file-picker td.label {
background: inherit;
color: inherit;
border: inherit;
text-shadow: none;
white-space: normal;
display: block;
@@ -36156,6 +36202,7 @@ li.activity.label,
}
.file-picker td.label {
border: inherit;
display: table-cell;
text-align: right;
padding: 8px;
@@ -36187,7 +36234,7 @@ li.activity.label,
* nice effect, though they'd still be active. Maybe reverse
* it to white?
*/
li.section.hidden,
.section.hidden,
.block.hidden,
.block.invisible {
visibility: visible;
+57 -10
View File
@@ -29068,6 +29068,9 @@ span.editinstructions .alert-link {
width: 32px;
border-radius: 0.25rem;
}
.course-section .action-menu.section-actions .btn.btn-icon {
font-size: 1.171875rem;
}
.course-section .section-summary-activities .icon {
width: inherit;
color: #0f6cbf;
@@ -29133,7 +29136,7 @@ span.editinstructions .alert-link {
margin-top: 0.25rem;
border-top: 1px solid #dee2e6;
}
.editing .activity {
.editing .activity, .section .activity:first-child {
padding-top: 0;
margin-top: 0;
border-top: none;
@@ -29142,7 +29145,7 @@ span.editinstructions .alert-link {
.activity-item {
position: relative;
padding: 0.75rem;
background-color: #fff;
border-radius: 1rem;
}
.activity-item.activityinline {
padding: 0.75rem 0;
@@ -29156,18 +29159,16 @@ span.editinstructions .alert-link {
}
.editing .activity-item {
cursor: move;
border: 2px solid transparent;
border-radius: 1rem;
}
.editing .activity-item .a {
cursor: pointer;
}
.editing .activity-item:hover, .editing .activity-item.selected {
border: 2px solid #0f6cbf;
.editing .activity-item:hover:not(:has(.activity:hover)), .editing .activity-item.selected {
outline: 2px solid #0f6cbf;
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
}
.editing .activity-item:hover .activityiconcontainer,
.editing .activity-item:hover .badge, .editing .activity-item.selected .activityiconcontainer,
.editing .activity-item:hover:not(:has(.activity:hover)) .activityiconcontainer,
.editing .activity-item:hover:not(:has(.activity:hover)) .badge, .editing .activity-item.selected .activityiconcontainer,
.editing .activity-item.selected .badge {
mix-blend-mode: multiply;
}
@@ -29863,6 +29864,10 @@ span.editinstructions .alert-link {
position: absolute;
left: -2rem;
}
.bulkenabled .activity-item:has(.delegated-section):hover {
outline: none !important;
box-shadow: none !important;
}
.bulkenabled .course-section-header .bulkselect {
left: -2.75rem;
position: relative;
@@ -29941,6 +29946,48 @@ span.editinstructions .alert-link {
font-weight: bold;
}
/* Subsection specific styles */
.activity.subsection {
border-top: none;
}
.activity.subsection > .activity-item {
border: 1px solid #dee2e6;
padding: 0;
}
.activity.subsection > .activity-item .activity-altcontent {
margin-top: 0;
}
.activity.subsection .section {
margin-top: 0;
}
.activity.subsection .section .section-item {
border: none;
padding: 0.75rem;
}
.activity.subsection .section .icons-collapse-expand:has(+ h4) {
height: 24px;
width: 24px;
font-size: 0.8203125rem;
}
.activity.subsection .section h4 {
font-size: 1.171875rem;
}
.activity.subsection .section .section-actions .btn.btn-icon {
font-size: inherit;
}
.activity.subsection + .activity {
border-top: none;
}
.activity.subsection .focus-control:not(:has(.focus-control)):focus-within .v-parent-focus, .activity.subsection .focus-control:not(:has(.focus-control)):hover .v-parent-focus {
opacity: 1 !important;
visibility: visible !important;
}
.activity.subsection .focus-control:focus-within .focus-control .v-parent-focus,
.activity.subsection .focus-control:hover .focus-control .v-parent-focus {
opacity: 0;
visibility: hidden;
}
/* Anchor link offset fix. This makes hash links scroll 60px down to account for the fixed header. */
:target,
:focus {
@@ -36146,7 +36193,6 @@ li.activity.label,
.file-picker td.label {
background: inherit;
color: inherit;
border: inherit;
text-shadow: none;
white-space: normal;
display: block;
@@ -36156,6 +36202,7 @@ li.activity.label,
}
.file-picker td.label {
border: inherit;
display: table-cell;
text-align: right;
padding: 8px;
@@ -36187,7 +36234,7 @@ li.activity.label,
* nice effect, though they'd still be active. Maybe reverse
* it to white?
*/
li.section.hidden,
.section.hidden,
.block.hidden,
.block.invisible {
visibility: visible;