@@ -977,105 +977,139 @@ function recordedAudioNotSaved(error) {
977977 console .error (error)
978978}
979979
980- function openMoveDialog (document ) {
981- item .value = document
982- isMoveDialogVisible .value = true
983- }
984-
985- function openReplaceDialog (document ) {
986- if (! canEdit (document )) {
987- return
980+ /**
981+ * -----------------------------------------
982+ * MOVE: helpers + folders fetching
983+ * -----------------------------------------
984+ */
985+ function normalizeResourceNodeId (value ) {
986+ if (value == null ) return null
987+ if (typeof value === " number" ) return value
988+
989+ if (typeof value === " string" ) {
990+ // Accept IRI like "/api/resource_nodes/123"
991+ const iriMatch = value .match (/ \/ api\/ resource_nodes\/ (\d + )/ )
992+ if (iriMatch) return Number (iriMatch[1 ])
993+
994+ // Accept "123"
995+ if (/ ^ \d + $ / .test (value)) return Number (value)
988996 }
989997
990- documentToReplace .value = document
991- isReplaceDialogVisible .value = true
998+ return null
992999}
9931000
994- async function replaceDocument () {
995- if (! selectedReplaceFile .value ) {
996- notification .showErrorNotification (t (" No file selected." ))
997- return
998- }
1001+ function getRootNodeIdForFolders () {
1002+ let node = resourceNode .value
1003+ let fallback =
1004+ normalizeResourceNodeId (node? .id ) ??
1005+ normalizeResourceNodeId (route .params .node ) ??
1006+ normalizeResourceNodeId (route .query .node )
9991007
1000- if ( ! ( documentToReplace . value . filetype === " file " || documentToReplace . value . filetype === " video " ) ) {
1001- notification . showErrorNotification ( t ( " Only files can be replaced. " ))
1002- return
1008+ while (node ? . parent ) {
1009+ if (node ? . resourceType ? . title === " courses " ) break
1010+ node = node . parent
10031011 }
10041012
1005- const formData = new FormData ()
1006- formData .append (" file" , selectedReplaceFile .value )
1007- try {
1008- await axios .post (` /api/documents/${ documentToReplace .value .iid } /replace` , formData, {
1009- headers: {
1010- " Content-Type" : " multipart/form-data" ,
1011- },
1012- })
1013- notification .showSuccessNotification (t (" File replaced" ))
1014- isReplaceDialogVisible .value = false
1015- onUpdateOptions (options .value )
1016- } catch (error) {
1017- notification .showErrorNotification (t (" Error replacing file." ))
1018- console .error (error)
1019- }
1013+ return normalizeResourceNodeId (node? .id ) ?? fallback
10201014}
10211015
10221016async function fetchFolders (nodeId = null , parentPath = " " ) {
1017+ const startId = normalizeResourceNodeId (nodeId || route .params .node || route .query .node )
1018+
10231019 const foldersList = [
10241020 {
10251021 label: t (" Documents" ),
1026- value: nodeId || route . params . node || route . query . node || " root-node-id" ,
1022+ value: startId || " root-node-id" ,
10271023 },
10281024 ]
10291025
10301026 try {
1031- let nodesToFetch = [{ id: nodeId || route . params . node || route . query . node , path: parentPath }]
1027+ let nodesToFetch = [{ id: startId , path: parentPath }]
10321028 let depth = 0
10331029 const maxDepth = 5
10341030
10351031 while (nodesToFetch .length > 0 && depth < maxDepth) {
10361032 const currentNode = nodesToFetch .shift ()
1033+ const currentNodeId = normalizeResourceNodeId (currentNode? .id )
1034+
1035+ if (! currentNodeId) {
1036+ depth++
1037+ continue
1038+ }
10371039
10381040 const response = await axios .get (" /api/documents" , {
10391041 params: {
1040- filetype: " folder" ,
1041- " resourceNode.parent" : currentNode .id ,
1042- cid: route .query .cid ,
1043- sid: route .query .sid ,
1042+ loadNode: 1 ,
1043+ filetype: [" folder" ],
1044+ " resourceNode.parent" : currentNodeId,
1045+ cid,
1046+ sid,
1047+ gid,
1048+ page: 1 ,
1049+ itemsPerPage: 200 ,
10441050 },
10451051 })
10461052
1047- response .data [" hydra:member" ].forEach ((folder ) => {
1048- const fullPath = ` ${ currentNode .path } /${ folder .title } `
1053+ const members = response .data ? .[" hydra:member" ] || []
1054+
1055+ members .forEach ((folder ) => {
1056+ const folderNodeId =
1057+ normalizeResourceNodeId (folder? .resourceNode ? .id ) ?? normalizeResourceNodeId (folder? .resourceNodeId )
1058+
1059+ if (! folderNodeId) {
1060+ return
1061+ }
1062+
1063+ const fullPath = ` ${ currentNode .path } /${ folder .title } ` .replace (/ ^ \/ + / , " " )
10491064
10501065 foldersList .push ({
10511066 label: fullPath,
1052- value: folder . resourceNode ? . id || folder . resourceNodeId || folder[ " @id " ],
1067+ value: folderNodeId, // ALWAYS numeric
10531068 })
10541069
1055- if (folder .resourceNode && folder .resourceNode .id ) {
1056- nodesToFetch .push ({ id: folder .resourceNode .id , path: fullPath })
1057- }
1070+ nodesToFetch .push ({ id: folderNodeId, path: fullPath })
10581071 })
10591072
10601073 depth++
10611074 }
10621075
10631076 return foldersList
10641077 } catch (error) {
1065- console .error (" Error fetching folders:" , error .message || error)
1066- return []
1078+ console .error (" Error fetching folders:" , error? .message || error)
1079+ return foldersList
10671080 }
10681081}
10691082
10701083async function loadAllFolders () {
1084+ // Keep your behavior: start from current node.
1085+ // If you want ALWAYS from course root, tell me and I’ll adjust in 2 lines.
10711086 folders .value = await fetchFolders ()
10721087}
10731088
1089+ async function openMoveDialog (document ) {
1090+ item .value = document
1091+ selectedFolder .value = null
1092+ await loadAllFolders ()
1093+ isMoveDialogVisible .value = true
1094+ }
1095+
10741096async function moveDocument () {
10751097 try {
1076- const response = await axios .put (` /api/documents/${ item .value .iid } /move` , {
1077- parentResourceNodeId: selectedFolder .value ,
1078- })
1098+ const parentId = normalizeResourceNodeId (selectedFolder .value )
1099+
1100+ if (! parentId) {
1101+ notification .showErrorNotification (t (" Select a folder" ))
1102+ return
1103+ }
1104+
1105+ await axios .put (
1106+ ` /api/documents/${ item .value .iid } /move` ,
1107+ { parentResourceNodeId: parentId },
1108+ {
1109+ // IMPORTANT: backend needs context to move the correct resource_link
1110+ params: { cid, sid, gid },
1111+ },
1112+ )
10791113
10801114 notification .showSuccessNotification (t (" Document moved successfully" ))
10811115 isMoveDialogVisible .value = false
@@ -1086,6 +1120,53 @@ async function moveDocument() {
10861120 }
10871121}
10881122
1123+ /**
1124+ * -----------------------------------------
1125+ * REPLACE
1126+ * -----------------------------------------
1127+ */
1128+ function openReplaceDialog (document ) {
1129+ if (! canEdit (document )) {
1130+ return
1131+ }
1132+
1133+ documentToReplace .value = document
1134+ isReplaceDialogVisible .value = true
1135+ }
1136+
1137+ async function replaceDocument () {
1138+ if (! selectedReplaceFile .value ) {
1139+ notification .showErrorNotification (t (" No file selected." ))
1140+ return
1141+ }
1142+
1143+ if (! (documentToReplace .value .filetype === " file" || documentToReplace .value .filetype === " video" )) {
1144+ notification .showErrorNotification (t (" Only files can be replaced." ))
1145+ return
1146+ }
1147+
1148+ const formData = new FormData ()
1149+ formData .append (" file" , selectedReplaceFile .value )
1150+ try {
1151+ await axios .post (` /api/documents/${ documentToReplace .value .iid } /replace` , formData, {
1152+ headers: {
1153+ " Content-Type" : " multipart/form-data" ,
1154+ },
1155+ })
1156+ notification .showSuccessNotification (t (" File replaced" ))
1157+ isReplaceDialogVisible .value = false
1158+ onUpdateOptions (options .value )
1159+ } catch (error) {
1160+ notification .showErrorNotification (t (" Error replacing file." ))
1161+ console .error (error)
1162+ }
1163+ }
1164+
1165+ /**
1166+ * -----------------------------------------
1167+ * CERTIFICATES
1168+ * -----------------------------------------
1169+ */
10891170async function selectAsDefaultCertificate (certificate ) {
10901171 try {
10911172 const response = await axios .patch (` /gradebook/set_default_certificate/${ cid} /${ certificate .iid } ` )
@@ -1113,6 +1194,11 @@ async function loadDefaultCertificate() {
11131194 }
11141195}
11151196
1197+ /**
1198+ * -----------------------------------------
1199+ * TEMPLATE
1200+ * -----------------------------------------
1201+ */
11161202const showTemplateFormModal = ref (false )
11171203const selectedFile = ref (null )
11181204const templateFormData = ref ({
0 commit comments