diff --git a/dashboard-prime/src/components/quiz/QuizDefinitions.vue b/dashboard-prime/src/components/quiz/QuizDefinitions.vue index 9f2ba5d98c..70a708c1cd 100644 --- a/dashboard-prime/src/components/quiz/QuizDefinitions.vue +++ b/dashboard-prime/src/components/quiz/QuizDefinitions.vue @@ -250,7 +250,7 @@ defineExpose({ outlined size="small"/> </router-link> - <ButtonGroup class="ml-1 flex-nowrap"> + <ButtonGroup class="ml-1 flex flex-nowrap"> <SkillsButton @click="showUpdateModal(slotProps.data)" icon="fas fa-edit" outlined diff --git a/dashboard-prime/src/components/skills/SkillNameRouterLink.vue b/dashboard-prime/src/components/skills/SkillNameRouterLink.vue new file mode 100644 index 0000000000..8922b98572 --- /dev/null +++ b/dashboard-prime/src/components/skills/SkillNameRouterLink.vue @@ -0,0 +1,71 @@ +<script setup> + +import { computed, onMounted, ref } from 'vue'; +import StringHighlighter from '@/common-components/utilities/StringHighlighter.js'; + +const props = defineProps({ + skill: Object, + subjectId: String, + filterValue: String, + limit: { + type: Number, + required: false, + default: 45, + }, +}); + +const slop = ref(15); +const displayFullText = ref(false); + +onMounted(() => { + displayFullText.value = props.skill.name.length < props.limit + slop.value; +}); + +const truncate = computed(() => { + return props.skill.name.length >= props.limit + slop.value; +}); + +const toDisplay = computed(() => { + if (displayFullText.value) { + return `${props.skill.name}`; + } + return `${props.skill.name.substring(0, props.limit)}`; +}); + +const highlightedValue = computed(() => { + const value = toDisplay.value; + const filterValue = props.filterValue ? props.filterValue.trim() : ''; + if (filterValue && filterValue.length > 0) { + const highlighted = StringHighlighter.highlight(value, filterValue); + return highlighted || value; + } else { + return value; + } +}); + +</script> + +<template> + <div class="inline-block"> + <router-link :data-cy="`manageSkillLink_${skill.skillId}`" + tag="span" + :to="{ name:'SkillOverview', params: { projectId: skill.projectId, subjectId: subjectId, skillId: skill.skillId }}" + :aria-label="`Manage skill ${skill.name} via link`"> + <span class="text-lg inline-block" v-html="highlightedValue" /> + </router-link> + <a v-if="truncate" + @click="displayFullText = !displayFullText" + aria-label="Show/Hide truncated text" + data-cy="showMoreOrLessBtn"> + <small v-if="displayFullText" data-cy="showLess"> << less</small> + <small v-else data-cy="showMore"><em>... >> more</em></small> + </a> + </div> +</template> + +<style scoped> +a, +a small { + cursor: pointer; +} +</style> \ No newline at end of file diff --git a/dashboard-prime/src/components/skills/SkillsTable.vue b/dashboard-prime/src/components/skills/SkillsTable.vue index f8d408ea59..92aad0a763 100644 --- a/dashboard-prime/src/components/skills/SkillsTable.vue +++ b/dashboard-prime/src/components/skills/SkillsTable.vue @@ -46,6 +46,7 @@ import RemoveSkillTagDialog from '@/components/skills/tags/RemoveSkillTagDialog. import SkillsDataTable from '@/components/utils/table/SkillsDataTable.vue' import { useLog } from '@/components/utils/misc/useLog.js' import { useAppConfig } from '@/common-components/stores/UseAppConfig.js'; +import SkillNameRouterLink from '@/components/skills/SkillNameRouterLink.vue'; const YEARLY = 'YEARLY'; const MONTHLY = 'MONTHLY'; @@ -553,16 +554,11 @@ const isLoading = computed(() => { :value="slotProps.data.name" :filter="filters.global.value" /> </div> - <div v-if="!slotProps.data.isGroupType" class="flex-1 w-min-10rem"> - <router-link - class="" - :data-cy="`manageSkillLink_${slotProps.data.skillId}`" - :to="{ name:'SkillOverview', params: { projectId: slotProps.data.projectId, subjectId, skillId: slotProps.data.skillId }}" - > - <highlighted-value - class="text-lg" - :value="slotProps.data.name" :filter="filters.global.value" /> - </router-link> + <div v-if="!slotProps.data.isGroupType" class="flex-1"> + <div class="flex"> + <SkillNameRouterLink :skill="slotProps.data" :subjectId="subjectId" + :filter-value="filters.global.value"/> + </div> <div class="flex flex-wrap gap-1"> <Tag v-if="slotProps.data.isCatalogImportedSkills" @@ -595,10 +591,9 @@ const isLoading = computed(() => { <span><i class="fas fa-tag"></i> {{ tag.tagValue }}</span> </Tag> </div> - </div> - <div class="flex-none"> - <div class="flex"> + <div class="flex align-items-start justify-content-end"> + <div class="flex flex-nowrap"> <ButtonGroup v-if="!projConfig.isReadOnlyProj" class="mt-2 ml-1"> <SkillsButton :id="`editSkillButton_${slotProps.data.skillId}`" diff --git a/dashboard-prime/src/components/skills/selfReport/ShowMore.vue b/dashboard-prime/src/components/skills/selfReport/ShowMore.vue index 1380f37405..193e5eb556 100644 --- a/dashboard-prime/src/components/skills/selfReport/ShowMore.vue +++ b/dashboard-prime/src/components/skills/selfReport/ShowMore.vue @@ -57,7 +57,7 @@ const toDisplay = computed(() => { </script> <template> - <div data-cy="showMoreText" class="text-break" :class="{'d-inline-block' : isInline}"> + <div data-cy="showMoreText" class="text-break" :class="{'inline-block' : isInline}"> <span> <span v-if="containsHtml" v-html="toDisplay"></span><span v-else data-cy="smtText">{{toDisplay}}</span> <a v-if="truncate" size="xs" variant="outline-info" diff --git a/e2e-tests/cypress/e2e/skills_spec.js b/e2e-tests/cypress/e2e/skills_spec.js index ba9b442d3c..eb73e0c8ab 100644 --- a/e2e-tests/cypress/e2e/skills_spec.js +++ b/e2e-tests/cypress/e2e/skills_spec.js @@ -1293,7 +1293,7 @@ describe('Skills Tests', () => { cy.get('[data-cy=pageHeaderStat]').eq(1).should('contain.text', '4') }) - it.skip('skills page - long skill names should be truncated', () => { + it('skills page - long skill names should be truncated', () => { const longName = 'Verylongandinterestingskill;Verylongandinterestingskill;Verylongandinterestingskill;Verylongandinterestingskill;Verylongandinterestingskill;' cy.intercept('GET', '/admin/projects/proj1/subjects/subj1/skills', (req) => { req.reply({