Skip to content

Commit 42ea6db

Browse files
committed
Merge remote-tracking branch 'origin/master'
2 parents b0b2256 + ff13e91 commit 42ea6db

16 files changed

Lines changed: 112484 additions & 228 deletions

File tree

assets/vue/views/documents/DocumentsList.vue

Lines changed: 70 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@
112112
v-model:filters="filters"
113113
v-model:selected-items="selectedItems"
114114
:global-filter-fields="['resourceNode.title', 'resourceNode.updatedAt']"
115-
:is-loading="isLoading"
115+
:is-loading="tableIsLoading"
116116
v-model:rows="options.itemsPerPage"
117117
:total-items="totalItems"
118118
:values="items"
@@ -672,6 +672,44 @@ const defaultCertificateId = ref(null)
672672
673673
const isCurrentTeacher = computed(() => securityStore.isCurrentTeacher && !platformConfigStore.isStudentViewActive)
674674
675+
/**
676+
* Local loading flag to show the table spinner immediately.
677+
* This prevents the "empty table" impression while the store is still preparing the request.
678+
*/
679+
const tableLoading = ref(true)
680+
const hasRequestedList = ref(false)
681+
682+
const tableIsLoading = computed(() => {
683+
return tableLoading.value || isLoading.value
684+
})
685+
686+
function triggerTableLoad() {
687+
// Make sure spinner appears immediately on any list refresh.
688+
hasRequestedList.value = true
689+
tableLoading.value = true
690+
onUpdateOptions(options.value)
691+
}
692+
693+
// Keep local loading in sync with the store loading state.
694+
watch(isLoading, (val) => {
695+
if (val) {
696+
tableLoading.value = true
697+
return
698+
}
699+
// If we already triggered at least one load and the store is not loading anymore, hide spinner.
700+
if (hasRequestedList.value) {
701+
tableLoading.value = false
702+
}
703+
})
704+
705+
// if store loading toggles late (or not at all), stop local loading when data changes.
706+
watch([items, totalItems], () => {
707+
if (!hasRequestedList.value) return
708+
if (!isLoading.value) {
709+
tableLoading.value = false
710+
}
711+
})
712+
675713
function resolveDefaultRows(total = 0) {
676714
const raw = platformConfigStore.getSetting("display.table_default_row", 10)
677715
const def = Number(raw)
@@ -701,6 +739,7 @@ const selectedReplaceFile = ref(null)
701739
const documentToReplace = ref(null)
702740
703741
onMounted(async () => {
742+
tableLoading.value = true
704743
isAllowedToEdit.value = await checkIsAllowedToEdit(true, true, true)
705744
filters.value.loadNode = 1
706745
filters.value.filetype = ["file", "folder", "video"]
@@ -714,18 +753,19 @@ onMounted(async () => {
714753
715754
await store.dispatch("resourcenode/findResourceNode", { id: `/api/resource_nodes/${nodeId}` })
716755
717-
await loadDefaultCertificate()
718-
await loadAllFolders()
719-
options.value.itemsPerPage = resolveDefaultRows(totalItems.value || 0)
720-
onUpdateOptions(options.value)
756+
options.value.itemsPerPage = resolveDefaultRows(0)
757+
triggerTableLoad()
758+
void loadDefaultCertificate()
759+
void loadAllFolders()
721760
722-
try {
723-
await courseSettingsStore.loadCourseSettings(cid, sid)
724-
} catch (e) {
725-
console.error("[AI] loadCourseSettings failed:", e)
726-
}
761+
void courseSettingsStore
762+
.loadCourseSettings(cid, sid)
763+
.catch((e) => console.error("[AI] loadCourseSettings failed:", e))
764+
.finally(() => {
765+
void loadAiCapabilities()
766+
})
727767
728-
await loadAiCapabilities()
768+
void loadAiCapabilities()
729769
consumeAiSavedToast()
730770
})
731771
@@ -747,6 +787,7 @@ watch(totalItems, (n) => {
747787
const def = Number(platformConfigStore.getSetting("display.table_default_row", 10))
748788
if (def === 0 && n) {
749789
options.value.itemsPerPage = n
790+
tableLoading.value = true
750791
onUpdateOptions(options.value)
751792
}
752793
})
@@ -760,7 +801,7 @@ watch(
760801
store.dispatch("resourcenode/findResourceNode", finderParams)
761802
762803
if ("DocumentsList" === route.name) {
763-
onUpdateOptions(options.value)
804+
triggerTableLoad()
764805
}
765806
},
766807
)
@@ -826,9 +867,10 @@ function createNewFolder() {
826867
},
827868
])
828869
870+
tableLoading.value = true
829871
store.dispatch("documents/createWithFormData", item.value).then(() => {
830872
notification.showSuccessNotification(t("Saved"))
831-
onUpdateOptions(options.value)
873+
triggerTableLoad()
832874
})
833875
}
834876
isNewFolderDialogVisible.value = false
@@ -868,13 +910,14 @@ async function forceDeleteItem() {
868910
try {
869911
const docIdsToDelete = [...new Set(lpListWarning.value.map((lp) => lp.documentId))]
870912
913+
tableLoading.value = true
871914
await Promise.all(docIdsToDelete.map((iid) => axios.delete(`/api/documents/${iid}`)))
872915
873916
notification.showSuccessNotification(t("Documents deleted"))
874917
isDeleteWarningLpDialogVisible.value = false
875918
item.value = {}
876919
unselectAll()
877-
onUpdateOptions(options.value)
920+
triggerTableLoad()
878921
} catch (error) {
879922
console.error("[Documents] Error deleting documents forcibly:", error)
880923
notification.showErrorNotification(t("Error deleting document(s)."))
@@ -927,6 +970,7 @@ async function deleteMultipleItems() {
927970
const itemsWithoutLp = []
928971
const documentsWithLpMap = {}
929972
973+
tableLoading.value = true
930974
for (const item of selectedItems.value) {
931975
try {
932976
const response = await axios.get(`/api/documents/${item.iid}/lp-usage`)
@@ -974,17 +1018,19 @@ async function deleteMultipleItems() {
9741018
}
9751019
9761020
isDeleteMultipleDialogVisible.value = false
977-
onUpdateOptions(options.value)
1021+
triggerTableLoad()
9781022
}
9791023
9801024
function unselectAll() {
9811025
selectedItems.value = []
9821026
}
9831027
9841028
function deleteSingleItem() {
1029+
tableLoading.value = true
9851030
deleteItem(item)
9861031
item.value = {}
9871032
isDeleteItemDialogVisible.value = false
1033+
triggerTableLoad()
9881034
}
9891035
9901036
function onPage(event) {
@@ -995,14 +1041,14 @@ function onPage(event) {
9951041
sortDesc: event.sortOrder === -1,
9961042
}
9971043
998-
onUpdateOptions(options.value)
1044+
triggerTableLoad()
9991045
}
10001046
10011047
function sortingChanged(event) {
10021048
options.value.sortBy = event.sortField
10031049
options.value.sortDesc = event.sortOrder === -1
10041050
1005-
onUpdateOptions(options.value)
1051+
triggerTableLoad()
10061052
}
10071053
10081054
function goToNewDocument() {
@@ -1094,7 +1140,7 @@ function showRecordAudioDialog() {
10941140
function recordedAudioSaved() {
10951141
notification.showSuccessNotification(t("Saved"))
10961142
isRecordAudioDialogVisible.value = false
1097-
onUpdateOptions(options.value)
1143+
triggerTableLoad()
10981144
}
10991145
11001146
function recordedAudioNotSaved(error) {
@@ -1260,7 +1306,7 @@ async function moveDocument() {
12601306
12611307
notification.showSuccessNotification(t("Document moved successfully"))
12621308
isMoveDialogVisible.value = false
1263-
onUpdateOptions(options.value)
1309+
triggerTableLoad()
12641310
} catch (error) {
12651311
console.error("[Documents] Error moving document:", error.response || error)
12661312
notification.showErrorNotification(t("Error moving the document"))
@@ -1302,7 +1348,7 @@ async function replaceDocument() {
13021348
})
13031349
notification.showSuccessNotification(t("File replaced"))
13041350
isReplaceDialogVisible.value = false
1305-
onUpdateOptions(options.value)
1351+
triggerTableLoad()
13061352
} catch (error) {
13071353
notification.showErrorNotification(t("Error replacing file."))
13081354
console.error(error)
@@ -1319,7 +1365,7 @@ async function selectAsDefaultCertificate(certificate) {
13191365
const response = await axios.patch(`/gradebook/set_default_certificate/${cid}/${certificate.iid}`)
13201366
if (response.status === 200) {
13211367
loadDefaultCertificate()
1322-
onUpdateOptions(options.value)
1368+
triggerTableLoad()
13231369
notification.showSuccessNotification(t("Certificate set as default successfully"))
13241370
}
13251371
} catch {
@@ -1368,7 +1414,7 @@ const isDocumentTemplate = async (documentId) => {
13681414
const deleteDocumentTemplate = async (documentId) => {
13691415
try {
13701416
await axios.post(`/template/document-templates/${documentId}/delete`)
1371-
onUpdateOptions(options.value)
1417+
triggerTableLoad()
13721418
notification.showSuccessNotification(t("Template successfully deleted."))
13731419
} catch (error) {
13741420
console.error("[Documents] Error deleting template:", error)
@@ -1386,7 +1432,7 @@ const openTemplateForm = async (documentId) => {
13861432
13871433
if (isTemplate) {
13881434
await deleteDocumentTemplate(documentId)
1389-
onUpdateOptions(options.value)
1435+
triggerTableLoad()
13901436
} else {
13911437
currentDocumentId.value = documentId
13921438
showTemplateFormModal.value = true
@@ -1419,7 +1465,7 @@ const submitTemplateForm = async () => {
14191465
templateFormData.value.title = ""
14201466
selectedFile.value = null
14211467
showTemplateFormModal.value = false
1422-
onUpdateOptions(options.value)
1468+
triggerTableLoad()
14231469
} else {
14241470
notification.showErrorNotification(t("Error creating the template."))
14251471
}

public/main/auth/courses.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@
114114
}
115115
if (Security::check_token('get')) {
116116
$courseInfo = api_get_course_info($courseCodeToSubscribe);
117-
CourseManager::autoSubscribeToCourse($courseCodeToSubscribe);
117+
CourseManager::autoSubscribeToCourse($courseInfo['id']);
118118
if ('course_home' === $redirectAfterSubscription) {
119119
$redirectionTarget = $courseInfo['course_public_url'];
120120
if ('true' === api_get_setting('catalog.course_subscription_in_user_s_session')) {
@@ -154,14 +154,14 @@
154154
);
155155
$form->addHeader($message);
156156
$form->addElement('hidden', 'sec_token', Security::getTokenFromSession());
157-
$form->addElement('hidden', 'subscribe_user_with_password', $courseInfo['code']);
157+
$form->addElement('hidden', 'subscribe_user_with_password', $courseInfo['id']);
158158
$form->addElement('text', 'course_registration_code');
159159
$form->addButtonSave(get_lang('Submit registration code'));
160160
$content = $form->returnForm();
161161

162162
if ($form->validate()) {
163163
if (sha1($_POST['course_registration_code']) === $courseInfo['registration_code']) {
164-
CourseManager::autoSubscribeToCourse($_POST['subscribe_user_with_password']);
164+
CourseManager::autoSubscribeToCourse((int)$_POST['subscribe_user_with_password']);
165165

166166
if ('course_home' === $redirectAfterSubscription) {
167167
$redirectionTarget = $courseInfo['course_public_url'];

public/main/auth/registration.php

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,20 @@
436436
}
437437

438438
// Direct Link Subscription feature #5299
439-
$course_code_redirect = isset($_REQUEST['c']) && !empty($_REQUEST['c']) ? $_REQUEST['c'] : null;
440-
$exercise_redirect = isset($_REQUEST['e']) && !empty($_REQUEST['e']) ? $_REQUEST['e'] : null;
439+
$courseIdRedirect = isset($_REQUEST['c']) && !empty($_REQUEST['c']) ? (int) $_REQUEST['c'] : null;
440+
$exercise_redirect = isset($_REQUEST['e']) && !empty($_REQUEST['e']) ? (int) $_REQUEST['e'] : null;
441441

442-
if (!empty($course_code_redirect)) {
442+
if (!empty($courseIdRedirect)) {
443443
if (!api_is_anonymous()) {
444-
$course_info = api_get_course_info($course_code_redirect);
445-
$subscribed = CourseManager::autoSubscribeToCourse($course_code_redirect);
444+
$subscribed = CourseManager::autoSubscribeToCourse($courseIdRedirect);
446445
if ($subscribed) {
447-
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$course_info['real_id'].'/home?sid=0');
446+
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$courseIdRedirect.'/home?sid=0');
448447
} else {
449-
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$course_info['real_id'].'/about');
448+
header('Location: ' . api_get_path(WEB_PATH) . 'course/'.$courseIdRedirect.'/about');
450449
}
451450
exit;
452451
}
453-
Session::write('course_redirect', $course_code_redirect);
452+
Session::write('course_redirect', $courseIdRedirect);
454453
Session::write('exercise_redirect', $exercise_redirect);
455454
}
456455

@@ -1182,7 +1181,7 @@ function ($email) {
11821181
$showTerms = true;
11831182
}
11841183

1185-
$course_code_redirect = Session::read('course_redirect');
1184+
$courseIdRedirect = Session::read('course_redirect');
11861185
$sessionToRedirect = Session::read('session_redirect');
11871186

11881187
if ($extraConditions && $extraFieldsLoaded) {
@@ -1360,8 +1359,8 @@ function ($email) {
13601359
}
13611360

13621361
// Saving user to course if it was set.
1363-
if (!empty($course_code_redirect)) {
1364-
$course_info = api_get_course_info($course_code_redirect);
1362+
if (!empty($courseIdRedirect)) {
1363+
$course_info = api_get_course_info_by_id($courseIdRedirect);
13651364
if (!empty($course_info)) {
13661365
if (in_array(
13671366
$course_info['visibility'],
@@ -1373,13 +1372,13 @@ function ($email) {
13731372
) {
13741373
CourseManager::subscribeUser(
13751374
$userId,
1376-
$course_info['real_id']
1375+
$courseIdRedirect
13771376
);
13781377
}
13791378
}
13801379
}
13811380

1382-
/* If the account has to be approved then we set the account to inactive,
1381+
/* If the account has to be approved, then we set the account to inactive,
13831382
sent a mail to the platform admin and exit the page.*/
13841383
if ('approval' === api_get_setting('allow_registration')) {
13851384
// 1. Send mail to all platform admin
@@ -1618,8 +1617,8 @@ function ($email) {
16181617
} else {
16191618
if (!api_is_anonymous()) {
16201619
// Saving user to course if it was set.
1621-
if (!empty($course_code_redirect)) {
1622-
$course_info = api_get_course_info($course_code_redirect);
1620+
if (!empty($courseIdRedirect)) {
1621+
$course_info = api_get_course_info_by_id($courseIdRedirect);
16231622
if (!empty($course_info)) {
16241623
if (in_array(
16251624
$course_info['visibility'],
@@ -1631,7 +1630,7 @@ function ($email) {
16311630
) {
16321631
CourseManager::subscribeUser(
16331632
api_get_user_id(),
1634-
$course_info['real_id']
1633+
$courseIdRedirect
16351634
);
16361635
}
16371636
}

public/main/course_info/infocours.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@
257257

258258
// --- End of legacy block, from here panels start as usual ---
259259

260-
$url = api_get_path(WEB_CODE_PATH)."auth/inscription.php?c=$course_code&e=1";
260+
$url = api_get_path(WEB_CODE_PATH)."auth/registration.php?c=$courseId&e=1";
261261
$url = Display::url($url, $url);
262262
$label = $form->addLabel(
263263
get_lang('Direct link'),

0 commit comments

Comments
 (0)