Skip to content

Commit 52fb5e2

Browse files
authored
Merge pull request #1915 from frappe/develop
chore: merge 'develop' into 'main'
2 parents 3ebff21 + 2596b85 commit 52fb5e2

60 files changed

Lines changed: 1852 additions & 2735 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

frontend/components.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ declare module 'vue' {
4242
CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default']
4343
CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default']
4444
ColorSwatches: typeof import('./src/components/Controls/ColorSwatches.vue')['default']
45+
CommandPalette: typeof import('./src/components/CommandPalette/CommandPalette.vue')['default']
46+
CommandPaletteGroup: typeof import('./src/components/CommandPalette/CommandPaletteGroup.vue')['default']
4547
Configuration: typeof import('./src/components/Sidebar/Configuration.vue')['default']
4648
ContactUsEmail: typeof import('./src/components/ContactUsEmail.vue')['default']
4749
CouponDetails: typeof import('./src/components/Settings/Coupons/CouponDetails.vue')['default']
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
<template>
2+
<Dialog v-model="show" :options="{ size: '2xl' }">
3+
<template #body>
4+
<div class="text-base">
5+
<div class="flex items-center space-x-2 pl-4.5 border-b">
6+
<Search class="size-4 text-ink-gray-4" />
7+
<input
8+
ref="inputRef"
9+
type="text"
10+
placeholder="Search"
11+
class="w-full border-none bg-transparent py-3 !pl-2 pr-4.5 text-base text-ink-gray-7 placeholder-ink-gray-4 focus:ring-0"
12+
@input="onInput"
13+
v-model="query"
14+
autocomplete="off"
15+
/>
16+
</div>
17+
18+
<div class="max-h-96 overflow-auto mb-2">
19+
<div v-if="query.length" class="mt-5 space-y-5">
20+
<CommandPaletteGroup
21+
:list="searchResults"
22+
@navigateTo="navigateTo"
23+
/>
24+
</div>
25+
26+
<div v-else class="mt-5 space-y-5">
27+
<CommandPaletteGroup
28+
:list="jumpToOptions"
29+
@navigateTo="navigateTo"
30+
/>
31+
</div>
32+
</div>
33+
34+
<div
35+
class="flex items-center space-x-5 w-full border-t py-2 text-sm text-ink-gray-7 px-4.5"
36+
>
37+
<div class="flex items-center space-x-2">
38+
<MoveUp
39+
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
40+
/>
41+
<MoveDown
42+
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
43+
/>
44+
<span>
45+
{{ __('to navigate') }}
46+
</span>
47+
</div>
48+
<div class="flex items-center space-x-2">
49+
<CornerDownLeft
50+
class="size-5 stroke-1.5 bg-surface-gray-2 p-1 rounded-sm"
51+
/>
52+
<span>
53+
{{ __('to select') }}
54+
</span>
55+
</div>
56+
<div class="flex items-center space-x-2">
57+
<span class="bg-surface-gray-2 p-1 rounded-sm"> esc </span>
58+
<span>
59+
{{ __('to close') }}
60+
</span>
61+
</div>
62+
</div>
63+
</div>
64+
</template>
65+
</Dialog>
66+
</template>
67+
<script setup lang="ts">
68+
import { createResource, debounce, Dialog } from 'frappe-ui'
69+
import { nextTick, onMounted, ref, watch } from 'vue'
70+
import { useRouter } from 'vue-router'
71+
import {
72+
BookOpen,
73+
Briefcase,
74+
CornerDownLeft,
75+
FileSearch,
76+
MoveUp,
77+
MoveDown,
78+
Search,
79+
Users,
80+
} from 'lucide-vue-next'
81+
import CommandPaletteGroup from './CommandPaletteGroup.vue'
82+
83+
const show = defineModel<boolean>({ required: true, default: false })
84+
const router = useRouter()
85+
const query = ref<string>('')
86+
const searchResults = ref<Array<any>>([])
87+
88+
const search = createResource({
89+
url: 'lms.command_palette.search_sqlite',
90+
makeParams: () => ({
91+
query: query.value,
92+
}),
93+
onSuccess() {
94+
generateSearchResults()
95+
},
96+
})
97+
98+
const debouncedSearch = debounce(() => {
99+
if (query.value.length > 2) {
100+
search.reload()
101+
}
102+
}, 500)
103+
104+
const onInput = () => {
105+
debouncedSearch()
106+
}
107+
108+
const generateSearchResults = () => {
109+
search.data?.forEach((type: any) => {
110+
let result: { title: string; items: any[] } = { title: '', items: [] }
111+
result.title = type.title
112+
type.items.forEach((item: any) => {
113+
let paramName = item.doctype === 'LMS Course' ? 'courseName' : 'batchName'
114+
item.route = {
115+
name: item.doctype === 'LMS Course' ? 'CourseDetail' : 'BatchDetail',
116+
params: {
117+
[paramName]: item.name,
118+
},
119+
}
120+
item.isActive = false
121+
})
122+
result.items = type.items
123+
searchResults.value.push(result)
124+
})
125+
}
126+
127+
const appendSearchPage = () => {
128+
let searchPage: { title: string; items: Array<any> } = {
129+
title: '',
130+
items: [],
131+
}
132+
searchPage.title = __('Jump to')
133+
searchPage.items = [
134+
{
135+
title: __('Search for ') + `"${query.value}"`,
136+
route: {
137+
name: 'Search',
138+
query: {
139+
q: query.value,
140+
},
141+
},
142+
icon: FileSearch,
143+
isActive: true,
144+
},
145+
]
146+
searchResults.value = [searchPage]
147+
}
148+
149+
watch(
150+
query,
151+
() => {
152+
appendSearchPage()
153+
},
154+
{ immediate: true }
155+
)
156+
157+
watch(show, () => {
158+
if (!show.value) {
159+
query.value = ''
160+
searchResults.value = []
161+
}
162+
})
163+
164+
onMounted(() => {
165+
addKeyboardShortcuts()
166+
})
167+
168+
const addKeyboardShortcuts = () => {
169+
window.addEventListener('keydown', (e: KeyboardEvent) => {
170+
if (e.key === 'ArrowUp' && show.value) {
171+
e.preventDefault()
172+
shortcutForArrowKey(-1)
173+
} else if (e.key === 'ArrowDown' && show.value) {
174+
shortcutForArrowKey(1)
175+
} else if (e.key === 'Enter' && show.value) {
176+
shortcutForEnter()
177+
} else if (e.key === 'Escape' && show.value) {
178+
show.value = false
179+
}
180+
})
181+
}
182+
183+
const shortcutForArrowKey = (direction: number) => {
184+
let currentList = query.value.length
185+
? searchResults.value
186+
: jumpToOptions.value
187+
let allItems = currentList.flatMap((result: any) => result.items)
188+
let indexOfActive = allItems.findIndex((option: any) => option.isActive)
189+
let newIndex = indexOfActive + direction
190+
if (newIndex < 0) newIndex = allItems.length - 1
191+
if (newIndex >= allItems.length) newIndex = 0
192+
allItems[indexOfActive].isActive = false
193+
allItems[newIndex].isActive = true
194+
nextTick(scrollActiveItemIntoView)
195+
}
196+
197+
const scrollActiveItemIntoView = () => {
198+
const activeItem = document.querySelector(
199+
'.hover\\:bg-surface-gray-2.bg-surface-gray-2'
200+
) as HTMLElement
201+
if (activeItem) {
202+
activeItem.scrollIntoView({ block: 'nearest' })
203+
}
204+
}
205+
206+
const shortcutForEnter = () => {
207+
let currentList = query.value.length
208+
? searchResults.value
209+
: jumpToOptions.value
210+
let allItems = currentList.flatMap((result: any) => result.items)
211+
let activeOption = allItems.find((option) => option.isActive)
212+
if (activeOption) {
213+
navigateTo(activeOption.route)
214+
}
215+
}
216+
217+
const navigateTo = (route: {
218+
name: string
219+
params?: Record<string, any>
220+
query?: Record<string, any>
221+
}) => {
222+
show.value = false
223+
query.value = ''
224+
router.replace({ name: route.name, params: route.params, query: route.query })
225+
}
226+
227+
const jumpToOptions = ref([
228+
{
229+
title: __('Jump to'),
230+
items: [
231+
{
232+
title: 'Advanced Search',
233+
icon: Search,
234+
route: {
235+
name: 'Search',
236+
},
237+
isActive: true,
238+
},
239+
{
240+
title: 'Courses',
241+
icon: BookOpen,
242+
route: {
243+
name: 'Courses',
244+
},
245+
isActive: false,
246+
},
247+
{
248+
title: 'Batches',
249+
icon: Users,
250+
route: {
251+
name: 'Batches',
252+
},
253+
isActive: false,
254+
},
255+
{
256+
title: 'Jobs',
257+
icon: Briefcase,
258+
route: {
259+
name: 'Jobs',
260+
},
261+
isActive: false,
262+
},
263+
],
264+
},
265+
])
266+
</script>
267+
<style>
268+
mark {
269+
background-color: theme('colors.amber.100');
270+
font-weight: 500;
271+
}
272+
</style>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<template>
2+
<div v-for="result in list" class="px-2.5 space-y-2">
3+
<div class="text-ink-gray-5 px-2">
4+
{{ result.title }}
5+
</div>
6+
<div class="">
7+
<div
8+
v-for="item in result.items"
9+
class="flex items-center justify-between p-2 rounded hover:bg-surface-gray-2 cursor-pointer"
10+
:class="{ 'bg-surface-gray-2': item.isActive }"
11+
@click="emit('navigateTo', item.route)"
12+
>
13+
<div class="flex items-center space-x-3">
14+
<component
15+
v-if="item.icon"
16+
:is="item.icon"
17+
class="size-4 stroke-1.5 text-ink-gray-6"
18+
/>
19+
<div v-html="item.title"></div>
20+
</div>
21+
<div v-if="item.modified" class="text-ink-gray-5">
22+
{{ dayjs.unix(item.modified).fromNow(true) }}
23+
</div>
24+
</div>
25+
</div>
26+
</div>
27+
</template>
28+
<script lang="ts" setup>
29+
import { inject } from 'vue'
30+
31+
const dayjs = inject<any>('$dayjs')
32+
const emit = defineEmits(['navigateTo'])
33+
34+
const props = defineProps<{
35+
list: Array<{
36+
title: string
37+
items: Array<{
38+
title: string
39+
icon?: any
40+
isActive?: boolean
41+
modified?: string
42+
}>
43+
}>
44+
}>()
45+
</script>

frontend/src/components/CourseCard.vue

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,7 @@ const getGradientColor = () => {
156156
localStorage.getItem('theme') == 'light' ? 'lightMode' : 'darkMode'
157157
let color = props.course.card_gradient?.toLowerCase() || 'blue'
158158
let colorMap = colors[theme][color]
159-
return `linear-gradient(to top right, black, ${colorMap[400]})`
160-
/* return `bg-gradient-to-br from-${color}-100 via-${color}-200 to-${color}-400` */
161-
/* return `linear-gradient(to bottom right, ${colorMap[100]}, ${colorMap[400]})` */
162-
/* return `radial-gradient(ellipse at 80% 20%, black 20%, ${colorMap[500]} 100%)` */
163-
/* return `radial-gradient(ellipse at 30% 70%, black 50%, ${colorMap[500]} 100%)` */
164-
/* return `radial-gradient(ellipse at 80% 20%, ${colorMap[100]} 0%, ${colorMap[300]} 50%, ${colorMap[500]} 100%)` */
165-
/* return `conic-gradient(from 180deg at 50% 50%, ${colorMap[100]} 0%, ${colorMap[200]} 50%, ${colorMap[400]} 100%)` */
166-
/* return `linear-gradient(135deg, ${colorMap[100]}, ${colorMap[300]}), linear-gradient(120deg, rgba(255,255,255,0.4) 0%, transparent 60%) ` */
167-
/* return `radial-gradient(circle at 20% 30%, ${colorMap[100]} 0%, transparent 40%),
168-
radial-gradient(circle at 80% 40%, ${colorMap[200]} 0%, transparent 50%),
169-
linear-gradient(135deg, ${colorMap[300]} 0%, ${colorMap[400]} 100%);` */
159+
return `linear-gradient(to top right, black, ${colorMap[200]})`
170160
}
171161
</script>
172162
<style>

frontend/src/components/CourseOutline.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@
9595
name: allowEdit ? 'LessonForm' : 'Lesson',
9696
params: {
9797
courseName: courseName,
98-
chapterNumber: lesson.number.split('.')[0],
99-
lessonNumber: lesson.number.split('.')[1],
98+
chapterNumber: lesson.number.split('-')[0],
99+
lessonNumber: lesson.number.split('-')[1],
100100
},
101101
}"
102102
>
@@ -389,8 +389,8 @@ const redirectToChapter = (chapter) => {
389389
390390
const isActiveLesson = (lessonNumber) => {
391391
return (
392-
route.params.chapterNumber == lessonNumber.split('.')[0] &&
393-
route.params.lessonNumber == lessonNumber.split('.')[1]
392+
route.params.chapterNumber == lessonNumber.split('-')[0] &&
393+
route.params.lessonNumber == lessonNumber.split('-')[1]
394394
)
395395
}
396396
</script>

frontend/src/components/Modals/JobApplicationModal.vue

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}"
1818
>
1919
<template #body-content>
20-
<div class="flex flex-col gap-4">
20+
<div class="flex flex-col gap-4 text-base">
2121
<p class="text-ink-gray-9">
2222
{{
2323
__(
@@ -39,6 +39,9 @@
3939
<template v-slot="{ file, progress, uploading, openFileSelector }">
4040
<div class="">
4141
<Button @click="openFileSelector" :loading="uploading">
42+
<template #prefix>
43+
<Upload class="size-4 stroke-1.5" />
44+
</template>
4245
{{
4346
uploading ? `Uploading ${progress}%` : 'Upload your resume'
4447
}}
@@ -66,7 +69,7 @@
6669
</template>
6770
<script setup>
6871
import { Dialog, FileUploader, Button, createResource, toast } from 'frappe-ui'
69-
import { FileText } from 'lucide-vue-next'
72+
import { FileText, Upload } from 'lucide-vue-next'
7073
import { ref, inject } from 'vue'
7174
import { getFileSize } from '@/utils/'
7275

0 commit comments

Comments
 (0)