2828</template >
2929
3030<script setup>
31- import { computed , ref , watch , watchEffect } from " vue"
31+ import { computed , ref , watch , watchEffect , onMounted } from " vue"
3232import { useRoute , useRouter } from " vue-router"
3333import { useI18n } from " vue-i18n"
3434import Breadcrumb from " primevue/breadcrumb"
3535import { useCidReqStore } from " ../store/cidReq"
3636import { storeToRefs } from " pinia"
3737import { useStore } from " vuex"
3838
39- const legacyItems = ref (window . breadcrumb )
39+ const legacyItems = ref ([] )
4040
4141const cidReqStore = useCidReqStore ()
4242const route = useRoute ()
4343const router = useRouter ()
44- const { t } = useI18n ()
44+ const { t , te } = useI18n ()
4545
4646const { course , session } = storeToRefs (cidReqStore)
4747const store = useStore ()
@@ -61,6 +61,13 @@ const specialRouteNames = [
6161
6262const itemList = ref ([])
6363
64+ onMounted (() => {
65+ const wb = (window && window .breadcrumb ) || []
66+ if (Array .isArray (wb) && wb .length > 0 ) {
67+ legacyItems .value = wb
68+ }
69+ })
70+
6471const formatToolName = (name ) => {
6572 if (! name) return " "
6673 return name
@@ -189,6 +196,35 @@ function addDocumentBreadcrumb() {
189196 }
190197}
191198
199+ /**
200+ * Resolve translated label for /admin/settings/:namespace
201+ */
202+ function resolveSettingsSectionLabel (nsRaw ) {
203+ const ns = String (nsRaw || " " ).trim ()
204+ // Safer because it's already translated server-side.
205+ try {
206+ const current = document .querySelector (" .list-group a.bg-gray-25" )
207+ const domText = current? .textContent ? .trim ()
208+ if (domText) {
209+ return domText
210+ }
211+ } catch (e) {}
212+
213+ // i18n candidates
214+ const candidates = [
215+ ` settings_section.${ ns} ` ,
216+ ` settings_section.${ ns .replace (/ -/ g , " _" )} ` ,
217+ ns,
218+ ns .replace (/ [-_] / g , " " ),
219+ ]
220+ for (const key of candidates) {
221+ const has = typeof te === " function" && te (key)
222+ if (has) return t (key)
223+ }
224+
225+ return ns .replace (/ [-_] / g , " " ).replace (/ \b \w / g , (c ) => c .toUpperCase ())
226+ }
227+
192228// Watch route changes to dynamically rebuild the breadcrumb trail
193229watchEffect (() => {
194230 if (" /" === route .fullPath ) return
@@ -220,10 +256,10 @@ watchEffect(() => {
220256 const mainUrl = window .location .href
221257 const mainPath = mainUrl .indexOf (" main/" )
222258 legacyItems .value .forEach ((item ) => {
223- let newUrl = item .url .toString ()
259+ let newUrl = ( item .url || " " ) .toString ()
224260 if (newUrl .indexOf (" main/" ) > 0 ) newUrl = " /" + newUrl .substring (mainPath)
225261 if (newUrl === " /" ) newUrl = " #"
226- itemList .value .push ({ label: item .name , url: newUrl })
262+ itemList .value .push ({ label: item .name , url: newUrl || undefined })
227263 })
228264 legacyItems .value = []
229265 } else if (course .value && route .name !== " CourseHome" ) {
@@ -268,7 +304,7 @@ watchEffect(() => {
268304 if (mainToolName === " ccalendarevent" ) {
269305 const cid = Number (route .query ? .cid || 0 )
270306 const gid = Number (route .query ? .gid || 0 )
271- toolLabel = gid > 0 ? " Group agenda" : ( cid > 0 ? " Agenda" : " Personal agenda" )
307+ toolLabel = gid > 0 ? " Group agenda" : cid > 0 ? " Agenda" : " Personal agenda"
272308 }
273309 itemList .value .push ({
274310 label: t (toolLabel),
@@ -300,10 +336,24 @@ watchResourceNodeLoader()
300336function cleanIdParam (id ) {
301337 if (! id) return undefined
302338 const match = id .toString ().match (/ (\d + )$ / )
303- return match ? match[1 ] : id
339+ return match ? id . toString (). match ( / ( \d + ) $ / ) [1 ] : id
304340}
305341
306342function buildManualBreadcrumbIfNeeded () {
343+ // If server already injected legacy breadcrumbs, use them.
344+ if (Array .isArray (legacyItems .value ) && legacyItems .value .length > 0 ) {
345+ const mainUrl = window .location .href
346+ const mainPath = mainUrl .indexOf (" main/" )
347+ legacyItems .value .forEach ((item ) => {
348+ let newUrl = (item .url || " " ).toString ()
349+ if (newUrl .indexOf (" main/" ) > 0 ) newUrl = " /" + newUrl .substring (mainPath)
350+ if (newUrl === " /" ) newUrl = " #"
351+ itemList .value .push ({ label: item .name , url: newUrl || undefined })
352+ })
353+ legacyItems .value = []
354+ return true
355+ }
356+
307357 const whitelist = [" admin" ]
308358 const overrides = {
309359 admin: " AdminIndex" ,
@@ -316,6 +366,24 @@ function buildManualBreadcrumbIfNeeded() {
316366 return false
317367 }
318368
369+ // /admin/settings/<namespace>
370+ const isAdminSettings = pathSegments[1 ] === " settings"
371+ if (isAdminSettings) {
372+ const ns = pathSegments[2 ] || route .params ? .namespace || route .query ? .namespace || " "
373+ const adminLabel = t (" Admin" )
374+ itemList .value .push ({
375+ label: adminLabel,
376+ route: { name: overrides .admin , params: route .params , query: route .query },
377+ })
378+ itemList .value .push ({
379+ label: t (" Settings" ),
380+ route: { path: " /admin/settings" },
381+ })
382+ const section = resolveSettingsSectionLabel (ns)
383+ itemList .value .push ({ label: section })
384+ return true
385+ }
386+
319387 const fullPath = " /" + pathSegments .join (" /" )
320388 const hasMatchedRoute = router .getRoutes ().some ((r ) => r .path === fullPath)
321389
0 commit comments