Skip to content

Commit 7cc0299

Browse files
authored
Merge pull request frappe#2442 from raizasafeel/courseform-label-input-consistency
fix(ui): batch form layout polish and rich-text instructor bios
2 parents 8760d76 + 8fe95ef commit 7cc0299

11 files changed

Lines changed: 198 additions & 130 deletions

File tree

cypress/e2e/batch_creation.cy.js

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -96,23 +96,22 @@ describe("Batch Creation", () => {
9696
.type(
9797
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
9898
);
99-
/* Instructor */
99+
// Instructors — frappe-ui MultiSelect. Click the trigger button;
100+
// the search input lives in a popover portalled to body, so we
101+
// type it outside the field wrapper.
100102
cy.get("label")
101103
.contains("Instructors")
102104
.parent()
103-
.within(() => {
104-
cy.get("input").click().clear().type(randomEvaluator);
105-
cy.get("input")
106-
.invoke("attr", "aria-controls")
107-
.as("instructor_list_id");
108-
});
109-
cy.get("@instructor_list_id").then((instructor_list_id) => {
110-
cy.get(`[id^=${instructor_list_id}`)
111-
.should("be.visible")
112-
.within(() => {
113-
cy.get("[id^=headlessui-combobox-option-").first().click();
114-
});
115-
});
105+
.find("button")
106+
.first()
107+
.click();
108+
cy.get('[data-slot="content-body"] [data-slot="input"]')
109+
.should("be.visible")
110+
.type(randomEvaluator);
111+
cy.wait(500);
112+
cy.get('[data-slot="content-body"] [role="option"]').first().click();
113+
// Close the popover so it doesn't overlay the Save button.
114+
cy.get("body").type("{esc}");
116115
cy.button("Save").click();
117116
cy.wait(1000);
118117

frontend/src/components/Controls/Link.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
<template #footer>
2121
<div
2222
data-popover-footer-sticky
23-
class="-m-1 border-t border-outline-gray-2 bg-surface-modal p-2"
23+
class="-m-1 border-t border-outline-gray-2 bg-surface-modal p-2 mt-1"
2424
>
2525
<div v-if="creating" class="flex items-center gap-1">
2626
<button

frontend/src/components/Controls/MultiLink.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
<template #footer="{ clearAll }">
5656
<slot name="footer" :close="closePopover">
5757
<div
58-
class="flex items-center justify-between gap-2 border-t border-outline-gray-1 px-2 py-1.5"
58+
class="flex items-center justify-between gap-2 border-t border-outline-gray-1 px-2 py-1.5 mt-1"
5959
>
6060
<Button
6161
variant="ghost"

frontend/src/components/CourseCreatorCard.vue

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
</div>
1919
</div>
2020
</router-link>
21-
<p
22-
v-if="instructors[0].bio"
23-
class="text-p-sm text-ink-gray-7 leading-6 mt-4 line-clamp-3"
24-
>
25-
{{ instructors[0].bio }}
26-
</p>
21+
<div
22+
v-if="hasBio(instructors[0].bio)"
23+
v-html="renderBio(instructors[0].bio)"
24+
class="ProseMirror prose prose-sm max-w-none text-p-sm text-ink-gray-7 leading-6 mt-4 line-clamp-3"
25+
></div>
2726
</template>
2827

2928
<template v-else>
@@ -39,12 +38,11 @@
3938
</div>
4039
</div>
4140
</router-link>
42-
<p
43-
v-if="focused?.bio"
44-
class="text-p-sm text-ink-gray-7 leading-6 mt-4 line-clamp-3"
45-
>
46-
{{ focused.bio }}
47-
</p>
41+
<div
42+
v-if="hasBio(focused?.bio)"
43+
v-html="renderBio(focused?.bio)"
44+
class="ProseMirror prose prose-sm max-w-none text-p-sm text-ink-gray-7 leading-6 mt-4 line-clamp-3"
45+
></div>
4846

4947
<div class="mt-4 pt-4 border-t border-outline-gray-2">
5048
<div
@@ -83,7 +81,9 @@
8381

8482
<script setup lang="ts">
8583
import { computed, ref, watch } from 'vue'
84+
import DOMPurify from 'dompurify'
8685
import UserAvatar from '@/components/UserAvatar.vue'
86+
import { decodeEntities, htmlToText } from '@/utils'
8787
import type { CourseInstructorInfo } from '@/types/api'
8888
8989
const props = defineProps<{
@@ -142,6 +142,30 @@ const headerLabel = computed<string>(() => {
142142
return __('Taught by a team of {0}').format(String(n))
143143
})
144144
145+
function hasBio(bio?: string | null): boolean {
146+
if (!bio) return false
147+
return htmlToText(bio).trim().length > 0 || /<img\b/i.test(bio)
148+
}
149+
150+
function renderBio(bio?: string | null): string {
151+
return DOMPurify.sanitize(decodeEntities(bio || ''), {
152+
ALLOWED_TAGS: [
153+
'b',
154+
'i',
155+
'em',
156+
'strong',
157+
'a',
158+
'p',
159+
'br',
160+
'ul',
161+
'ol',
162+
'li',
163+
'img',
164+
],
165+
ALLOWED_ATTR: ['href', 'target', 'rel', 'src'],
166+
})
167+
}
168+
145169
function profileLink(instructor: CourseInstructorInfo) {
146170
return { name: 'Profile', params: { username: instructor.username } }
147171
}

frontend/src/components/Modals/BatchCourseModal.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
:label="__('Course')"
2121
:required="true"
2222
:filters="{ published: 1 }"
23+
variant="outline"
2324
:onCreate="
2425
(value, close) => {
2526
close()
@@ -34,6 +35,7 @@
3435
doctype="Course Evaluator"
3536
v-model="evaluator"
3637
:label="__('Evaluator')"
38+
variant="outline"
3739
:onCreate="(value, close) => openSettings('Evaluators', close)"
3840
class="mt-4"
3941
/>

0 commit comments

Comments
 (0)