diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss index aaeecc57c3ad8919ce21b1fe6ae21b580ebf3330..55da7833b1c04d411f0fb005369b3dd74ed4447e 100755 --- a/resources/assets/stylesheets/scss/courseware.scss +++ b/resources/assets/stylesheets/scss/courseware.scss @@ -278,6 +278,7 @@ $consum_ribbon_width: calc(100% - 58px); max-width: calc(100% - 106px); .cw-ribbon-nav { + display: flex; min-width: 75px; } @@ -329,14 +330,13 @@ $consum_ribbon_width: calc(100% - 58px); .cw-ribbon-button { height: 24px; width: 24px; - margin: 0 0.5em; + margin: 0 7px; + padding: 1px 2px; border: none; background-color: transparent; background-repeat: no-repeat; background-position: center; background-size: 24px; - cursor: pointer; - outline: none; &.cw-ribbon-button-menu { @include background-icon(table-of-contents, clickable, 24); @@ -423,69 +423,64 @@ $consum_ribbon_width: calc(100% - 58px); top: 0; background-color: $white; margin: 0; - padding: 10px 0 0 0; + padding: 0; color: $base-color; display: flex; - border-bottom: solid thin $content-color-40; z-index: 43; .cw-tools-hide-button { + position: absolute; border: none; height: 36px; width: 24px; min-width: 24px; margin-right: 1em; padding: 0 4px; - float: right; + right: 0; + top: 12px; @include background-icon(decline, clickable, 24); background-repeat: no-repeat; background-size: 24px; background-position: center right; background-color: #fff; - outline: none; - cursor: pointer; } - ul { - list-style-type: none; + >.cw-ribbon-tool-content-tablist { width: 100%; - display: flex; - flex-wrap: wrap; - margin: 0; - padding: 0; - } - li { - padding: 0 8px; - margin-top: 8px; - text-align: center; - flex-grow: 0.5; - cursor: pointer; - - &:after { - display: block; - content: ''; - border-bottom: solid 3px $dark-gray-color-75; - transform: scaleX(0); - transition: transform 300ms ease-in-out; - margin: 17px 0 0 0; - } - - &.active, - &:hover { - color: $black; - &:after { - transform: scaleX(1); + >.cw-tabs-nav { + border: none; + width: calc(100% - 48px); + + >button { + padding: 18px 8px 4px 8px; + margin-top: 2px; + max-width: unset; + flex-grow: 0.5; + &::after { + margin-top: 16px; + } } - } - } - &:hover li { - &.active::after { - transform: scaleX(0); } - &:hover::after { - transform: scaleX(1); + >.cw-tabs-content { + border: none; + border-top: solid thin $content-color-40; + padding: 0; + + >.cw-tab { + max-height: 700px; + padding: 14px 8px 8px 8px; + overflow-y: auto; + overflow-x: hidden; + scrollbar-width: thin; + scrollbar-color: $base-color $white; + + &.cw-ribbon-tool-blockadder-tab { + overflow: hidden; + padding: 0; + } + } } } } @@ -1496,8 +1491,9 @@ $icons: ( border: solid thin $content-color-40; border-bottom: none; - li { + button { background-color: $white; + border: none; padding: 1em 0 4px 0; margin: 0 7px 0 21px; color: $base-color; @@ -1603,7 +1599,7 @@ $icons: ( .cw-tabs-content { border: none; min-width: 500px; - min-height: 400px; + min-height: 400px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: $base-color #f5f5f5; @@ -1642,54 +1638,35 @@ b l o c k a d d e r * * * * * * * * * */ .cw-tools-element-adder-tabs { - display: flex; - list-style: none; - padding: 0; - margin: 0; - border: solid thin $content-color-40; - border-bottom: none; - - .cw-tools-element-adder-tab { - background-color: $white; - padding: 1em 1.5em 0 1.5em; - margin: 0 1em; - color: $base-color; - cursor: pointer; - text-align: center; - flex-grow: 1; - - &:after { - display: block; - margin-top: 4px; - content: ''; - border-bottom: solid 3px $dark-gray-color-75; - transform: scaleX(0); - transition: transform 300ms ease-in-out; - } + .cw-tabs-nav { + margin-top: 4px; + border: none; + border-bottom: solid thin $content-color-40; - &.active, - &:hover { - color: $black; - &:after { - transform: scaleX(1); - } + button { + max-width: unset; + padding: 1em 1.5em 4px 1.5em; + margin: 0px 2em; } - } - - &:hover .cw-tools-element-adder-tab{ - &.active::after { - transform: scaleX(0); - } - &:hover::after { - transform: scaleX(1); - } + .cw-tabs-content { + border: none; + max-height: 640px; + overflow-x: hidden; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: $base-color #f5f5f5; } -} - -.cw-tools-element-adder { - padding-bottom: 4em; + .cw-collapsible { + .cw-collapsible-content { + visibility: unset; + display: none; + &.cw-collapsible-content-open { + display: block; + } + } + } } .cw-blockadder-item { @@ -1717,6 +1694,7 @@ b l o c k a d d e r } .cw-block-adder-area { + background-color: $white; border: solid thin $content-color-40; padding: 1em 0; color: $base-color; @@ -1725,6 +1703,10 @@ b l o c k a d d e r font-weight: 600; cursor: pointer; + &:hover { + border-color: $base-color; + } + &.cw-block-adder-active { border: solid thin $base-color; background-color: $base-color; @@ -2300,10 +2282,9 @@ d a s h b o a r d } .cw-dashboard-progress-item { + display: block; border-bottom: solid thin $content-color-40; - width: 100%; - cursor: pointer; - padding: 8px 0 8px 0; + padding: 10px 0; &:hover{ background-color: hsla(217,6%,45%,.2); @@ -2330,7 +2311,6 @@ d a s h b o a r d } } .cw-dashboard-progress-item-description { - width: calc(100% - 90px); color: $base-color; padding-left: 14px; text-overflow: ellipsis; @@ -2712,22 +2692,27 @@ m a n a g e r } + .cw-manager-element-item-wrapper { + margin-bottom: 4px; + + &:last-child { + margin-bottom: 0; + } + } + .cw-manager-element-item { + display: flex; border: solid thin $content-color-40; padding: 1em; - margin-bottom: 4px; - text-align: center; + justify-content: space-between; vertical-align: middle; background-color: $white; color: $base-color; - cursor: pointer; img { vertical-align: middle; } - &:last-child { - margin-bottom: 0; - } + &:hover { color: $white; background-color: $base-color; @@ -2737,6 +2722,21 @@ m a n a g e r background-color: $white; color: $base-color; } + + &.cw-manager-element-item-inserter { + padding-left: 2em; + justify-content: start; + @include background-icon(arr_2left, clickable, 16); + background-position: 6px center; + background-repeat: no-repeat; + &:hover { + @include background-icon(arr_2left, info-alt, 16); + } + + img { + margin-right: 1em; + } + } } .cw-manager-filing { @@ -2753,6 +2753,10 @@ m a n a g e r font-weight: 600; cursor: pointer; + &:hover { + border-color: $base-color; + } + &.cw-manager-filing-active { @include background-icon(arr_eol-down, info-alt, 24); background-color: $base-color; @@ -2769,8 +2773,14 @@ m a n a g e r .cw-manager-block-buttons, .cw-manager-container-buttons, .cw-manager-element-item-buttons { - display: inline; - float: right; + display: flex; + justify-content: end; + min-width: 38px; + + a { + flex: unset; + } + img { cursor: pointer; transition: opacity 0.4s ease-in-out; @@ -4617,8 +4627,8 @@ cw tiles end list-style: none; } .cw-manager-copy-selector-course { + display: block; color: $base-color; - cursor: pointer; line-height: 2em; &:hover { diff --git a/resources/vue/components/StudipActionMenu.vue b/resources/vue/components/StudipActionMenu.vue index 95ec6a5f65a647aa26812af9c22d4b285965d273..6b8f229cd673e47e318885ab6bd5981650db9f4a 100644 --- a/resources/vue/components/StudipActionMenu.vue +++ b/resources/vue/components/StudipActionMenu.vue @@ -80,7 +80,7 @@ export default { } return { label: item.label, - url: item.url || false, + url: item.url || '#', emit: item.emit || false, emitArguments: item.emitArguments || [], icon: item.icon ? { diff --git a/resources/vue/components/StudipDialog.vue b/resources/vue/components/StudipDialog.vue index b248bb64aab3958abddca8f5b47ed1c98299fafe..1dabe6ecf6ca553da113a1ab7d525d18631aca2f 100755 --- a/resources/vue/components/StudipDialog.vue +++ b/resources/vue/components/StudipDialog.vue @@ -30,7 +30,7 @@ :class="{ 'studip-dialog-warning': question, 'studip-dialog-alert': alert }" class="studip-dialog-body" role="dialog" - :aria-modal="'true'" + aria-modal="true" :aria-labelledby="dialogTitleId" :aria-describedby="dialogDescId" ref="dialog" diff --git a/resources/vue/components/courseware/CoursewareActionWidget.vue b/resources/vue/components/courseware/CoursewareActionWidget.vue index 4772bb2cc315eb6df93eb76601169d2bca047d71..2c98a3ba22f833a96d07a4fbbe0018b25fa5751c 100644 --- a/resources/vue/components/courseware/CoursewareActionWidget.vue +++ b/resources/vue/components/courseware/CoursewareActionWidget.vue @@ -1,35 +1,59 @@ <template> <ul class="widget-list widget-links cw-action-widget" v-if="structuralElement"> - <li class="cw-action-widget-show-toc" @click="toggleTOC"> - {{ tocText }} + <li class="cw-action-widget-show-toc"> + <a href="#" @click="toggleTOC"> + {{ tocText }} + </a> + </li> + <li class="cw-action-widget-show-consume-mode"> + <a href="#" @click="showConsumeMode"> + <translate>Vollbild einschalten</translate> + </a> </li> - <li class="cw-action-widget-show-consume-mode" @click="showConsumeMode"> - <translate>Vollbild einschalten</translate> + <li v-if="canEdit" class="cw-action-widget-edit"> + <a href="#" @click="editElement"> + <translate>Seite bearbeiten</translate> + </a> </li> - <li v-if="canEdit" class="cw-action-widget-edit" @click="editElement"> - <translate>Seite bearbeiten</translate> + <li v-if="canEdit" class="cw-action-widget-sort"> + <a href="#" @click="sortContainers"> + <translate>Abschnitte sortieren</translate> + </a> </li> - <li v-if="canEdit" class="cw-action-widget-sort" @click="sortContainers"> - <translate>Abschnitte sortieren</translate> + <li v-if="canEdit" class="cw-action-widget-add"> + <a href="#" @click="addElement"> + <translate>Seite hinzufügen</translate> + </a> </li> - <li v-if="canEdit" class="cw-action-widget-add" @click="addElement"> - <translate>Seite hinzufügen</translate> + <li class="cw-action-widget-info"> + <a href="#" @click="showElementInfo"> + <translate>Informationen anzeigen</translate> + </a> </li> - <li class="cw-action-widget-info" @click="showElementInfo"><translate>Informationen anzeigen</translate></li> - <li class="cw-action-widget-star" @click="createBookmark"><translate>Lesezeichen setzen</translate></li> - <li v-if="canExport" @click="exportElement" class="cw-action-widget-export"> - <translate>Seite exportieren</translate> + <li class="cw-action-widget-star"> + <a href="#" @click="createBookmark"> + <translate>Lesezeichen setzen</translate> + </a> + </li> + <li v-if="canEdit" class="cw-action-widget-export"> + <a href="#" @click="exportElement"> + <translate>Seite exportieren</translate> + </a> </li> <li v-if="(canEdit || userIsTeacher) && canVisit" class="cw-action-widget-export-pdf"> <a :href="pdfExportURL"> <translate>Seite als pdf-Dokument exportieren</translate> </a> </li> - <li v-if="canEdit && oerEnabled && userIsTeacher" @click="oerElement" class="cw-action-widget-oer"> - <translate>Seite auf %{oerTitle} veröffentlichen</translate> + <li v-if="canEdit && oerEnabled" class="cw-action-widget-oer"> + <a href="#" @click="oerElement"> + <translate>Seite auf %{oerTitle} veröffentlichen</translate> + </a> </li> - <li v-if="!isRoot && canEdit && !isTask" class="cw-action-widget-trash" @click="deleteElement"> - <translate>Seite löschen</translate> + <li v-if="!isRoot && canEdit" class="cw-action-widget-trash"> + <a href="#" @click="deleteElement"> + <translate>Seite löschen</translate> + </a> </li> </ul> </template> @@ -55,9 +79,6 @@ export default { consumeMode: 'consumeMode', showToolbar: 'showToolbar', userIsTeacher: 'userIsTeacher', - consumeMode: 'consumeMode', - showToolbar: 'showToolbar', - userIsTeacher: 'userIsTeacher', }), isRoot() { if (!this.structuralElement) { @@ -148,7 +169,8 @@ export default { lockObject: 'lockObject', setConsumeMode: 'coursewareConsumeMode', setViewMode: 'coursewareViewMode', - setShowToolbar: 'coursewareShowToolbar' + setShowToolbar: 'coursewareShowToolbar', + setSelectedToolbarItem: 'coursewareSelectedToolbarItem' }), async editElement() { if (this.blockedByAnotherUser) { @@ -197,6 +219,7 @@ export default { }, showConsumeMode() { this.setViewMode('read'); + this.setSelectedToolbarItem('contents'); this.setConsumeMode(true); }, }, diff --git a/resources/vue/components/courseware/CoursewareAdminTemplates.vue b/resources/vue/components/courseware/CoursewareAdminTemplates.vue index c237ebb649148e259f4c3a94e1a8cc725af4a34d..b3f330c708fe76320c51ce45b720531eda8add49 100755 --- a/resources/vue/components/courseware/CoursewareAdminTemplates.vue +++ b/resources/vue/components/courseware/CoursewareAdminTemplates.vue @@ -28,10 +28,10 @@ <studip-dialog v-if="showAddTemplateDialog" :title="$gettext('Vorlage hinzufügen')" - :confirmText="'Erstellen'" - :confirmClass="'accept'" + :confirmText="$gettext('Erstellen')" + confirmClass="accept" :closeText="$gettext('Schließen')" - :closeClass="'cancel'" + closeClass="cancel" class="cw-admin-template-dialog" height="360" @close="closeAddDialog" @@ -73,10 +73,10 @@ <studip-dialog v-if="showEditTemplateDialog" :title="$gettext('Vorlage bearbeiten')" - :confirmText="'Speichern'" - :confirmClass="'accept'" + :confirmText="$gettext('Speichern')" + confirmClass="accept" :closeText="$gettext('Schließen')" - :closeClass="'cancel'" + closeClass="cancel" class="cw-admin-template-dialog" @close="closeEditDialog" @confirm="updateCurrentTemplate" diff --git a/resources/vue/components/courseware/CoursewareBlockAdderArea.vue b/resources/vue/components/courseware/CoursewareBlockAdderArea.vue index f2fdc8634865fbe029ccbde4246d99782dfef7e7..7bc25136e70b9878e0783da1a40908a42007f215 100755 --- a/resources/vue/components/courseware/CoursewareBlockAdderArea.vue +++ b/resources/vue/components/courseware/CoursewareBlockAdderArea.vue @@ -1,14 +1,15 @@ <template> - <div + <button class="cw-block-adder-area" :class="{ 'cw-block-adder-active': adderActive }" + :aria-pressed="adderActive" @click="selectBlockAdder" > <studip-icon v-show="!adderActive" shape="add" /> <studip-icon v-show="adderActive" shape="add" role="info_alt"/> <span v-show="!adderActive"><translate>Block zu diesem Abschnitt hinzufügen</translate></span> <span v-show="adderActive"><translate>Abschnitt aktiv - Blöcke werden hier eingefügt</translate></span> - </div> + </button> </template> <script> @@ -41,6 +42,7 @@ export default { } else { this.adderActive = true; this.$store.dispatch('coursewareBlockAdder', { container: this.container, section: this.section }); + this.$store.dispatch('coursewareSelectedToolbarItem', 'blockadder'); this.$store.dispatch('coursewareShowToolbar', true); } }, diff --git a/resources/vue/components/courseware/CoursewareBlockadderItem.vue b/resources/vue/components/courseware/CoursewareBlockadderItem.vue index 78b3bc6db11d1475264d60ce44e606ae226eb57c..860847cd7bed2375d83c0aa34d629028ba957925 100755 --- a/resources/vue/components/courseware/CoursewareBlockadderItem.vue +++ b/resources/vue/components/courseware/CoursewareBlockadderItem.vue @@ -1,12 +1,14 @@ <template> - <div class="cw-blockadder-item" :class="['cw-blockadder-item-' + type]" @click="addBlock"> - <header class="cw-blockadder-item-title"> - {{ title }} - </header> - <p class="cw-blockadder-item-description"> - {{ description }} - </p> - </div> + <a href="#" @click="addBlock"> + <div class="cw-blockadder-item" :class="['cw-blockadder-item-' + type]"> + <header class="cw-blockadder-item-title"> + {{ title }} + </header> + <p class="cw-blockadder-item-description"> + {{ description }} + </p> + </div> + </a> </template> <script> diff --git a/resources/vue/components/courseware/CoursewareCollapsibleBox.vue b/resources/vue/components/courseware/CoursewareCollapsibleBox.vue index 3bd0f40f6a8de4316f0a10ebb6788d5a4d1b3425..fd1d19ce925d0e6e53b93734b6d82c72734e06e2 100755 --- a/resources/vue/components/courseware/CoursewareCollapsibleBox.vue +++ b/resources/vue/components/courseware/CoursewareCollapsibleBox.vue @@ -1,8 +1,10 @@ <template> <div class="cw-collapsible" :class="{ 'cw-collapsible-open': isOpen }"> - <header :class="{ 'cw-collapsible-open': isOpen }" class="cw-collapsible-title" @click="isOpen = !isOpen"> - <studip-icon v-if="icon" :shape="icon" /> {{ title }} - </header> + <a href="#" :aria-expanded="isOpen" @click="isOpen = !isOpen"> + <header :class="{ 'cw-collapsible-open': isOpen }" class="cw-collapsible-title"> + <studip-icon v-if="icon" :shape="icon" /> {{ title }} + </header> + </a> <div class="cw-collapsible-content" :class="{ 'cw-collapsible-content-open': isOpen }"> <slot></slot> </div> diff --git a/resources/vue/components/courseware/CoursewareContainerAdderItem.vue b/resources/vue/components/courseware/CoursewareContainerAdderItem.vue index dc3bdff84fc441314de636c28decf3ee4f03dde8..bf64758f837706525329498878537d0c826a4898 100755 --- a/resources/vue/components/courseware/CoursewareContainerAdderItem.vue +++ b/resources/vue/components/courseware/CoursewareContainerAdderItem.vue @@ -1,12 +1,14 @@ <template> - <div class="cw-blockadder-item" :class="['cw-blockadder-item-' + type]" @click="addContainer"> - <header class="cw-blockadder-item-title"> - {{ title }} - </header> - <p class="cw-blockadder-item-description"> - {{ description }} - </p> - </div> + <a href="#" @click="addContainer"> + <div class="cw-blockadder-item" :class="['cw-blockadder-item-' + type]"> + <header class="cw-blockadder-item-title"> + {{ title }} + </header> + <p class="cw-blockadder-item-description"> + {{ description }} + </p> + </div> + </a> </template> <script> import { mapActions } from 'vuex'; diff --git a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue b/resources/vue/components/courseware/CoursewareContentOverviewElements.vue index 75bc3c7697532b8fad2efe1455782ad4541e6ebf..fb98985ab2d3f0230c7d9797e1df40004c3b6d80 100755 --- a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue +++ b/resources/vue/components/courseware/CoursewareContentOverviewElements.vue @@ -52,10 +52,10 @@ :title="$gettext('Neues Lernmaterial anlegen')" height="600" width="500" - :confirmText="'Erstellen'" - :confirmClass="'accept'" + :confirmText="$gettext('Erstellen')" + confirmClass="accept" :closeText="$gettext('Schließen')" - :closeClass="'cancel'" + closeClass="cancel" class="cw-structural-element-dialog" @close="closeAddDialog" @confirm="createElement" diff --git a/resources/vue/components/courseware/CoursewareCourseManager.vue b/resources/vue/components/courseware/CoursewareCourseManager.vue index aed709d1c026f5f1367b2d03e013c7d8db2598c2..a2dbe99a88d8413e7f3d253b4745482e47148c7c 100755 --- a/resources/vue/components/courseware/CoursewareCourseManager.vue +++ b/resources/vue/components/courseware/CoursewareCourseManager.vue @@ -2,7 +2,7 @@ <div class="cw-course-manager-wrapper"> <div class="cw-course-manager"> <courseware-tabs class="cw-course-manager-tabs"> - <courseware-tab :name="$gettext('Diese Courseware')" :selected="true"> + <courseware-tab :name="$gettext('Diese Courseware')" :selected="true" :index="0"> <courseware-manager-element type="current" :currentElement="currentElement" @@ -10,7 +10,7 @@ @reloadElement="reloadElements" /> </courseware-tab> - <courseware-tab :name="$gettext('Export')"> + <courseware-tab :name="$gettext('Export')" :index="1"> <button class="button" @click.prevent="doExportCourseware" @@ -31,7 +31,7 @@ </courseware-tabs> <courseware-tabs class="cw-course-manager-tabs"> - <courseware-tab :name="$gettext('FAQ')"> + <courseware-tab :name="$gettext('FAQ')" :index="0"> <courseware-collapsible-box :open="true" :title="$gettext('Wie finde ich die gewünschte Stelle?')"> <p><translate> Wählen Sie auf der linken Seite "Diese Courseware" aus. @@ -77,7 +77,7 @@ </translate></p> </courseware-collapsible-box> </courseware-tab> - <courseware-tab name="Verschieben" :selected="true"> + <courseware-tab name="Verschieben" :selected="true" :index="1"> <courseware-manager-element type="self" :currentElement="selfElement" @@ -88,11 +88,11 @@ /> </courseware-tab> - <courseware-tab :name="$gettext('Kopieren')"> + <courseware-tab :name="$gettext('Kopieren')" :index="2"> <courseware-manager-copy-selector @loadSelf="reloadElements" @reloadElement="reloadElements" /> </courseware-tab> - <courseware-tab :name="$gettext('Importieren')"> + <courseware-tab :name="$gettext('Importieren')" :index="3"> <courseware-companion-box v-show="!importRunning && importDone" :msgCompanion="$gettext('Import erfolgreich!')" mood="special"/> <courseware-companion-box v-show="importRunning" :msgCompanion="$gettext('Import läuft. Bitte verlassen Sie die Seite nicht bis der Import abgeschlossen wurde.')" mood="pointing"/> <button @@ -138,7 +138,7 @@ <input ref="importFile" type="file" accept=".zip" @change="setImport" style="visibility: hidden" /> </courseware-tab> - <courseware-tab v-if="context.type === 'courses'" :name="$gettext('Aufgabe verteilen')"> + <courseware-tab v-if="context.type === 'courses'" :name="$gettext('Aufgabe verteilen')" :index="4"> <courseware-manager-task-distributor /> </courseware-tab> </courseware-tabs> diff --git a/resources/vue/components/courseware/CoursewareDashboardProgress.vue b/resources/vue/components/courseware/CoursewareDashboardProgress.vue index 0c32d825ea585f273a6215df0bde89a5067da98d..36e4a5106181f327cb64f44878220a8d78b03ba9 100755 --- a/resources/vue/components/courseware/CoursewareDashboardProgress.vue +++ b/resources/vue/components/courseware/CoursewareDashboardProgress.vue @@ -1,13 +1,27 @@ <template> <div class="cw-dashboard-progress"> - <div class="cw-dashboard-progress-breadcrumb"> - <span v-if="parent" @click="visitRoot"><studip-icon shape="home" /></span> - <span v-if="parent" @click="selectChapter(parent.id)"> / {{ parent.name }}</span> - </div> - <div class="cw-dashboard-progress-chapter" v-if="selected"> - <h1> - <a :href="chapterUrl">{{ selected.name }}</a> - </h1> + <nav aria-label="Breadcrumb" class="cw-dashboard-progress-breadcrumb"> + <a + v-if="parent" + href="#" + :title="$gettext('Hauptseite')" + @click="visitRoot" + > + <studip-icon shape="home" /> + </a> + <a + v-if="parent" + href="#" + :title="parent.name" + @click="selectChapter(parent.id)" + > + / {{ parent.name }} + </a> + </nav> + <div v-if="selected" class="cw-dashboard-progress-chapter"> + <a :href="chapterUrl" :title="$gettextInterpolate('%{ pageTitle } öffnen', {pageTitle: selected.name})"> + <h1>{{ selected.name }}</h1> + </a> <courseware-progress-circle :title="$gettext('diese Seite inkl. darunter liegende Seiten')" :value="parseInt(selected.progress.cumulative)" diff --git a/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue b/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue index adc26b7a87225bc5edb51448638405262b3dfc53..e5ec0d23567f1b5b952f62bcb338a018e6990752 100755 --- a/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue +++ b/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue @@ -1,12 +1,17 @@ <template> - <div class="cw-dashboard-progress-item" @click="$emit('selectChapter', chapterId)"> + <a + href="#" + class="cw-dashboard-progress-item" + :title="name" + @click="$emit('selectChapter', chapterId)" + > <div class="cw-dashboard-progress-item-value"> <courseware-progress-circle :value="parseInt(value)" /> </div> <div class="cw-dashboard-progress-item-description"> {{ name }} </div> - </div> + </a> </template> <script> diff --git a/resources/vue/components/courseware/CoursewareDashboardStudents.vue b/resources/vue/components/courseware/CoursewareDashboardStudents.vue index 4e49327716b43199842f536d681c2488da9736cb..6498b432f278a6fa410439f3cc04a7461389691b 100755 --- a/resources/vue/components/courseware/CoursewareDashboardStudents.vue +++ b/resources/vue/components/courseware/CoursewareDashboardStudents.vue @@ -124,9 +124,9 @@ v-if="showRenewalDialog" :title="text.renewalDialog.title" :confirmText="text.renewalDialog.confirm" - :confirmClass="'accept'" + confirmClass="accept" :closeText="text.renewalDialog.close" - :closeClass="'cancel'" + closeClass="cancel" height="350" @close=" showRenewalDialog = false; @@ -158,9 +158,9 @@ v-if="showEditFeedbackDialog" :title="text.editFeedbackDialog.title" :confirmText="text.editFeedbackDialog.confirm" - :confirmClass="'accept'" + confirmClass="accept" :closeText="text.editFeedbackDialog.close" - :closeClass="'cancel'" + closeClass="cancel" height="420" @close=" showEditFeedbackDialog = false; @@ -188,9 +188,9 @@ v-if="showAddFeedbackDialog" :title="text.addFeedbackDialog.title" :confirmText="text.addFeedbackDialog.confirm" - :confirmClass="'accept'" + confirmClass="accept" :closeText="text.addFeedbackDialog.close" - :closeClass="'cancel'" + closeClass="cancel" @close=" showAddFeedbackDialog = false; currentDialogFeedback = {}; diff --git a/resources/vue/components/courseware/CoursewareDefaultContainer.vue b/resources/vue/components/courseware/CoursewareDefaultContainer.vue index edc46207e01d67b8fd7b43a4edccc3922dfd6166..b1c370c5ccadb42f8210fa2cc142b2bb36cd60ea 100755 --- a/resources/vue/components/courseware/CoursewareDefaultContainer.vue +++ b/resources/vue/components/courseware/CoursewareDefaultContainer.vue @@ -22,9 +22,9 @@ v-if="showEditDialog" :title="textEditTitle" :confirmText="textEditConfirm" - :confirmClass="'accept'" + confirmClass="accept" :closeText="textEditClose" - :closeClass="'cancel'" + closeClass="cancel" @close="closeEdit" @confirm="storeContainer" height="400" diff --git a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue index cdee93a1f033d1d0d97e404730a268d9f9df3d50..2256795d4ca461bb89bdeb1e0602f4f99d32816e 100755 --- a/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue +++ b/resources/vue/components/courseware/CoursewareDialogCardsBlock.vue @@ -58,11 +58,12 @@ <button class="button add" @click="addCard"><translate>Karte hinzufügen</translate></button> <courseware-tabs v-if="currentCards.length > 0" - @selectTab="activateCard(parseInt($event.replace($gettext('Karte') + ' ', '')) - 1)" + @selectTab="activateCard(parseInt($event.name.replace($gettext('Karte') + ' ', '')) - 1)" > <courseware-tab v-for="(card, index) in currentCards" :key="index" + :index="index" :name="$gettext('Karte') + ' ' + (index + 1).toString()" :selected="index === 0" canBeEmpty diff --git a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue index 8680a555e2bc5a62e03a075951a9b4d6e64ff068..b64413d26c8b7f5db556ab0874622021bd28dd38 100755 --- a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue +++ b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue @@ -38,6 +38,7 @@ export default { this.$store.dispatch('coursewareViewMode', 'edit'); this.$store.dispatch('coursewareConsumeMode', false); this.$store.dispatch('coursewareContainerAdder', true); + this.$store.dispatch('coursewareSelectedToolbarItem', 'blockadder'); this.$store.dispatch('coursewareShowToolbar', true); }, switchToEditView() { diff --git a/resources/vue/components/courseware/CoursewareImageMapBlock.vue b/resources/vue/components/courseware/CoursewareImageMapBlock.vue index 9596e8854a022a93fe5df5737a5e27752750e690..9ba60d662c1137e45b00033f15c9ebba8590b5f6 100755 --- a/resources/vue/components/courseware/CoursewareImageMapBlock.vue +++ b/resources/vue/components/courseware/CoursewareImageMapBlock.vue @@ -54,6 +54,7 @@ <courseware-tab v-for="(shape, index) in currentShapes" :key="index" + :index="index" :name="shape.title" :icon="shape.title === '' ? 'link-extern' : ''" :selected="index === 0" diff --git a/resources/vue/components/courseware/CoursewareListContainer.vue b/resources/vue/components/courseware/CoursewareListContainer.vue index 8e8506aedc12dc058947e5aaa5cff31d4eecf357..69fc69f45845e5980632d587d428c74bfadfb941 100755 --- a/resources/vue/components/courseware/CoursewareListContainer.vue +++ b/resources/vue/components/courseware/CoursewareListContainer.vue @@ -1,7 +1,7 @@ <template> <courseware-default-container :container="container" - :containerClass="'cw-container-list'" + containerClass="cw-container-list" :canEdit="canEdit" :isTeacher="isTeacher" @storeContainer="storeContainer" diff --git a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue b/resources/vue/components/courseware/CoursewareManagerCopySelector.vue index 29d51dcab2cd5512f53a5f8fab20a06c99b5859d..01c2c36b7d73f98d39debdf8adf84cabfa1f3424 100755 --- a/resources/vue/components/courseware/CoursewareManagerCopySelector.vue +++ b/resources/vue/components/courseware/CoursewareManagerCopySelector.vue @@ -13,18 +13,18 @@ <li v-for="semester in semesterMap" :key="semester.id"> <h3>{{semester.attributes.title}}</h3> <ul> - <li - v-for="course in coursesBySemester(semester)" - :key="course.id" - class="cw-manager-copy-selector-course" - @click="loadRemoteCourseware(course.id)" - > - <studip-icon :shape="getCourseIcon(course)" /> - {{course.attributes.title}} + <li v-for="course in coursesBySemester(semester)" :key="course.id"> + <a + href="#" + class="cw-manager-copy-selector-course" + @click="loadRemoteCourseware(course.id)" + > + <studip-icon :shape="getCourseIcon(course)" /> + {{course.attributes.title}} + </a> </li> </ul> </li> - </ul> <courseware-companion-box v-if="!hasRemoteCid && semesterMap.length === 0" diff --git a/resources/vue/components/courseware/CoursewareManagerElement.vue b/resources/vue/components/courseware/CoursewareManagerElement.vue index f2c3254c0a70317bb38f1896205924cf2210a3b0..8167a287c1919130c7d544eeaa96b4a62b4494a3 100755 --- a/resources/vue/components/courseware/CoursewareManagerElement.vue +++ b/resources/vue/components/courseware/CoursewareManagerElement.vue @@ -4,20 +4,27 @@ <courseware-companion-box v-if="insertingInProgress" :msgCompanion="text.inProgress" mood="pointing" /> <courseware-companion-box v-if="copyingFailed && !insertingInProgress" :msgCompanion="copyProcessFailedMessage" mood="sad" /> <div class="cw-manager-element-title"> - <div class="cw-manager-element-breadcrumb"> - <span + <nav aria-label="Breadcrumb" class="cw-manager-element-breadcrumb"> + <a v-for="element in breadcrumb" :key="element.id" + :title="element.attributes.title" + href="#" class="cw-manager-element-breadcrumb-item" @click="selectChapter(element.id)" > {{ element.attributes.title }} - </span> - </div> + </a> + </nav> <header> - <span v-if="elementInserterActive && moveSelfPossible && canEdit" @click="insertElement({element: currentElement, source: type})"> - <studip-icon shape="arr_2left" size="24" role="sort" /> - </span> + <a + v-if="elementInserterActive && moveSelfPossible && canEdit" + href="#" + :title="$gettextInterpolate('%{ elementTitle } verschieben', {elementTitle: elementTitle})" + @click="insertElement({element: currentElement, source: type})" + > + <studip-icon shape="arr_2left" size="24" role="clickable" /> + </a> {{ elementTitle }} </header> </div> diff --git a/resources/vue/components/courseware/CoursewareManagerElementItem.vue b/resources/vue/components/courseware/CoursewareManagerElementItem.vue index 0162ca23f5732563b219563b6f86a8fe72711748..49ec34af44e1e5c6139b2d34adb7c3954cec711e 100755 --- a/resources/vue/components/courseware/CoursewareManagerElementItem.vue +++ b/resources/vue/components/courseware/CoursewareManagerElementItem.vue @@ -1,16 +1,27 @@ <template> - <div - class="cw-manager-element-item" - :class="{ 'cw-manager-element-item-sorting': sortChapters }" - @click="clickItem" - > - <span v-if="inserter" @click="clickItem"> - <studip-icon shape="arr_2left" size="16" role="sort" /> - </span> - {{ element.attributes.title }} - <div v-if="sortChapters" class="cw-manager-element-item-buttons"> - <studip-icon :class="{'cw-manager-icon-disabled' : !canMoveUp}" shape="arr_2up" size="16" role="sort" @click="moveUp" /> - <studip-icon :class="{'cw-manager-icon-disabled' : !canMoveDown}" shape="arr_2down" size="16" role="sort" @click="moveDown" /> + <div class="cw-manager-element-item-wrapper"> + <a + v-if="!sortChapters" + href="#" + class="cw-manager-element-item" + :class="[inserter ? 'cw-manager-element-item-inserter' : '']" + :title="inserter ? $gettextInterpolate('%{ elementTitle } verschieben', {elementTitle: element.attributes.title}) : element.attributes.title" + @click="clickItem"> + {{ element.attributes.title }} + </a> + <div + v-else + class="cw-manager-element-item cw-manager-element-item-sorting" + > + {{ element.attributes.title }} + <div v-if="sortChapters" class="cw-manager-element-item-buttons"> + <a v-if="canMoveUp" href="#" @click="moveUp" :title="$gettext('Element nach oben verschieben')"> + <studip-icon :class="{'cw-manager-icon-disabled' : !canMoveUp}" shape="arr_2up" size="16" role="clickable" /> + </a> + <a v-if="canMoveDown" href="#" @click="moveDown" :title="$gettext('Element nach unten verschieben')"> + <studip-icon :class="{'cw-manager-icon-disabled' : !canMoveDown}" shape="arr_2down" size="16" role="clickable" /> + </a> + </div> </div> </div> </template> diff --git a/resources/vue/components/courseware/CoursewareManagerFiling.vue b/resources/vue/components/courseware/CoursewareManagerFiling.vue index d6f1503f1735930a3778cca84a0863df35d8ab2a..fa0a7133837ec813bfad3147f8e55d6e81bab73e 100755 --- a/resources/vue/components/courseware/CoursewareManagerFiling.vue +++ b/resources/vue/components/courseware/CoursewareManagerFiling.vue @@ -1,13 +1,14 @@ <template> - <div + <button class="cw-manager-filing" :class="{ 'cw-manager-filing-active': active, 'cw-manager-filing-disabled': disabled }" + :aria-pressed="active" @click="toggleFiling" > <span v-if="itemType === 'element'"><translate>Seite an dieser Stelle einfügen</translate> </span> <span v-if="itemType === 'container'"><translate>Abschnitt an dieser Stelle einfügen</translate> </span> <span v-if="itemType === 'block'"><translate>Block an dieser Stelle einfügen</translate> </span> - </div> + </button> </template> <script> diff --git a/resources/vue/components/courseware/CoursewareRibbon.vue b/resources/vue/components/courseware/CoursewareRibbon.vue index b8e3cb2c8788ba5ccabbb6e4076c38550a2cc828..da6580705f20294cb048ead546c0e5960208fa5c 100755 --- a/resources/vue/components/courseware/CoursewareRibbon.vue +++ b/resources/vue/components/courseware/CoursewareRibbon.vue @@ -14,13 +14,21 @@ </nav> </div> <div class="cw-ribbon-wrapper-right"> - <button class="cw-ribbon-button cw-ribbon-button-menu" @click="activeToolbar" :title="textRibbon.toolbar"></button> - <button - class="cw-ribbon-button" + <a + href="#" + class="cw-ribbon-button cw-ribbon-button-menu" + :title="textRibbon.toolbar" + @click="activeToolbar" + > + </a> + <a + href="#" + ref="consumeModeSwitch" + class="cw-ribbon-button" :class="[consumeMode ? 'cw-ribbon-button-zoom-out' : 'cw-ribbon-button-zoom']" :title="consumeMode ? textRibbon.fullscreen_off : textRibbon.fullscreen_on" @click="toggleConsumeMode" - ></button> + ></a> <slot name="menu" /> </div> <div v-if="consumeMode" class="cw-ribbon-consume-bottom"></div> @@ -76,6 +84,7 @@ export default { toggleConsumeMode() { if (!this.consumeMode) { this.$store.dispatch('coursewareConsumeMode', true); + this.$store.dispatch('coursewareSelectedToolbarItem', 'contents'); this.$store.dispatch('coursewareViewMode', 'read'); } else { this.$store.dispatch('coursewareConsumeMode', false); @@ -112,6 +121,9 @@ export default { } }, 800); } + }, + consumeMode(newState) { + this.$refs.consumeModeSwitch.focus(); } } }; diff --git a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue index e5b79722582f5fe5a5cdf97dcf12bbe9bdf386a0..d06450acf0b6648a3a91e5418a979857dd560266 100755 --- a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue +++ b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue @@ -1,58 +1,82 @@ <template> - <div - class="cw-ribbon-tools" - :class="{ unfold: toolsActive, 'cw-ribbon-tools-consume': consumeMode }" - > - <div class="cw-ribbon-tool-content"> - <div class="cw-ribbon-tool-content-nav"> - <ul> - <li - tabindex="0" - ref="focusPoint" - :class="{ active: showContents }" - @click="displayTool('contents')" + <focus-trap v-model="trap" :initial-focus="() => initialFocusElement"> + <div + class="cw-ribbon-tools" + :class="{ unfold: toolsActive, 'cw-ribbon-tools-consume': consumeMode }" + @keydown.esc="$emit('deactivate')" + > + <div class="cw-ribbon-tool-content"> + <div class="cw-ribbon-tool-content-nav"> + <courseware-tabs + class="cw-ribbon-tool-content-tablist" + ref="tabs" + @selectTab="selectTool($event.alias)" > - <translate>Inhaltsverzeichnis</translate> - </li> - <li - v-if="!consumeMode && showEditMode && canEdit" - tabindex="0" - :class="{ active: showBlockAdder }" - @click="displayTool('blockadder')" - > - <translate>Elemente hinzufügen</translate> - </li> - <li - v-if="!consumeMode && displaySettings" - tabindex="0" - :class="{ active: showAdmin }" - @click="displayTool('admin')" - > - <translate>Einstellungen</translate> - </li> - </ul> - <button :title="textClose" class="cw-tools-hide-button" @click="$emit('deactivate')"></button> - </div> - <div class="cw-ribbon-tool" ref="ribbonContent" @scroll="handleScroll"> - <courseware-tools-contents v-if="showContents" /> - <courseware-tools-blockadder v-if="showBlockAdder" @scrollTop="scrollTop('blockadder')"/> - <courseware-tools-admin v-if="showAdmin" /> + <courseware-tab + :name="$gettext('Inhaltsverzeichnis')" + :selected="showContents" + alias="contents" + ref="contents" + :index="0" + > + <courseware-tools-contents + id="cw-ribbon-tool-contents" + /> + </courseware-tab> + <courseware-tab + v-if="!consumeMode && showEditMode && canEdit" + :name="$gettext('Elemente hinzufügen')" + :selected="showBlockAdder" + alias="blockadder" + class="cw-ribbon-tool-blockadder-tab" + :index="1" + > + <courseware-tools-blockadder + id="cw-ribbon-tool-blockadder" + /> + </courseware-tab> + <courseware-tab + v-if="!consumeMode && displaySettings" + :name="$gettext('Einstellungen')" + :selected="showAdmin" + alias="admin" + :index="2" + > + <courseware-tools-admin + id="cw-ribbon-tool-admin" + /> + </courseware-tab> + </courseware-tabs> + <a + href="#" + :title="$gettext('schließen')" + class="cw-tools-hide-button" + ref="closeTools" + @click="$emit('deactivate')"> + </a> + </div> </div> </div> - </div> + </focus-trap> </template> <script> +import CoursewareTabs from './CoursewareTabs.vue'; +import CoursewareTab from './CoursewareTab.vue'; import CoursewareToolsAdmin from './CoursewareToolsAdmin.vue'; import CoursewareToolsBlockadder from './CoursewareToolsBlockadder.vue'; import CoursewareToolsContents from './CoursewareToolsContents.vue'; -import { mapGetters } from 'vuex'; +import { FocusTrap } from 'focus-trap-vue'; +import { mapActions, mapGetters } from 'vuex'; export default { name: 'courseware-ribbon-toolbar', components: { + CoursewareTabs, + CoursewareTab, CoursewareToolsAdmin, CoursewareToolsBlockadder, CoursewareToolsContents, + FocusTrap, }, props: { toolsActive: Boolean, @@ -63,12 +87,8 @@ export default { showContents: true, showAdmin: false, showBlockAdder: false, - textClose: this.$gettext('schließen'), - scrollPos: { - contents: 0, - admin: 0, - blockadder: 0 - } + trap: false, + initialFocusElement: null }; }, computed: { @@ -81,6 +101,7 @@ export default { context: 'context', userById: 'users/byId', userId: 'userId', + selectedToolbarItem: 'selectedToolbarItem', }), showEditMode() { return this.viewMode === 'edit'; @@ -94,15 +115,19 @@ export default { }, }, methods: { - displayTool(tool) { + ...mapActions({ + setToolbarItem: 'coursewareSelectedToolbarItem' + }), + selectTool(alias) { this.showContents = false; this.showAdmin = false; this.showBlockAdder = false; - switch (tool) { + switch (alias) { case 'contents': this.showContents = true; this.disableContainerAdder(); + this.scrollToCurrent(); break; case 'admin': this.showAdmin = true; @@ -112,87 +137,56 @@ export default { this.showBlockAdder = true; break; } - this.updateScrollPos(); + + if (this.selectedToolbarItem !== alias) { + this.setToolbarItem(alias); + } }, disableContainerAdder() { if (this.containerAdder !== false) { this.$store.dispatch('coursewareContainerAdder', false); } }, - scrollTop(tool) { - switch (tool) { - case 'contents': - this.$set(this.scrollPos, 'contents', 0); - break; - case 'admin': - this.$set(this.scrollPos, 'admin', 0); - break; - case 'blockadder': - this.$set(this.scrollPos, 'blockadder', 0); - break; - } - this.updateScrollPos(); - }, - handleScroll: function() { - if (this.timeout) { - clearTimeout(this.timeout); - } - - this.timeout = setTimeout(() => { - var currentScrollPos = this.$refs.ribbonContent.scrollTop; - if (this.showContents) { - this.$set(this.scrollPos, 'contents', currentScrollPos); - } - if (this.showAdmin) { - this.$set(this.scrollPos, 'admin', currentScrollPos); - } - if (this.showBlockAdder) { - this.$set(this.scrollPos, 'blockadder', currentScrollPos); - } - }, 100); + scrollToCurrent() { + setTimeout(() => { + let contents = this.$refs.contents.$el; + let current = contents.querySelector('.cw-tree-item-link-current'); + contents.scroll({ top: current.offsetTop, behavior: 'smooth' }); + }, 360); }, - updateScrollPos() { - var scrollPos = 0; - if (this.showContents) { - scrollPos = this.scrollPos.contents; - } - if (this.showAdmin) { - scrollPos = this.scrollPos.admin; - } - if (this.showBlockAdder) { - scrollPos = this.scrollPos.blockadder; - } - this.$nextTick(function() { - $(this.$refs.ribbonContent).stop().animate({ - scrollTop: scrollPos - }, 100); - }); - } }, mounted () { - this.updateScrollPos(); + this.selectTool(this.selectedToolbarItem); }, watch: { adderStorage(newValue) { if (Object.keys(newValue).length !== 0) { - this.displayTool('blockadder'); + this.selectTool('blockadder'); } }, consumeMode(newValue) { if (newValue) { - this.displayTool('contents'); + this.selectTool('contents'); } }, containerAdder(newValue) { if (newValue === true) { - this.displayTool('blockadder'); + this.selectTool('blockadder'); } }, showEditMode(newValue) { if (!newValue) { - this.displayTool('contents'); + this.selectTool('contents'); + } + }, + toolsActive(newValue) { + if (newValue) { + setTimeout(() => { + this.initialFocusElement = this.$refs.tabs.getTabButtonByAlias(this.selectedToolbarItem); + this.trap = true; + }, 300); } - } + }, }, }; </script> diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue index bb4a9411a8bbe7e7c7a990fffe7ed6b9c43a5d08..60f3aac77d02ba1ef6a00571bd900ff4ff8f0151 100755 --- a/resources/vue/components/courseware/CoursewareStructuralElement.vue +++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue @@ -1,494 +1,481 @@ <template> - <div> - <div - :class="{ 'cw-structural-element-consumemode': consumeMode }" - class="cw-structural-element" - v-if="validContext" - > - <div class="cw-structural-element-content" v-if="structuralElement"> - <courseware-ribbon :canEdit="canEdit && canAddElements"> - <template #buttons> - <router-link v-if="prevElement" :to="'/structural_element/' + prevElement.id"> - <button class="cw-ribbon-button cw-ribbon-button-prev" :title="textRibbon.perv" /> - </router-link> - <button v-else class="cw-ribbon-button cw-ribbon-button-prev-disabled" /> - <router-link v-if="nextElement" :to="'/structural_element/' + nextElement.id"> - <button class="cw-ribbon-button cw-ribbon-button-next" :title="textRibbon.next" /> - </router-link> - <button v-else class="cw-ribbon-button cw-ribbon-button-next-disabled" /> - </template> - <template #breadcrumbList> - <li - v-for="ancestor in ancestors" - :key="ancestor.id" - :title="ancestor.attributes.title" - class="cw-ribbon-breadcrumb-item" - > - <span> - <router-link :to="'/structural_element/' + ancestor.id"> - {{ ancestor.attributes.title || "–" }} - </router-link> - </span> - </li> - <li - class="cw-ribbon-breadcrumb-item cw-ribbon-breadcrumb-item-current" - :title="structuralElement.attributes.title" - > - <span>{{ structuralElement.attributes.title || "–" }}</span> - </li> - </template> - <template #breadcrumbFallback> - <li - class="cw-ribbon-breadcrumb-item cw-ribbon-breadcrumb-item-current" - :title="structuralElement.attributes.title" - > - <span>{{ structuralElement.attributes.title }}</span> - </li> - </template> - <template #menu> - <studip-action-menu - v-if="!consumeMode" - :items="menuItems" - class="cw-ribbon-action-menu" - @editCurrentElement="menuAction('editCurrentElement')" - @addElement="menuAction('addElement')" - @deleteCurrentElement="menuAction('deleteCurrentElement')" - @showInfo="menuAction('showInfo')" - @showExportOptions="menuAction('showExportOptions')" - @oerCurrentElement="menuAction('oerCurrentElement')" - @setBookmark="menuAction('setBookmark')" - @sortContainers="menuAction('sortContainers')" - @pdfExport="menuAction('pdfExport')" + <focus-trap v-model="consumModeTrap"> + <div> + <div + :class="{ 'cw-structural-element-consumemode': consumeMode }" + class="cw-structural-element" + v-if="validContext" + > + <div class="cw-structural-element-content" v-if="structuralElement"> + <courseware-ribbon :canEdit="canEdit && canAddElements"> + <template #buttons> + <router-link v-if="prevElement" :to="'/structural_element/' + prevElement.id"> + <div class="cw-ribbon-button cw-ribbon-button-prev" :title="textRibbon.perv" /> + </router-link> + <div v-else class="cw-ribbon-button cw-ribbon-button-prev-disabled" :title="$gettext('keine vorherige Seite')"/> + <router-link v-if="nextElement" :to="'/structural_element/' + nextElement.id"> + <div class="cw-ribbon-button cw-ribbon-button-next" :title="textRibbon.next" /> + </router-link> + <div v-else class="cw-ribbon-button cw-ribbon-button-next-disabled" :title="$gettext('keine nächste Seite')"/> + </template> + <template #breadcrumbList> + <li + v-for="ancestor in ancestors" + :key="ancestor.id" + :title="ancestor.attributes.title" + class="cw-ribbon-breadcrumb-item" + > + <span> + <router-link :to="'/structural_element/' + ancestor.id"> + {{ ancestor.attributes.title || "–" }} + </router-link> + </span> + </li> + <li + class="cw-ribbon-breadcrumb-item cw-ribbon-breadcrumb-item-current" + :title="structuralElement.attributes.title" + > + <span>{{ structuralElement.attributes.title || "–" }}</span> + </li> + </template> + <template #breadcrumbFallback> + <li + class="cw-ribbon-breadcrumb-item cw-ribbon-breadcrumb-item-current" + :title="structuralElement.attributes.title" + > + <span>{{ structuralElement.attributes.title }}</span> + </li> + </template> + <template #menu> + <studip-action-menu + v-if="!consumeMode" + :items="menuItems" + class="cw-ribbon-action-menu" + @editCurrentElement="menuAction('editCurrentElement')" + @addElement="menuAction('addElement')" + @deleteCurrentElement="menuAction('deleteCurrentElement')" + @showInfo="menuAction('showInfo')" + @showExportOptions="menuAction('showExportOptions')" + @oerCurrentElement="menuAction('oerCurrentElement')" + @setBookmark="menuAction('setBookmark')" + @sortContainers="menuAction('sortContainers')" + @pdfExport="menuAction('pdfExport')" + /> + </template> + </courseware-ribbon> + + <div + v-if="canVisit && !sortMode" + class="cw-container-wrapper" + :class="{ + 'cw-container-wrapper-consume': consumeMode, + 'cw-container-wrapper-discuss': discussView, + }" + > + <div v-if="structuralElementLoaded" class="cw-companion-box-wrapper"> + <courseware-empty-element-box + v-if="showEmptyElementBox" + :canEdit="canEdit" + :noContainers="noContainers" + /> + <courseware-wellcome-screen v-if="noContainers && isRoot && canEdit" /> + </div> + <courseware-structural-element-discussion + v-if="!noContainers && discussView" + :structuralElement="structuralElement" + :canEdit="canEdit" /> - </template> - </courseware-ribbon> - - <div - v-if="canVisit && !sortMode" - class="cw-container-wrapper" - :class="{ - 'cw-container-wrapper-consume': consumeMode, - 'cw-container-wrapper-discuss': discussView, - }" - > - <div v-if="structuralElementLoaded" class="cw-companion-box-wrapper"> - <courseware-empty-element-box - v-if="showEmptyElementBox" + <component + v-for="container in containers" + :key="container.id" + :is="containerComponent(container)" + :container="container" :canEdit="canEdit" - :noContainers="noContainers" + :canAddElements="canAddElements" + :isTeacher="userIsTeacher" + class="cw-container-item" /> - <courseware-wellcome-screen v-if="noContainers && isRoot && canEdit" /> </div> - <courseware-structural-element-discussion - v-if="!noContainers && discussView" - :structuralElement="structuralElement" - :canEdit="canEdit" - /> - <component - v-for="container in containers" - :key="container.id" - :is="containerComponent(container)" - :container="container" - :canEdit="canEdit" - :canAddElements="canAddElements" - :isTeacher="userIsTeacher" - class="cw-container-item" - /> - </div> - <div v-if="canVisit && canEdit && sortMode" class="cw-container-wrapper-sort-mode"> - <draggable - class="cw-structural-element-list-sort-mode" - tag="ul" - v-model="containerList" - v-bind="dragOptions" - handle=".cw-sortable-handle" - @start="isDragging = true" - @end="isDragging = false" - > - <transition-group type="transition" name="flip-containers"> - <li - v-for="container in containerList" - :key="container.id" - class="cw-container-item-sortable" - > - <span class="cw-sortable-handle"></span> - <span>{{ container.attributes.title }} ({{ container.attributes.width }})</span> - </li> - </transition-group> - </draggable> - <div class="cw-container-sort-buttons"> - <button class="button accept" @click="storeSort"> - <translate>Sortierung speichern</translate> - </button> - <button class="button cancel" @click="resetSort"> - <translate>Sortieren abbrechen</translate> - </button> + <div v-if="canVisit && canEdit && sortMode" class="cw-container-wrapper-sort-mode"> + <draggable + class="cw-structural-element-list-sort-mode" + tag="ul" + v-model="containerList" + v-bind="dragOptions" + handle=".cw-sortable-handle" + @start="isDragging = true" + @end="isDragging = false" + > + <transition-group type="transition" name="flip-containers"> + <li + v-for="container in containerList" + :key="container.id" + class="cw-container-item-sortable" + > + <span class="cw-sortable-handle"></span> + <span>{{ container.attributes.title }} ({{ container.attributes.width }})</span> + </li> + </transition-group> + </draggable> + <div class="cw-container-sort-buttons"> + <button class="button accept" @click="storeSort"> + <translate>Sortierung speichern</translate> + </button> + <button class="button cancel" @click="resetSort"> + <translate>Sortieren abbrechen</translate> + </button> + </div> </div> - </div> - <div - v-if="!canVisit" - class="cw-container-wrapper" - :class="{ 'cw-container-wrapper-consume': consumeMode }" - > - <div v-if="structuralElementLoaded" class="cw-companion-box-wrapper"> - <courseware-companion-box - mood="sad" - :msgCompanion="$gettext('Diese Seite steht Ihnen leider nicht zur Verfügung.')" - /> + <div + v-if="!canVisit" + class="cw-container-wrapper" + :class="{ 'cw-container-wrapper-consume': consumeMode }" + > + <div v-if="structuralElementLoaded" class="cw-companion-box-wrapper"> + <courseware-companion-box + mood="sad" + :msgCompanion="$gettext('Diese Seite steht Ihnen leider nicht zur Verfügung.')" + /> + </div> </div> </div> - </div> - <courseware-companion-overlay /> - - <studip-dialog - v-if="showEditDialog" - :title="textEdit.title" - :confirmText="textEdit.confirm" - :confirmClass="'accept'" - :closeText="textEdit.close" - :closeClass="'cancel'" - height="500" - width="500" - class="studip-dialog-with-tab" - @close="closeEditDialog" - @confirm="storeCurrentElement" - > - <template v-slot:dialogContent> - <courseware-tabs class="cw-tab-in-dialog"> - <courseware-tab :name="textEdit.basic" :selected="true"> - <form class="default" @submit.prevent=""> - <label> - <translate>Titel</translate> - <input type="text" v-model="currentElement.attributes.title" /> - </label> - <label> - <translate>Beschreibung</translate> - <textarea - v-model="currentElement.attributes.payload.description" - class="cw-structural-element-description" + <courseware-companion-overlay /> + + <studip-dialog + v-if="showEditDialog" + :title="textEdit.title" + :confirmText="textEdit.confirm" + confirmClass="accept" + :closeText="textEdit.close" + closeClass="cancel" + height="500" + width="500" + class="studip-dialog-with-tab" + @close="closeEditDialog" + @confirm="storeCurrentElement" + > + <template v-slot:dialogContent> + <courseware-tabs class="cw-tab-in-dialog"> + <courseware-tab :name="textEdit.basic" :selected="true" :index="0"> + <form class="default" @submit.prevent=""> + <label> + <translate>Titel</translate> + <input type="text" v-model="currentElement.attributes.title" /> + </label> + <label> + <translate>Beschreibung</translate> + <textarea + v-model="currentElement.attributes.payload.description" + class="cw-structural-element-description" + /> + </label> + </form> + </courseware-tab> + <courseware-tab :name="textEdit.meta" :index="1"> + <form class="default" @submit.prevent=""> + <label> + <translate>Farbe</translate> + <v-select + v-model="currentElement.attributes.payload.color" + :options="colors" + :reduce="(color) => color.class" + label="class" + class="cw-vs-select" + > + <template #open-indicator="selectAttributes"> + <span v-bind="selectAttributes" + ><studip-icon shape="arr_1down" size="10" + /></span> + </template> + <template #no-options="{ search, searching, loading }"> + <translate>Es steht keine Auswahl zur Verfügung</translate>. + </template> + <template #selected-option="{ name, hex }"> + <span class="vs__option-color" :style="{ 'background-color': hex }"></span + ><span>{{ name }}</span> + </template> + <template #option="{ name, hex }"> + <span class="vs__option-color" :style="{ 'background-color': hex }"></span + ><span>{{ name }}</span> + </template> + </v-select> + </label> + <label> + <translate>Zweck</translate> + <select v-model="currentElement.attributes.purpose"> + <option value="content"><translate>Inhalt</translate></option> + <option value="template"><translate>Vorlage</translate></option> + <option value="oer"><translate>OER-Material</translate></option> + <option value="portfolio"><translate>ePortfolio</translate></option> + <option value="draft"><translate>Entwurf</translate></option> + <option value="other"><translate>Sonstiges</translate></option> + </select> + </label> + <label> + <translate>Lizenztyp</translate> + <select v-model="currentElement.attributes.payload.license_type"> + <option v-for="license in licenses" :key="license.id" :value="license.id"> + {{ license.name }} + </option> + </select> + </label> + <label> + <translate>Geschätzter zeitlicher Aufwand</translate> + <input type="text" v-model="currentElement.attributes.payload.required_time" /> + </label> + <label> + <translate>Niveau</translate><br /> + <translate>von</translate> + <select v-model="currentElement.attributes.payload.difficulty_start"> + <option + v-for="difficulty_start in 12" + :key="difficulty_start" + :value="difficulty_start" + > + {{ difficulty_start }} + </option> + </select> + <translate>bis</translate> + <select v-model="currentElement.attributes.payload.difficulty_end"> + <option + v-for="difficulty_end in 12" + :key="difficulty_end" + :value="difficulty_end" + > + {{ difficulty_end }} + </option> + </select> + </label> + </form> + </courseware-tab> + <courseware-tab :name="textEdit.image" :index="2"> + <form class="default" @submit.prevent=""> + <img + v-if="image" + :src="image" + class="cw-structural-element-image-preview" + :alt="$gettext('Vorschaubild')" /> - </label> - </form> - </courseware-tab> - <courseware-tab :name="textEdit.meta"> - <form class="default" @submit.prevent=""> + <label v-if="image"> + <button class="button" @click="deleteImage" v-translate>Bild löschen</button> + </label> + <div v-if="uploadFileError" class="messagebox messagebox_error"> + {{ uploadFileError }} + </div> + <label v-if="!image"> + <translate>Bild hochladen</translate> + <input ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" /> + </label> + </form> + </courseware-tab> + <courseware-tab :name="textEdit.approval" :index="3"> + <courseware-structural-element-permissions + v-if="inCourse" + :element="currentElement" + @updateReadApproval="updateReadApproval" + @updateWriteApproval="updateWriteApproval" + /> + </courseware-tab> + <courseware-tab v-if="inCourse" :name="textEdit.visible" :index="4"> + <form class="default" @submit.prevent=""> + <label> + <translate>Sichtbar ab</translate> + <input type="date" v-model="currentElement.attributes['release-date']" /> + </label> + <label> + <translate>Unsichtbar ab</translate> + <input type="date" v-model="currentElement.attributes['withdraw-date']" /> + </label> + </form> + </courseware-tab> + </courseware-tabs> + </template> + </studip-dialog> + + <studip-dialog + v-if="showAddDialog" + :title="$gettext('Seite hinzufügen')" + :confirmText="$gettext('Erstellen')" + confirmClass="accept" + :closeText="$gettext('Schließen')" + closeClass="cancel" + class="cw-structural-element-dialog" + @close="closeAddDialog" + @confirm="createElement" + > + <template v-slot:dialogContent> + <form class="default" @submit.prevent=""> + <label> + <translate>Position der neuen Seite</translate> + <select v-model="newChapterParent"> + <option v-if="!isRoot" value="sibling"> + <translate>Neben der aktuellen Seite</translate> + </option> + <option value="descendant"><translate>Unterhalb der aktuellen Seite</translate></option> + </select> + </label> + <label> + <translate>Name der neuen Seite</translate><br /> + <input v-model="newChapterName" type="text" /> + </label> + </form> + </template> + </studip-dialog> + + <studip-dialog + v-if="showInfoDialog" + :title="textInfo.title" + :closeText="textInfo.close" + closeClass="cancel" + @close="showElementInfoDialog(false)" + > + <template v-slot:dialogContent> + <table class="cw-structural-element-info"> + <tr> + <td><translate>Titel</translate>:</td> + <td>{{ structuralElement.attributes.title }}</td> + </tr> + <tr> + <td><translate>Beschreibung</translate>:</td> + <td>{{ structuralElement.attributes.payload.description }}</td> + </tr> + <tr> + <td><translate>Seite wurde erstellt von</translate>:</td> + <td>{{ owner }}</td> + </tr> + <tr> + <td><translate>Seite wurde erstellt am</translate>:</td> + <td><iso-date :date="structuralElement.attributes.mkdate" /></td> + </tr> + <tr> + <td><translate>Zuletzt bearbeitet von</translate>:</td> + <td>{{ editor }}</td> + </tr> + <tr> + <td><translate>Zuletzt bearbeitet am</translate>:</td> + <td><iso-date :date="structuralElement.attributes.chdate" /></td> + </tr> + </table> + </template> + </studip-dialog> + + <studip-dialog + v-if="showExportDialog" + :title="textExport.title" + :confirmText="textExport.confirm" + confirmClass="accept" + :closeText="textExport.close" + closeClass="cancel" + height="350" + @close="showElementExportDialog(false)" + @confirm="exportCurrentElement" + > + <template v-slot:dialogContent> + <div v-show="!exportRunning"> + <translate> Hiermit exportieren Sie die Seite "%{ currentElement.attributes.title }" als ZIP-Datei.</translate> + <div class="cw-element-export"> <label> - <translate>Farbe</translate> - <studip-select - v-model="currentElement.attributes.payload.color" - :options="colors" - :reduce="(color) => color.class" - label="class" - > - <template #open-indicator="selectAttributes"> - <span v-bind="selectAttributes" - ><studip-icon shape="arr_1down" size="10" - /></span> - </template> - <template #no-options="{ search, searching, loading }"> - <translate>Es steht keine Auswahl zur Verfügung.</translate> - </template> - <template #selected-option="{ name, hex }"> - <span class="vs__option-color" :style="{ 'background-color': hex }"></span - ><span>{{ name }}</span> - </template> - <template #option="{ name, hex }"> - <span class="vs__option-color" :style="{ 'background-color': hex }"></span - ><span>{{ name }}</span> - </template> - </studip-select> + <input type="checkbox" v-model="exportChildren" /> + <translate>Unterseiten exportieren</translate> </label> + </div> + </div> + + <courseware-companion-box + v-show="exportRunning" + :msgCompanion="$gettext('Export läuft, bitte haben sie einen Moment Geduld...')" + mood="pointing" + /> + <div v-show="exportRunning" class="cw-import-zip"> + <header>{{ exportState }}:</header> + <div class="progress-bar-wrapper"> + <div + class="progress-bar" + role="progressbar" + :style="{ width: exportProgress + '%' }" + :aria-valuenow="exportProgress" + aria-valuemin="0" + aria-valuemax="100" + > + {{ exportProgress }}% + </div> + </div> + </div> + </template> + </studip-dialog> + + <studip-dialog + v-if="showOerDialog" + height="600" + width="600" + :title="textOer.title" + :confirmText="textOer.confirm" + confirmClass="accept" + :closeText="textOer.close" + closeClass="cancel" + @close="showElementOerDialog(false)" + @confirm="publishCurrentElement" + > + <template v-slot:dialogContent> + <form class="default" @submit.prevent=""> + <fieldset> + <legend><translate>Grunddaten</translate></legend> <label> - <translate>Zweck</translate> - <select v-model="currentElement.attributes.purpose"> - <option value="content"><translate>Inhalt</translate></option> - <option value="template"><translate>Aufgabenvorlage</translate></option> - <option value="oer"><translate>OER-Material</translate></option> - <option value="portfolio"><translate>ePortfolio</translate></option> - <option value="draft"><translate>Entwurf</translate></option> - <option value="other"><translate>Sonstiges</translate></option> - </select> + <p><translate>Vorschaubild</translate>:</p> + <img + v-if="currentElement.relationships.image.data" + :src="currentElement.relationships.image.meta['download-url']" + width="400" + /> </label> <label> - <translate>Lizenztyp</translate> - <select v-model="currentElement.attributes.payload.license_type"> - <option v-for="license in licenses" :key="license.id" :value="license.id"> - {{ license.name }} - </option> - </select> + <p><translate>Beschreibung</translate>:</p> + <p>{{ currentElement.attributes.payload.description }}</p> </label> <label> - <translate>Geschätzter zeitlicher Aufwand</translate> - <input type="text" v-model="currentElement.attributes.payload.required_time" /> + <translate>Niveau</translate>: + <p> + {{ currentElement.attributes.payload.difficulty_start }} - + {{ currentElement.attributes.payload.difficulty_end }} + </p> </label> <label> - <translate>Niveau</translate><br /> - <translate>von</translate> - <select v-model="currentElement.attributes.payload.difficulty_start"> - <option - v-for="difficulty_start in 12" - :key="difficulty_start" - :value="difficulty_start" - > - {{ difficulty_start }} - </option> - </select> - <translate>bis</translate> - <select v-model="currentElement.attributes.payload.difficulty_end"> - <option - v-for="difficulty_end in 12" - :key="difficulty_end" - :value="difficulty_end" - > - {{ difficulty_end }} - </option> - </select> - </label> - </form> - </courseware-tab> - <courseware-tab :name="textEdit.image"> - <form class="default" @submit.prevent=""> - <img - v-if="image" - :src="image" - class="cw-structural-element-image-preview" - :alt="$gettext('Vorschaubild')" - /> - <label v-if="image"> - <button class="button" @click="deleteImage" v-translate>Bild löschen</button> + <translate>Lizenztyp</translate>: + <p>{{ currentLicenseName }}</p> </label> - <div v-if="uploadFileError" class="messagebox messagebox_error"> - {{ uploadFileError }} - </div> - <label v-if="!image"> - <translate>Bild hochladen</translate> - <input ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" /> - </label> - </form> - </courseware-tab> - <courseware-tab :name="textEdit.approval"> - <courseware-structural-element-permissions - v-if="inCourse" - :element="currentElement" - @updateReadApproval="updateReadApproval" - @updateWriteApproval="updateWriteApproval" - /> - <!-- <h1> - <translate>Lehrende in Stud.IP</translate> - </h1> - <label> - <input - type="checkbox" - class="default" - value="copy_approval" - v-model="currentElement.attributes['copy-approval']" - /> - <translate>Seite zum kopieren für Lehrende freigeben</translate> - </label> --> - </courseware-tab> - <courseware-tab v-if="inCourse" :name="textEdit.visible"> - <form class="default" @submit.prevent=""> <label> - <translate>Sichtbar ab</translate> - <input type="date" v-model="currentElement.attributes['release-date']" /> + <translate>Sie können diese Daten unter "Seite bearbeiten" verändern.</translate> </label> + </fieldset> + <fieldset> + <legend><translate>Einstellungen</translate></legend> <label> - <translate>Unsichtbar ab</translate> - <input type="date" v-model="currentElement.attributes['withdraw-date']" /> + <translate>Unterseiten veröffentlichen</translate> + <input type="checkbox" v-model="oerChildren" /> </label> - </form> - </courseware-tab> - </courseware-tabs> - </template> - </studip-dialog> - - <studip-dialog - v-if="showAddDialog" - :title="$gettext('Seite hinzufügen')" - :confirmText="'Erstellen'" - :confirmClass="'accept'" - :closeText="$gettext('Schließen')" - :closeClass="'cancel'" - class="cw-structural-element-dialog" - @close="closeAddDialog" - @confirm="createElement" - > - <template v-slot:dialogContent> - <form class="default" @submit.prevent=""> - <label> - <translate>Position der neuen Seite</translate> - <select v-model="newChapterParent"> - <option v-if="!isRoot" value="sibling"> - <translate>Neben der aktuellen Seite</translate> - </option> - <option value="descendant"><translate>Unterhalb der aktuellen Seite</translate></option> - </select> - </label> - <label> - <translate>Name der neuen Seite</translate><br /> - <input v-model="newChapterName" type="text" required /> - <div class="invalid_message" :style="{ display: errorEmptyChapterName ? 'block' : 'none' }"> - <translate>Der Name der neuen Seite darf nicht leer sein.</translate> - </div> - </label> - </form> - </template> - </studip-dialog> - - <studip-dialog - v-if="showInfoDialog" - :title="textInfo.title" - :closeText="textInfo.close" - :closeClass="'cancel'" - @close="showElementInfoDialog(false)" - > - <template v-slot:dialogContent> - <table class="cw-structural-element-info"> - <tr> - <td><translate>Titel</translate>:</td> - <td>{{ structuralElement.attributes.title }}</td> - </tr> - <tr> - <td><translate>Beschreibung</translate>:</td> - <td>{{ structuralElement.attributes.payload.description }}</td> - </tr> - <tr> - <td><translate>Seite wurde erstellt von</translate>:</td> - <td>{{ owner }}</td> - </tr> - <tr> - <td><translate>Seite wurde erstellt am</translate>:</td> - <td><iso-date :date="structuralElement.attributes.mkdate" /></td> - </tr> - <tr> - <td><translate>Zuletzt bearbeitet von</translate>:</td> - <td>{{ editor }}</td> - </tr> - <tr> - <td><translate>Zuletzt bearbeitet am</translate>:</td> - <td><iso-date :date="structuralElement.attributes.chdate" /></td> - </tr> - </table> - </template> - </studip-dialog> - - <studip-dialog - v-if="showExportDialog" - :title="textExport.title" - :confirmText="textExport.confirm" - :confirmClass="'accept'" - :closeText="textExport.close" - :closeClass="'cancel'" - height="350" - @close="showElementExportDialog(false)" - @confirm="exportCurrentElement" - > - <template v-slot:dialogContent> - <div v-show="!exportRunning"> - <translate> Hiermit exportieren Sie die Seite "%{ currentElement.attributes.title }" als ZIP-Datei.</translate> - <div class="cw-element-export"> - <label> - <input type="checkbox" v-model="exportChildren" /> - <translate>Unterseiten exportieren</translate> - </label> - </div> - </div> - - <courseware-companion-box - v-show="exportRunning" - :msgCompanion="$gettext('Export läuft, bitte haben sie einen Moment Geduld...')" - mood="pointing" - /> - <div v-show="exportRunning" class="cw-import-zip"> - <header>{{ exportState }}:</header> - <div class="progress-bar-wrapper"> - <div - class="progress-bar" - role="progressbar" - :style="{ width: exportProgress + '%' }" - :aria-valuenow="exportProgress" - aria-valuemin="0" - aria-valuemax="100" - > - {{ exportProgress }}% - </div> - </div> - </div> - </template> - </studip-dialog> - - <studip-dialog - v-if="showOerDialog" - height="600" - width="600" - :title="textOer.title" - :confirmText="textOer.confirm" - :confirmClass="'accept'" - :closeText="textOer.close" - :closeClass="'cancel'" - @close="showElementOerDialog(false)" - @confirm="publishCurrentElement" - > - <template v-slot:dialogContent> - <form class="default" @submit.prevent=""> - <fieldset> - <legend><translate>Grunddaten</translate></legend> - <label> - <p><translate>Vorschaubild</translate>:</p> - <img - v-if="currentElement.relationships.image.data" - :src="currentElement.relationships.image.meta['download-url']" - width="400" - /> - </label> - <label> - <p><translate>Beschreibung</translate>:</p> - <p>{{ currentElement.attributes.payload.description }}</p> - </label> - <label> - <translate>Niveau</translate>: - <p> - {{ currentElement.attributes.payload.difficulty_start }} - - {{ currentElement.attributes.payload.difficulty_end }} - </p> - </label> - <label> - <translate>Lizenztyp</translate>: - <p>{{ currentLicenseName }}</p> - </label> - <label> - <translate>Sie können diese Daten unter "Seite bearbeiten" verändern.</translate> - </label> - </fieldset> - <fieldset> - <legend><translate>Einstellungen</translate></legend> - <label> - <translate>Unterseiten veröffentlichen</translate> - <input type="checkbox" v-model="oerChildren" /> - </label> - </fieldset> - </form> - </template> - </studip-dialog> - - <studip-dialog - v-if="showDeleteDialog" - :title="textDelete.title" - :question="textDelete.alert" - height="180" - @confirm="deleteCurrentElement" - @close="closeDeleteDialog" - ></studip-dialog> - </div> - <div v-else> - <courseware-companion-box - v-if="currentElement !== ''" - :msgCompanion="textCompanionWrongContext" - mood="sad" - /> + </fieldset> + </form> + </template> + </studip-dialog> + <studip-dialog + v-if="showDeleteDialog" + :title="textDelete.title" + :question="textDelete.alert" + height="180" + @confirm="deleteCurrentElement" + @close="closeDeleteDialog" + ></studip-dialog> + </div> + <div v-else> + <courseware-companion-box + v-if="currentElement !== ''" + :msgCompanion="textCompanionWrongContext" + mood="sad" + /> + </div> </div> - </div> + </focus-trap> </template> <script> @@ -507,6 +494,7 @@ import CoursewareRibbon from './CoursewareRibbon.vue'; import CoursewareTabs from './CoursewareTabs.vue'; import CoursewareTab from './CoursewareTab.vue'; import CoursewareExport from '@/vue/mixins/courseware/export.js'; +import { FocusTrap } from 'focus-trap-vue'; import IsoDate from './IsoDate.vue'; import StudipDialog from '../StudipDialog.vue'; import draggable from 'vuedraggable'; @@ -527,6 +515,7 @@ export default { CoursewareEmptyElementBox, CoursewareTabs, CoursewareTab, + FocusTrap, IsoDate, StudipDialog, draggable, @@ -582,6 +571,7 @@ export default { ghostClass: 'container-ghost', }, errorEmptyChapterName: false, + consumModeTrap: false, }; }, @@ -1319,6 +1309,9 @@ export default { containers() { this.containerList = this.containers; }, + consumeMode(newState) { + this.consumModeTrap = newState; + }, }, // this line provides all the components to courseware plugins diff --git a/resources/vue/components/courseware/CoursewareTab.vue b/resources/vue/components/courseware/CoursewareTab.vue index c11a552e253c32b6c9c22867b143d7ebab898dd2..5c5217283f811f4a9d1a5fe264f09e66b153353c 100755 --- a/resources/vue/components/courseware/CoursewareTab.vue +++ b/resources/vue/components/courseware/CoursewareTab.vue @@ -1,5 +1,11 @@ <template> - <div class="cw-tab" :class="{ 'cw-tab-active': isActive }"> + <div + role="tabpanel" + class="cw-tab" + :id="id" + :class="{ 'cw-tab-active': isActive }" + :aria-labelledby="selectorId" + > <slot></slot> </div> </template> @@ -9,8 +15,9 @@ export default { name: 'courseware-tab', props: { name: {type: String, required: true }, + alias: {type: String, default: ''}, selected: { type: Boolean, default: false }, - index: {type: Number, default: 0 }, + index: {type: Number, required: true }, icon: {type: String, default: ''}, }, data() { @@ -19,12 +26,20 @@ export default { }; }, computed: { - href() { - return '#' +this.index + '-' +this.name.toLowerCase().replace(/ /g, '-'); + selectorId() { + return '#' +this.index + '-' + this.name.toLowerCase().replace(/ /g, '-'); + }, + id() { + return this.index + '-' + this.name.toLowerCase().replace(/ /g, '-') + '-tabpanel'; }, }, mounted() { this.isActive = this.selected; }, + watch: { + selected(newValue) { + this.isActive = newValue; + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareTabs.vue b/resources/vue/components/courseware/CoursewareTabs.vue index 4df1a5d724c30a911ef04eb5f51403784962c71c..db2d7cca646dd6916825136f4b264338c52f78c6 100755 --- a/resources/vue/components/courseware/CoursewareTabs.vue +++ b/resources/vue/components/courseware/CoursewareTabs.vue @@ -1,23 +1,25 @@ <template> <div class="cw-tabs"> - <ul class="cw-tabs-nav"> - <li - v-for="(tab, index) in tabs" + <div role="tablist" class="cw-tabs-nav"> + <button + v-for="(tab, index) in sortedTabs" :key="index" :class="[ tab.isActive ? 'is-active' : '', tab.icon !== '' && tab.name !== '' ? 'cw-tabs-nav-icon-text-' + tab.icon : '', tab.icon !== '' && tab.name === '' ? 'cw-tabs-nav-icon-solo-' + tab.icon : '', ]" - :href="tab.href" - tabindex="0" - @click="selectTab(tab)" - @keydown.enter="selectTab(tab)" - @keydown.space="selectTab(tab)" + :aria-selected="tab.isActive" + :aria-controls="tab.id" + :id="tab.selectorId" + :tabindex="tab.isActive ? 0 : -1" + @click="selectTab(tab.selectorId)" + @keydown="handleKeyEvent($event)" + :ref="tab.selectorId" > {{ tab.name }} - </li> - </ul> + </button> + </div> <div class="cw-tabs-content"> <slot></slot> </div> @@ -28,18 +30,90 @@ export default { name: 'courseware-tabs', data() { - return { tabs: [] }; + return { + tabs: [], + }; + }, + computed: { + sortedTabs() { + return this.tabs.sort((a, b) => { + return a.index > b.index ? 1 : a.index < b.index ? -1 : 0; + }); + } }, created() { this.tabs = this.$children; }, methods: { - selectTab(selectedTab) { + selectTab(selectorId) { + let view = this; this.tabs.forEach((tab) => { - tab.isActive = tab.index + '-' + tab.name === selectedTab.index + '-' + selectedTab.name; + tab.isActive = false; + if (tab.selectorId === selectorId) { + tab.isActive = true; + view.$refs[selectorId][0].focus(); + view.$emit('selectTab', tab); + } }); - this.$emit('selectTab', selectedTab.name); }, - }, + handleKeyEvent(e) { + let tablist = e.target.parentElement; + switch (e.keyCode) { + case 37: // left + case 38: // up + if (tablist.firstChild.id !== e.target.id) { + this.selectTab(e.target.previousElementSibling.id); + } else { + this.selectTab(tablist.lastChild.id); + } + break; + case 39: // right + case 40: // down + if (tablist.lastChild.id !== e.target.id) { + this.selectTab(e.target.nextElementSibling.id); + } else { + this.selectTab(tablist.firstChild.id); + } + break; + case 36: //pos1 + this.selectTab(tablist.firstChild.id); + break; + case 35: //end + this.selectTab(tablist.lastChild.id); + break; + } + }, + getButtonById(id) { + return this.$refs[id][0]; + }, + getFirstTabButton() { + let selectorId = this.sortedTabs[0].selectorId; + return this.$refs[selectorId][0]; + }, + getTabButtonByName(name) { + let selectorId = null; + this.tabs.forEach((tab) => { + if (tab.name === name) { + selectorId = tab.selectorId; + } + }); + if (selectorId) { + return this.$refs[selectorId][0]; + } + return null; + }, + getTabButtonByAlias(alias) { + let selectorId = null; + this.tabs.forEach((tab) => { + if (tab.alias === alias) { + selectorId = tab.selectorId; + } + }); + if (selectorId) { + return this.$refs[selectorId][0]; + } + return null; + } + } }; </script> diff --git a/resources/vue/components/courseware/CoursewareTabsContainer.vue b/resources/vue/components/courseware/CoursewareTabsContainer.vue index 66c669265153ca707a2bfbfbf66419c8972fb910..328eee1c482bec6c51f312f07cfd7ed21dc59b91 100755 --- a/resources/vue/components/courseware/CoursewareTabsContainer.vue +++ b/resources/vue/components/courseware/CoursewareTabsContainer.vue @@ -1,7 +1,7 @@ <template> <courseware-default-container :container="container" - :containerClass="'cw-container-tabs'" + containerClass="cw-container-tabs" :canEdit="canEdit" :isTeacher="isTeacher" @storeContainer="storeContainer" diff --git a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue index 7baa986c8edcfdbd684711e4b1244794c7664248..929c5a4c731ab46c16f3ee0ec16caaf74d4f3e6b 100755 --- a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue +++ b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue @@ -1,45 +1,54 @@ <template> <div class="cw-tools-element-adder"> - <ul class="cw-tools-element-adder-tabs"> - <li - :class="{ 'active': showBlockadder }" - class="cw-tools-element-adder-tab" - @click="displayBlockAdder" - > - <translate>Blöcke</translate> - </li> - <li - :class="{ 'active': showContaineradder }" - class="cw-tools-element-adder-tab" - @click="displayContainerAdder" - > - <translate>Abschnitte</translate> - </li> - </ul> - - <div v-show="showBlockadder" class="cw-tools cw-tools-blockadder"> - <courseware-collapsible-box :title="textBlockHelper"> - <courseware-block-helper :blockTypes="blockTypes" /> - </courseware-collapsible-box> - - <courseware-collapsible-box :title="textAdderFavs" :open="favoriteBlockTypes.length > 0"> - <div class="cw-element-adder-wrapper" v-if="!showEditFavs"> - <courseware-companion-box - v-if="favoriteBlockTypes.length === 0" - mood="sad" - :msgCompanion="textFavsEmpty" - /> - <courseware-blockadder-item - v-for="(block, index) in favoriteBlockTypes" - :key="index" - :title="block.title" - :icon="block.icon" - :type="block.type" - :description="block.description" - /> - </div> - - <div class="cw-element-adder-favs-wrapper" v-if="showEditFavs"> + <courseware-tabs class="cw-tools-element-adder-tabs"> + <courseware-tab :name="$gettext('Blöcke')" :selected="showBlockadder" :index="0"> + <courseware-collapsible-box :title="textBlockHelper"> + <courseware-block-helper :blockTypes="blockTypes" /> + </courseware-collapsible-box> + <courseware-collapsible-box :title="textAdderFavs" :open="favoriteBlockTypes.length > 0"> + <div class="cw-element-adder-wrapper" v-if="!showEditFavs"> + <courseware-companion-box + v-if="favoriteBlockTypes.length === 0" + mood="sad" + :msgCompanion="textFavsEmpty" + /> + <courseware-blockadder-item + v-for="(block, index) in favoriteBlockTypes" + :key="index" + :title="block.title" + :icon="block.icon" + :type="block.type" + :description="block.description" + /> + </div> + <div class="cw-element-adder-favs-wrapper" v-if="showEditFavs"> + <div class="cw-element-adder-all-blocks" :class="{ 'fav-edit-active': showEditFavs }"> + <courseware-blockadder-item + v-for="(block, index) in blockTypes" + :key="index" + :title="block.title" + :type="block.type" + :description="block.description" + /> + </div> + <div class="cw-element-adder-favs"> + <div + v-for="(block, index) in blockTypes" + :key="'fav-item-' + index" + class="cw-block-fav-item" + :class="[isBlockFav(block) ? 'cw-block-fav-item-active' : '']" + @click="toggleFavItem(block)" + ></div> + </div> + </div> + <button v-show="!showEditFavs" class="button" @click="showEditFavs = true"> + <translate>Favoriten bearbeiten</translate> + </button> + <button v-show="showEditFavs" class="button" @click="endEditFavs"> + <translate>Favoriten bearbeiten schließen</translate> + </button> + </courseware-collapsible-box> + <courseware-collapsible-box :title="textAdderAll"> <div class="cw-element-adder-all-blocks" :class="{ 'fav-edit-active': showEditFavs }"> <courseware-blockadder-item v-for="(block, index) in blockTypes" @@ -49,78 +58,50 @@ :description="block.description" /> </div> - <div class="cw-element-adder-favs"> - <div - v-for="(block, index) in blockTypes" - :key="'fav-item-' + index" - class="cw-block-fav-item" - :class="[isBlockFav(block) ? 'cw-block-fav-item-active' : '']" - @click="toggleFavItem(block)" - ></div> + </courseware-collapsible-box> + <courseware-collapsible-box + v-for="(category, index) in blockCategories" + :key="index" + :title="category.title" + :open="category.type === 'basis' && favoriteBlockTypes.length === 0" + > + <div v-for="(block, index) in blockTypes" :key="index"> + <courseware-blockadder-item + v-if="block.categories.includes(category.type)" + :title="block.title" + :icon="block.icon" + :type="block.type" + :description="block.description" + /> </div> - </div> - - <button v-show="!showEditFavs" class="button" @click="showEditFavs = true"> - <translate>Favoriten bearbeiten</translate> - </button> - <button v-show="showEditFavs" class="button" @click="endEditFavs"> - <translate>Favoriten bearbeiten schließen</translate> - </button> - </courseware-collapsible-box> - - <courseware-collapsible-box :title="textAdderAll"> - <div class="cw-element-adder-all-blocks" :class="{ 'fav-edit-active': showEditFavs }"> - <courseware-blockadder-item - v-for="(block, index) in blockTypes" - :key="index" - :title="block.title" - :type="block.type" - :description="block.description" - /> - </div> - </courseware-collapsible-box> - - <courseware-collapsible-box - v-for="(category, index) in blockCategories" - :key="index" - :title="category.title" - :open="category.type === 'basis' && favoriteBlockTypes.length === 0" - > - <div v-for="(block, index) in blockTypes" :key="index"> - <courseware-blockadder-item - v-if="block.categories.includes(category.type)" - :title="block.title" - :icon="block.icon" - :type="block.type" - :description="block.description" - /> - </div> - </courseware-collapsible-box> - </div> - - <div v-show="showContaineradder" class="cw-tools cw-tools-containeradder"> - <courseware-collapsible-box - v-for="(style, index) in containerStyles" - :key="index" - :title="style.title" - :open="index === 0" - > - <courseware-container-adder-item - v-for="(container, index) in containerTypes" + </courseware-collapsible-box> + </courseware-tab> + <courseware-tab :name="$gettext('Abschnitte')" :selected="showContaineradder" :index="1"> + <courseware-collapsible-box + v-for="(style, index) in containerStyles" :key="index" - :title="container.title" - :type="container.type" - :colspan="style.colspan" - :description="container.description" - :firstSection="$gettext('erstes Element')" - :secondSection="$gettext('zweites Element')" - ></courseware-container-adder-item> - </courseware-collapsible-box> - </div> + :title="style.title" + :open="index === 0" + > + <courseware-container-adder-item + v-for="(container, index) in containerTypes" + :key="index" + :title="container.title" + :type="container.type" + :colspan="style.colspan" + :description="container.description" + :firstSection="$gettext('erstes Element')" + :secondSection="$gettext('zweites Element')" + ></courseware-container-adder-item> + </courseware-collapsible-box> + </courseware-tab> + </courseware-tabs> </div> </template> <script> +import CoursewareTabs from './CoursewareTabs.vue'; +import CoursewareTab from './CoursewareTab.vue'; import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue'; import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue'; import CoursewareContainerAdderItem from './CoursewareContainerAdderItem.vue'; @@ -131,6 +112,8 @@ import CoursewareCompanionBox from './CoursewareCompanionBox.vue'; export default { name: 'cw-tools-blockadder', components: { + CoursewareTabs, + CoursewareTab, CoursewareCollapsibleBox, CoursewareBlockadderItem, CoursewareContainerAdderItem, @@ -216,7 +199,7 @@ export default { endEditFavs() { this.showEditFavs = false; this.$emit('scrollTop'); - } + }, }, mounted() { if (this.containerAdder === true) { diff --git a/resources/vue/components/courseware/CoursewareViewWidget.vue b/resources/vue/components/courseware/CoursewareViewWidget.vue index fd3ee9ff7b59743e7e8410b59cd17ad7804439f8..61fd77b554cbc4360790ba20c76a3558e959242e 100755 --- a/resources/vue/components/courseware/CoursewareViewWidget.vue +++ b/resources/vue/components/courseware/CoursewareViewWidget.vue @@ -1,24 +1,22 @@ <template> <ul class="widget-list widget-links sidebar-views cw-view-widget"> - <li - :class="{ active: readView }" - @click="setReadView" - > - <translate>Lesen</translate> + <li :class="{ active: readView }"> + <a href="#" @click="setReadView"> + <translate>Lesen</translate> + </a> </li> - <li - v-if="canEdit" - :class="{ active: editView }" - @click="setEditView" - > - <translate>Bearbeiten</translate> + <li :class="{ active: editView }"> + <a href="#" @click="setEditView"> + <translate>Bearbeiten</translate> + </a> </li> <li v-if="context.type === 'courses' && canVisit" :class="{ active: discussView }" - @click="setDiscussView" > - <translate>Diskutieren</translate> + <a href="#" @click="setDiscussView"> + <translate>Diskutieren</translate> + </a> </li> </ul> </template> @@ -42,24 +40,21 @@ export default { }, discussView() { return this.viewMode === 'discuss'; - }, - canEdit() { - if (!this.structuralElement) { - return false; - } - return this.structuralElement.attributes['can-edit']; - }, + } }, methods: { - ...mapActions( - ['coursewareBlockAdder'] - ), + ...mapActions({ + coursewareViewMode: 'coursewareViewMode', + coursewareBlockAdder: 'coursewareBlockAdder', + setToolbarItem: 'coursewareSelectedToolbarItem', + }), setReadView() { - this.$store.dispatch('coursewareViewMode', 'read'); + this.coursewareViewMode('read'); + this.setToolbarItem('contents'); this.coursewareBlockAdder({}); }, setEditView() { - this.$store.dispatch('coursewareViewMode', 'edit'); + this.coursewareViewMode('edit'); }, setDiscussView() { this.$store.dispatch('coursewareViewMode', 'discuss'); diff --git a/resources/vue/components/courseware/CoursewareWellcomeScreen.vue b/resources/vue/components/courseware/CoursewareWellcomeScreen.vue index 87f6672797c8146d108d650b629dafd656567088..52a629eaf4633b02f6b63edf477ff7a7b1441e5b 100755 --- a/resources/vue/components/courseware/CoursewareWellcomeScreen.vue +++ b/resources/vue/components/courseware/CoursewareWellcomeScreen.vue @@ -5,8 +5,8 @@ <translate>Willkommen bei Courseware</translate> </header> <div class="cw-wellcome-screen-actions"> - <a href="https://hilfe.studip.de/help/5.0/de/Basis.Courseware" target="_blank"> - <button class="button"><translate>Mehr über Courseware erfahren</translate></button> + <a href="https://hilfe.studip.de/help/5.0/de/Basis.Courseware" target="_blank" class="button"> + <translate>Mehr über Courseware erfahren</translate> </a> <button class="button" :title="$gettext('Fügt einen Standard-Abschnitt mit einem Text-Block hinzu')" @click="addDefault"><translate>Ersten Inhalt erstellen</translate></button> <button class="button" @click="addContainer"><translate>Einen Abschnitt auswählen</translate></button> @@ -78,4 +78,4 @@ export default { } } -</script> \ No newline at end of file +</script> diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js index dd3c61bbbb1c701a848a4d7f51918c36486f8ae8..dd806598117fd4553c02d8d44aca3679fe51cc72 100755 --- a/resources/vue/store/courseware/courseware.module.js +++ b/resources/vue/store/courseware/courseware.module.js @@ -20,6 +20,7 @@ const getDefaultState = () => { pluginManager: null, showCompanionOverlay: false, showToolbar: false, + selectedToolbarItem: 'contents', urlHelper: null, userId: null, viewMode: 'read', @@ -101,6 +102,9 @@ const getters = { showToolbar(state) { return state.showToolbar; }, + selectedToolbarItem(state) { + return state.selectedToolbarItem; + }, blockAdder(state) { return state.blockAdder; }, @@ -719,6 +723,10 @@ export const actions = { context.commit('coursewareShowToolbarSet', toolbar); }, + coursewareSelectedToolbarItem(context, item) { + context.commit('coursewareSelectedToolbarItemSet', item); + }, + coursewareBlockAdder(context, adder) { context.commit('coursewareBlockAdderSet', adder); }, @@ -1160,6 +1168,10 @@ export const mutations = { state.showToolbar = data; }, + coursewareSelectedToolbarItemSet(state, data) { + state.selectedToolbarItem = data; + }, + coursewareBlockAdderSet(state, data) { state.blockAdder = data; },