Merge pull request #1537 from frappe/develop

chore: merge 'develop' into 'main'
This commit is contained in:
Jannat Patel
2025-05-26 15:33:03 +05:30
committed by GitHub
57 changed files with 8114 additions and 8208 deletions

View File

@@ -1,12 +1,15 @@
describe("Course Creation", () => { describe("Course Creation", () => {
it("creates a new course", () => { it("creates a new course", () => {
cy.login(); cy.login();
cy.wait(1000); cy.wait(500);
cy.visit("/lms/courses"); cy.visit("/lms/courses");
// Close onboarding modal
cy.closeOnboardingModal();
// Create a course // Create a course
cy.get("button").contains("New").click(); cy.get("button").contains("New").click();
cy.wait(1000); cy.wait(500);
cy.url().should("include", "/courses/new/edit"); cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course"); cy.get("label").contains("Title").type("Test Course");
@@ -96,7 +99,8 @@ describe("Course Creation", () => {
// View Course // View Course
cy.wait(1000); cy.wait(1000);
cy.visit("/lms"); cy.visit("/lms");
cy.wait(500); cy.closeOnboardingModal();
cy.url().should("include", "/lms/courses"); cy.url().should("include", "/lms/courses");
cy.get(".grid a:first").within(() => { cy.get(".grid a:first").within(() => {
cy.get("div").contains("Test Course"); cy.get("div").contains("Test Course");

View File

@@ -25,6 +25,7 @@
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import "cypress-file-upload"; import "cypress-file-upload";
import "cypress-real-events";
Cypress.Commands.add("login", (email, password) => { Cypress.Commands.add("login", (email, password) => {
if (!email) { if (!email) {
@@ -68,3 +69,11 @@ Cypress.Commands.add("paste", { prevSubject: true }, (subject, text) => {
element.dispatchEvent(event); element.dispatchEvent(event);
}); });
}); });
Cypress.Commands.add("closeOnboardingModal", () => {
cy.wait(500);
cy.get('[class*="z-50"]')
.find('button:has(svg[class*="feather-x"])')
.realClick();
cy.wait(1000);
});

View File

@@ -47,11 +47,14 @@ declare module 'vue' {
Discussions: typeof import('./src/components/Discussions.vue')['default'] Discussions: typeof import('./src/components/Discussions.vue')['default']
EditCoverImage: typeof import('./src/components/Modals/EditCoverImage.vue')['default'] EditCoverImage: typeof import('./src/components/Modals/EditCoverImage.vue')['default']
EditProfile: typeof import('./src/components/Modals/EditProfile.vue')['default'] EditProfile: typeof import('./src/components/Modals/EditProfile.vue')['default']
EmailTemplateModal: typeof import('./src/components/Modals/EmailTemplateModal.vue')['default']
EmailTemplates: typeof import('./src/components/EmailTemplates.vue')['default']
EmptyState: typeof import('./src/components/EmptyState.vue')['default'] EmptyState: typeof import('./src/components/EmptyState.vue')['default']
EvaluationModal: typeof import('./src/components/Modals/EvaluationModal.vue')['default'] EvaluationModal: typeof import('./src/components/Modals/EvaluationModal.vue')['default']
Evaluators: typeof import('./src/components/Evaluators.vue')['default'] Evaluators: typeof import('./src/components/Evaluators.vue')['default']
Event: typeof import('./src/components/Modals/Event.vue')['default'] Event: typeof import('./src/components/Modals/Event.vue')['default']
ExplanationVideos: typeof import('./src/components/Modals/ExplanationVideos.vue')['default'] ExplanationVideos: typeof import('./src/components/Modals/ExplanationVideos.vue')['default']
FeedbackModal: typeof import('./src/components/Modals/FeedbackModal.vue')['default']
FrappeCloudIcon: typeof import('./src/components/Icons/FrappeCloudIcon.vue')['default'] FrappeCloudIcon: typeof import('./src/components/Icons/FrappeCloudIcon.vue')['default']
IconPicker: typeof import('./src/components/Controls/IconPicker.vue')['default'] IconPicker: typeof import('./src/components/Controls/IconPicker.vue')['default']
IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default'] IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default']

View File

@@ -27,7 +27,7 @@
"codemirror-editor-vue3": "^2.8.0", "codemirror-editor-vue3": "^2.8.0",
"dayjs": "^1.11.6", "dayjs": "^1.11.6",
"feather-icons": "^4.28.0", "feather-icons": "^4.28.0",
"frappe-ui": "^0.1.143", "frappe-ui": "^0.1.147",
"highlight.js": "^11.11.1", "highlight.js": "^11.11.1",
"lucide-vue-next": "^0.383.0", "lucide-vue-next": "^0.383.0",
"markdown-it": "^14.0.0", "markdown-it": "^14.0.0",

View File

@@ -9,18 +9,14 @@
<script setup> <script setup>
import { FrappeUIProvider } from 'frappe-ui' import { FrappeUIProvider } from 'frappe-ui'
import { Dialogs } from '@/utils/dialogs' import { Dialogs } from '@/utils/dialogs'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onUnmounted, ref } from 'vue'
import { useScreenSize } from './utils/composables' import { useScreenSize } from './utils/composables'
import DesktopLayout from './components/DesktopLayout.vue' import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue' import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue' import NoSidebarLayout from './components/NoSidebarLayout.vue'
import { stopSession } from '@/telemetry'
import { init as initTelemetry } from '@/telemetry'
import { usersStore } from '@/stores/user'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const screenSize = useScreenSize() const screenSize = useScreenSize()
let { userResource } = usersStore()
const router = useRouter() const router = useRouter()
const noSidebar = ref(false) const noSidebar = ref(false)
@@ -39,13 +35,9 @@ const Layout = computed(() => {
} }
if (screenSize.width < 640) { if (screenSize.width < 640) {
return MobileLayout return MobileLayout
} else {
return DesktopLayout
} }
})
onMounted(async () => { return DesktopLayout
if (userResource.data) await initTelemetry()
}) })
onUnmounted(() => { onUnmounted(() => {

View File

@@ -181,7 +181,6 @@
import UserDropdown from '@/components/UserDropdown.vue' import UserDropdown from '@/components/UserDropdown.vue'
import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue' import CollapseSidebar from '@/components/Icons/CollapseSidebar.vue'
import SidebarLink from '@/components/SidebarLink.vue' import SidebarLink from '@/components/SidebarLink.vue'
import { useStorage } from '@vueuse/core'
import { ref, onMounted, inject, watch, reactive, markRaw, h } from 'vue' import { ref, onMounted, inject, watch, reactive, markRaw, h } from 'vue'
import { getSidebarLinks } from '../utils' import { getSidebarLinks } from '../utils'
import { usersStore } from '@/stores/user' import { usersStore } from '@/stores/user'
@@ -244,6 +243,7 @@ const iconProps = {
onMounted(() => { onMounted(() => {
addNotifications() addNotifications()
setSidebarLinks() setSidebarLinks()
setUpOnboarding()
socket.on('publish_lms_notifications', (data) => { socket.on('publish_lms_notifications', (data) => {
unreadNotifications.reload() unreadNotifications.reload()
}) })
@@ -388,10 +388,6 @@ const deletePage = (link) => {
) )
} }
const getSidebarFromStorage = () => {
return useStorage('sidebar_is_collapsed', false)
}
const toggleSidebar = () => { const toggleSidebar = () => {
sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed sidebarStore.isSidebarCollapsed = !sidebarStore.isSidebarCollapsed
localStorage.setItem( localStorage.setItem(
@@ -438,6 +434,7 @@ const steps = reactive([
title: __('Add your first chapter'), title: __('Add your first chapter'),
icon: markRaw(h(FolderTree, iconProps)), icon: markRaw(h(FolderTree, iconProps)),
completed: false, completed: false,
dependsOn: 'create_first_course',
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let course = await getFirstCourse() let course = await getFirstCourse()
@@ -453,6 +450,7 @@ const steps = reactive([
title: __('Add your first lesson'), title: __('Add your first lesson'),
icon: markRaw(h(FileText, iconProps)), icon: markRaw(h(FileText, iconProps)),
completed: false, completed: false,
dependsOn: 'create_first_chapter',
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let course = await getFirstCourse() let course = await getFirstCourse()
@@ -471,6 +469,7 @@ const steps = reactive([
title: __('Create your first quiz'), title: __('Create your first quiz'),
icon: markRaw(h(CircleHelp, iconProps)), icon: markRaw(h(CircleHelp, iconProps)),
completed: false, completed: false,
dependsOn: 'create_first_course',
onClick: () => { onClick: () => {
minimize.value = true minimize.value = true
router.push({ name: 'Quizzes' }) router.push({ name: 'Quizzes' })
@@ -502,6 +501,7 @@ const steps = reactive([
title: __('Add students to your batch'), title: __('Add students to your batch'),
icon: markRaw(h(UserPlus, iconProps)), icon: markRaw(h(UserPlus, iconProps)),
completed: false, completed: false,
dependsOn: 'create_first_batch',
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let batch = await getFirstBatch() let batch = await getFirstBatch()
@@ -522,6 +522,7 @@ const steps = reactive([
title: __('Add courses to your batch'), title: __('Add courses to your batch'),
icon: markRaw(h(BookText, iconProps)), icon: markRaw(h(BookText, iconProps)),
completed: false, completed: false,
dependsOn: 'create_first_batch',
onClick: async () => { onClick: async () => {
minimize.value = true minimize.value = true
let batch = await getFirstBatch() let batch = await getFirstBatch()

View File

@@ -1,44 +1,49 @@
<template> <template>
<div v-if="user.data?.is_student"> <div v-if="user.data?.is_student">
<div <div>
v-if="feedbackList.data?.length" <div class="leading-5 mb-4">
class="bg-surface-blue-2 text-blue-700 p-2 rounded-md mb-5" <div v-if="readOnly">
> {{ __('Thank you for providing your feedback.') }}
{{ __('Thank you for providing your feedback!') }} <span
</div> @click="showFeedbackForm = !showFeedbackForm"
<div v-else class="flex justify-between items-center mb-5"> class="underline cursor-pointer"
<div class="text-lg font-semibold"> >{{ __('Click here') }}</span
{{ __('Help Us Improve') }} >
{{ __('to view your feedback.') }}
</div>
<div v-else>
{{ __('Help us improve by providing your feedback.') }}
</div>
</div> </div>
<Button @click="submitFeedback()"> <div class="space-y-4" :class="showFeedbackForm ? 'block' : 'hidden'">
{{ __('Submit') }} <div class="space-y-4">
</Button> <Rating
</div> v-for="key in ratingKeys"
<div class="space-y-8"> v-model="feedback[key]"
<div class="flex items-center justify-between"> :label="__(convertToTitleCase(key))"
<Rating :readonly="readOnly"
v-for="key in ratingKeys" />
v-model="feedback[key]" </div>
:label="__(convertToTitleCase(key))" <FormControl
v-model="feedback.feedback"
type="textarea"
:label="__('Feedback')"
:rows="9"
:readonly="readOnly" :readonly="readOnly"
/> />
<Button v-if="!readOnly" @click="submitFeedback">
{{ __('Submit Feedback') }}
</Button>
</div> </div>
<FormControl
v-model="feedback.feedback"
type="textarea"
:label="__('Feedback')"
:rows="7"
:readonly="readOnly"
/>
</div> </div>
</div> </div>
<div v-else-if="feedbackList.data?.length"> <div v-else-if="feedbackList.data?.length">
<div class="text-lg font-semibold mb-5"> <div class="leading-5 text-sm mb-2 mt-5">
{{ __('Average of Feedback Received') }} {{ __('Average Feedback Received') }}
</div> </div>
<div class="flex items-center justify-between mb-10"> <div class="space-y-4">
<Rating <Rating
v-for="key in ratingKeys" v-for="key in ratingKeys"
v-model="average[key]" v-model="average[key]"
@@ -47,81 +52,32 @@
/> />
</div> </div>
<div class="text-lg font-semibold mb-5"> <Button variant="outline" class="mt-5" @click="showAllFeedback = true">
{{ __('All Feedback') }} {{ __('View all feedback') }}
</div> </Button>
<ListView
:columns="feedbackColumns"
:rows="feedbackList.data"
row-key="name"
:options="{
showTooltip: false,
rowHeight: 'h-16',
selectable: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
></ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in feedbackList.data"
class="group cursor-pointer feedback-list"
>
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="text-sm"
>
<template #prefix>
<div v-if="column.key == 'member_name'">
<Avatar
class="flex"
:image="row['member_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div v-if="ratingKeys.includes(column.key)">
<Rating v-model="row[column.key]" :readonly="true" />
</div>
<div v-else class="leading-5">
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
</div> </div>
<div v-else class="text-sm italic text-center text-ink-gray-7 mt-5"> <div v-else class="text-ink-gray-7 mt-5 leading-5">
{{ __('No feedback received yet.') }} {{ __('No feedback received yet.') }}
</div> </div>
<FeedbackModal
v-if="feedbackList.data?.length"
v-model="showAllFeedback"
:feedbackList="feedbackList.data"
/>
</template> </template>
<script setup> <script setup>
import { computed, inject, onMounted, reactive, ref, watch } from 'vue' import { inject, onMounted, reactive, ref, watch } from 'vue'
import { convertToTitleCase } from '@/utils' import { convertToTitleCase } from '@/utils'
import { import { Button, createListResource, FormControl, Rating } from 'frappe-ui'
Avatar, import FeedbackModal from '@/components/Modals/FeedbackModal.vue'
Button,
createListResource,
FormControl,
ListView,
ListHeader,
ListRows,
ListRow,
ListRowItem,
Rating,
} from 'frappe-ui'
const user = inject('$user') const user = inject('$user')
const ratingKeys = ['content', 'instructors', 'value'] const ratingKeys = ['content', 'instructors', 'value']
const readOnly = ref(false) const readOnly = ref(false)
const average = reactive({}) const average = reactive({})
const feedback = reactive({}) const feedback = reactive({})
const showFeedbackForm = ref(true)
const showAllFeedback = ref(false)
const props = defineProps({ const props = defineProps({
batch: { batch: {
@@ -167,6 +123,7 @@ watch(
if (feedbackList.data.length) { if (feedbackList.data.length) {
let data = feedbackList.data let data = feedbackList.data
readOnly.value = true readOnly.value = true
showFeedbackForm.value = false
ratingKeys.forEach((key) => { ratingKeys.forEach((key) => {
average[key] = 0 average[key] = 0
@@ -201,40 +158,11 @@ const submitFeedback = () => {
{ {
onSuccess: () => { onSuccess: () => {
feedbackList.reload() feedbackList.reload()
showFeedbackForm.value = false
}, },
} }
) )
} }
const feedbackColumns = computed(() => {
return [
{
label: 'Member',
key: 'member_name',
width: '10rem',
},
{
label: 'Feedback',
key: 'feedback',
width: '15rem',
},
{
label: 'Content',
key: 'content',
width: '9rem',
},
{
label: 'Instructors',
key: 'instructors',
width: '9rem',
},
{
label: 'Value',
key: 'value',
width: '9rem',
},
]
})
</script> </script>
<style> <style>
.feedback-list > button > div { .feedback-list > button > div {

View File

@@ -6,103 +6,59 @@
</div> </div>
</div> </div>
<div class="grid grid-cols-4 gap-5 mb-8"> <div class="grid grid-cols-4 gap-5 mb-8">
<div <NumberChart
class="flex items-center border py-2 px-3 rounded-md text-ink-gray-7" class="border rounded-md"
> :config="{ title: __('Students'), value: students.data?.length || 0 }"
<div class="p-2 rounded-md bg-surface-gray-2 mr-3"> />
<User class="w-5 h-5 stroke-1.5" />
</div> <NumberChart
<div class="flex items-center space-x-2"> class="border rounded-md"
<span class="font-semibold"> :config="{
{{ students.data?.length }} title: __('Certified'),
</span> value: certificationCount.data || 0,
<span class=""> }"
{{ __('Students') }} />
</span>
</div> <NumberChart
</div> class="border rounded-md"
:config="{
<div title: __('Courses'),
class="flex items-center border py-2 px-3 rounded-md text-ink-gray-7" value: batch.data.courses?.length || 0,
> }"
<div class="p-2 rounded-md bg-surface-gray-2 mr-3"> />
<GraduationCap class="w-5 h-5 stroke-1.5" />
</div> <NumberChart
<div class="flex items-center space-x-2"> class="border rounded-md"
<span class="font-semibold"> :config="{ title: __('Assessments'), value: assessmentCount || 0 }"
{{ certificationCount.data }}
</span>
<span class="">
{{ __('Certified') }}
</span>
</div>
</div>
<div
class="flex items-center border py-2 px-3 rounded-md text-ink-gray-7"
>
<div class="p-2 rounded-md bg-surface-gray-2 mr-3">
<BookOpen class="w-5 h-5 stroke-1.5" />
</div>
<div class="flex items-center space-x-2">
<span class="font-semibold">
{{ batch.data.courses?.length }}
</span>
<span>
{{ __('Courses') }}
</span>
</div>
</div>
<div
class="flex items-center border py-2 px-3 rounded-md text-ink-gray-7"
>
<div class="p-2 rounded-md bg-surface-gray-2 mr-3">
<ShieldCheck class="w-5 h-5 stroke-1.5" />
</div>
<div class="flex items-center space-x-2">
<span class="font-semibold">
{{ assessmentCount }}
</span>
<span>
{{ __('Assessments') }}
</span>
</div>
</div>
</div>
<div v-if="showProgressChart" class="mb-8">
<div class="text-ink-gray-7 font-medium">
{{ __('Progress') }}
</div>
<ApexChart
:options="chartOptions"
:series="chartData"
type="bar"
:height="chartData[0].data.length * 30 + 100"
/> />
<div
class="flex items-center justify-center text-sm text-ink-gray-7 space-x-4"
>
<div class="flex items-center space-x-2">
<div
class="w-3 h-3 rounded-sm"
:style="{ 'background-color': theme.colors.green[600] }"
></div>
<div>
{{ __('Courses') }}
</div>
</div>
<div class="flex items-center space-x-2">
<div
class="w-3 h-3 rounded-sm"
:style="{ 'background-color': theme.colors.blue[600] }"
></div>
<div>
{{ __('Assessments') }}
</div>
</div>
</div>
</div> </div>
<AxisChart
v-if="showProgressChart"
:config="{
data: chartData,
title: __('Batch Summary'),
subtitle: __('Progress of students in courses and assessments'),
xAxis: {
key: 'task',
title: 'Tasks',
type: 'category',
},
yAxis: {
title: __('Number of Students'),
echartOptions: {
minInterval: 1,
},
},
swapXY: true,
series: [
{
name: 'value',
type: 'bar',
},
],
}"
/>
</div> </div>
<div> <div>
@@ -214,6 +170,7 @@
<script setup> <script setup>
import { import {
Avatar, Avatar,
AxisChart,
Button, Button,
createResource, createResource,
FeatherIcon, FeatherIcon,
@@ -224,6 +181,7 @@ import {
ListRows, ListRows,
ListView, ListView,
ListRowItem, ListRowItem,
NumberChart,
toast, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { import {
@@ -245,7 +203,6 @@ const showStudentModal = ref(false)
const showStudentProgressModal = ref(false) const showStudentProgressModal = ref(false)
const selectedStudent = ref(null) const selectedStudent = ref(null)
const chartData = ref(null) const chartData = ref(null)
const chartOptions = ref(null)
const showProgressChart = ref(false) const showProgressChart = ref(false)
const assessmentCount = ref(0) const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode const readOnlyMode = window.read_only_mode
@@ -333,96 +290,49 @@ const removeStudents = (selections, unselectAll) => {
} }
const getChartData = () => { const getChartData = () => {
let categories = {} let tasks = []
let data = []
if (!students.data?.length) return [] students.data.forEach((row) => {
tasks = countAssessments(row, tasks)
Object.keys(students.data[0].courses).forEach((course) => { tasks = countCourses(row, tasks)
categories[course] = {
value: 0,
type: 'course',
label: course,
}
}) })
Object.keys(students.data?.[0].assessments).forEach((assessment) => { tasks.forEach((task) => {
categories[assessment] = { data.push({
value: 0, task: task.label,
type: 'assessment', value: task.value,
label: assessment,
}
})
students.data.forEach((student) => {
Object.keys(student.courses).forEach((course) => {
if (student.courses[course] === 100) {
categories[course].value += 1
}
})
Object.keys(student.assessments).forEach((assessment) => {
if (student.assessments[assessment].result === 'Pass') {
categories[assessment].value += 1
}
}) })
}) })
return data
chartOptions.value = getChartOptions(categories)
return [
{
name: __('Completed by Students'),
data: Object.values(categories).map((item) => item.value),
},
]
} }
const getChartOptions = (categories) => { const countAssessments = (row, tasks) => {
const courseColor = theme.colors.green[700] Object.keys(row.assessments).forEach((assessment) => {
const assessmentColor = theme.colors.blue[700] if (row.assessments[assessment].result === 'Pass') {
const maxY = tasks.filter((task) => task.label === assessment).length
students.data?.length % 5 ? tasks.filter((task) => task.label === assessment)[0].value++
? students.data?.length + (5 - (students.data?.length % 5)) : tasks.push({
: students.data?.length value: 1,
label: assessment,
})
}
})
return tasks
}
return { const countCourses = (row, tasks) => {
chart: { Object.keys(row.courses).forEach((course) => {
type: 'bar', if (row.courses[course] === 100) {
toolbar: { tasks.filter((task) => task.label === course).length
show: false, ? tasks.filter((task) => task.label === course)[0].value++
}, : tasks.push({
}, value: 1,
plotOptions: { label: course,
bar: { })
distributed: true, }
borderRadius: 3, })
borderRadiusApplication: 'end', return tasks
horizontal: true,
barHeight: '40%',
},
},
colors: Object.values(categories).map((item) =>
item.type === 'course' ? courseColor : assessmentColor
),
xaxis: {
categories: Object.values(categories).map((item) => item.label),
labels: {
style: {
fontSize: '10px',
},
rotate: 0,
formatter: function (value) {
return value.length > 30 ? `${value.substring(0, 30)}...` : value
},
},
},
yaxis: {
max: maxY,
min: 0,
stepSize: 10,
tickAmount: maxY / 5,
/* reversed: true */
},
}
} }
watch(students, () => { watch(students, () => {
@@ -442,8 +352,3 @@ const certificationCount = createResource({
auto: true, auto: true,
}) })
</script> </script>
<style>
.apexcharts-legend {
display: none !important;
}
</style>

View File

@@ -18,11 +18,11 @@
</div> </div>
<div class="overflow-y-auto"> <div class="overflow-y-auto">
<SettingFields :fields="fields" :data="data.data" /> <SettingFields :fields="fields" :data="data.data" />
<div class="flex flex-row-reverse mt-auto"> </div>
<Button variant="solid" :loading="saveSettings.loading" @click="update"> <div class="flex flex-row-reverse mt-auto">
{{ __('Update') }} <Button variant="solid" :loading="saveSettings.loading" @click="update">
</Button> {{ __('Update') }}
</div> </Button>
</div> </div>
</div> </div>
</template> </template>

View File

@@ -1,16 +1,32 @@
<template> <template>
<div class="flex flex-col min-h-0"> <div class="flex flex-col min-h-0 text-base">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between mb-5">
<div class="text-xl font-semibold mb-5 text-ink-gray-9"> <div class="flex flex-col space-y-2">
{{ label }} <div class="text-xl font-semibold text-ink-gray-9">
{{ label }}
</div>
<div class="text-xs text-ink-gray-5">
{{ __(description) }}
</div>
</div>
<div class="flex items-center space-x-5">
<div
class="flex items-center space-x-1 text-ink-amber-3 border border-outline-amber-1 bg-surface-amber-1 rounded-lg px-2 py-1"
v-if="saving"
>
<LoadingIndicator class="size-2" />
<span class="text-xs">
{{ __('saving...') }}
</span>
</div>
<Button @click="() => showCategoryForm()">
<template #prefix>
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
<X v-else class="h-3 w-3 stroke-1.5" />
</template>
{{ showForm ? __('Close') : __('New') }}
</Button>
</div> </div>
<Button @click="() => showCategoryForm()">
<template #prefix>
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
<X v-else class="h-3 w-3 stroke-1.5" />
</template>
{{ showForm ? __('Close') : __('New') }}
</Button>
</div> </div>
<div <div
@@ -29,13 +45,39 @@
</div> </div>
<div class="overflow-y-scroll"> <div class="overflow-y-scroll">
<div class="text-base space-y-2"> <div class="divide-y space-y-2">
<FormControl <div
:value="cat.category" v-for="(cat, index) in categories.data"
type="text" :key="cat.name"
v-for="cat in categories.data" class="pt-2"
@change.stop="(e) => update(cat.name, e.target.value)" >
/> <div
v-if="editing?.name !== cat.name"
class="flex items-center justify-between group text-sm"
>
<div @dblclick="allowEdit(cat, index)">
{{ cat.category }}
</div>
<Button
variant="ghost"
theme="red"
class="invisible group-hover:visible"
@click="deleteCategory(cat.name)"
>
<template #icon>
<Trash2 class="size-4 stroke-1.5 text-ink-red-4" />
</template>
</Button>
</div>
<FormControl
v-else
:ref="(el) => (editInputRef[index] = el)"
v-model="editedValue"
type="text"
class="w-full"
@keyup.enter="saveChanges(cat.name, editedValue)"
/>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -44,16 +86,22 @@
import { import {
Button, Button,
FormControl, FormControl,
LoadingIndicator,
createListResource, createListResource,
createResource, createResource,
debounce, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { Plus, X } from 'lucide-vue-next' import { Plus, Trash2, X } from 'lucide-vue-next'
import { ref } from 'vue' import { ref } from 'vue'
import { cleanError } from '@/utils'
const showForm = ref(false) const showForm = ref(false)
const category = ref(null) const category = ref(null)
const categoryInput = ref(null) const categoryInput = ref(null)
const saving = ref(false)
const editing = ref(null)
const editedValue = ref('')
const editInputRef = ref([])
const props = defineProps({ const props = defineProps({
label: { label: {
@@ -72,25 +120,20 @@ const categories = createListResource({
auto: true, auto: true,
}) })
const newCategory = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Category',
category: category.value,
},
}
},
})
const addCategory = () => { const addCategory = () => {
newCategory.submit( categories.insert.submit(
{}, {
category: category.value,
},
{ {
onSuccess(data) { onSuccess(data) {
categories.reload() categories.reload()
category.value = null category.value = null
showForm.value = false
toast.success(__('Category added successfully'))
},
onError(err) {
toast.error(__(cleanError(err.messages[0]) || 'Unable to add category'))
}, },
} }
) )
@@ -115,6 +158,7 @@ const updateCategory = createResource({
}) })
const update = (name, value) => { const update = (name, value) => {
saving.value = true
updateCategory.submit( updateCategory.submit(
{ {
name: name, name: name,
@@ -122,9 +166,51 @@ const update = (name, value) => {
}, },
{ {
onSuccess() { onSuccess() {
saving.value = false
categories.reload() categories.reload()
editing.value = null
editedValue.value = ''
toast.success(__('Category updated successfully'))
},
onError(err) {
saving.value = false
editing.value = null
editedValue.value = ''
toast.error(
__(cleanError(err.messages[0]) || 'Unable to update category')
)
}, },
} }
) )
} }
const deleteCategory = (name) => {
saving.value = true
categories.delete.submit(name, {
onSuccess() {
saving.value = false
categories.reload()
toast.success(__('Category deleted successfully'))
},
onError(err) {
saving.value = false
toast.error(
__(cleanError(err.messages[0]) || 'Unable to delete category')
)
},
})
}
const saveChanges = (name, value) => {
saving.value = true
update(name, value)
}
const allowEdit = (cat, index) => {
editing.value = cat
editedValue.value = cat.category
setTimeout(() => {
editInputRef.value[index].$el.querySelector('input').focus()
}, 0)
}
</script> </script>

View File

@@ -116,7 +116,7 @@
v-if="parseInt(course.data.rating) > 0" v-if="parseInt(course.data.rating) > 0"
class="flex items-center text-ink-gray-9" class="flex items-center text-ink-gray-9"
> >
<Star class="h-4 w-4 stroke-1.5 fill-orange-500 text-gray-50" /> <Star class="size-4 stroke-1.5 fill-yellow-500 text-transparent" />
<span class="ml-2"> <span class="ml-2">
{{ course.data.rating }} {{ __('Rating') }} {{ course.data.rating }} {{ __('Rating') }}
</span> </span>

View File

@@ -35,14 +35,14 @@
<span class="text-ink-gray-7"> <span class="text-ink-gray-7">
{{ review.creation }} {{ review.creation }}
</span> </span>
<div class="flex mt-2"> <div class="flex mt-2 space-x-1">
<Star <Star
v-for="index in 5" v-for="index in 5"
class="h-5 w-5 text-ink-gray-1 rounded-sm mr-2" class="size-4 text-transparent rounded-sm"
:class=" :class="
index <= Math.ceil(review.rating) index <= Math.ceil(review.rating)
? 'fill-orange-500' ? 'fill-yellow-500'
: 'fill-gray-600' : 'fill-gray-300'
" "
/> />
</div> </div>

View File

@@ -0,0 +1,160 @@
<template>
<div class="flex flex-col min-h-0 text-base">
<div class="flex items-center justify-between mb-5">
<div class="flex flex-col space-y-2">
<div class="text-xl font-semibold text-ink-gray-9">
{{ label }}
</div>
<div class="text-xs text-ink-gray-5">
{{ __(description) }}
</div>
</div>
<div class="flex items-center space-x-5">
<Button @click="openTemplateForm('new')">
<template #prefix>
<Plus class="h-3 w-3 stroke-1.5" />
</template>
{{ __('New') }}
</Button>
</div>
</div>
<div v-if="emailTemplates.data?.length" class="overflow-y-scroll">
<ListView
:columns="columns"
:rows="emailTemplates.data"
row-key="name"
:options="{
showTooltip: false,
onRowClick: (row) => {
openTemplateForm(row.name)
},
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
>
<ListHeaderItem :item="item" v-for="item in columns">
<template #prefix="{ item }">
<component
v-if="item.icon"
:is="item.icon"
class="h-4 w-4 stroke-1.5 ml-4"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow :row="row" v-for="row in emailTemplates.data">
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<div class="leading-5 text-sm">
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
<ListSelectBanner>
<template #actions="{ unselectAll, selections }">
<div class="flex gap-2">
<Button
variant="ghost"
@click="removeTemplate(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
</div>
<EmailTemplateModal
v-model="showForm"
v-model:emailTemplates="emailTemplates"
:templateID="selectedTemplate"
/>
</template>
<script setup lang="ts">
import {
Button,
call,
createListResource,
ListView,
ListHeader,
ListHeaderItem,
ListSelectBanner,
ListRows,
ListRow,
ListRowItem,
toast,
} from 'frappe-ui'
import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import EmailTemplateModal from '@/components/Modals/EmailTemplateModal.vue'
const props = defineProps({
label: {
type: String,
required: true,
},
description: {
type: String,
default: '',
},
})
const showForm = ref(false)
const readOnlyMode = window.read_only_mode
const selectedTemplate = ref(null)
const emailTemplates = createListResource({
doctype: 'Email Template',
fields: ['name', 'subject', 'use_html', 'response', 'response_html'],
auto: true,
orderBy: 'modified desc',
cache: 'email-templates',
})
const removeTemplate = (selections, unselectAll) => {
call('lms.lms.api.delete_documents', {
doctype: 'Email Template',
documents: Array.from(selections),
})
.then(() => {
emailTemplates.reload()
toast.success(__('Email Templates deleted successfully'))
unselectAll()
})
.catch((err) => {
toast.error(
cleanError(err.messages[0]) || __('Error deleting email templates')
)
})
}
const openTemplateForm = (templateID) => {
if (readOnlyMode) {
return
}
selectedTemplate.value = templateID
showForm.value = true
}
const columns = computed(() => {
return [
{
label: 'Name',
key: 'name',
width: '20rem',
},
{
label: 'Subject',
key: 'subject',
width: '25rem',
},
]
})
</script>

View File

@@ -1,5 +1,5 @@
<template> <template>
<div> <div class="flex min-h-0 flex-col text-base">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div> <div>
<div class="text-xl font-semibold mb-1 text-ink-gray-9"> <div class="text-xl font-semibold mb-1 text-ink-gray-9">
@@ -39,25 +39,27 @@
</Button> </Button>
</div> </div>
<div class="divide-y"> <div class="overflow-y-scroll">
<div <div class="divide-y">
v-for="evaluator in evaluators.data" <div
@click="openProfile(evaluator.username)" v-for="evaluator in evaluators.data"
class="cursor-pointer" @click="openProfile(evaluator.username)"
> class="cursor-pointer"
<div class="flex items-center justify-between py-3"> >
<div class="flex items-center space-x-3"> <div class="flex items-center justify-between py-3">
<Avatar <div class="flex items-center space-x-3">
:image="evaluator.user_image" <Avatar
:label="evaluator.full_name" :image="evaluator.user_image"
size="lg" :label="evaluator.full_name"
/> size="lg"
<div> />
<div class="text-base font-semibold text-ink-gray-9"> <div>
{{ evaluator.full_name }} <div class="text-base font-semibold text-ink-gray-9">
</div> {{ evaluator.full_name }}
<div class="text-xs text-ink-gray-5"> </div>
{{ evaluator.evaluator }} <div class="text-xs text-ink-gray-5">
{{ evaluator.evaluator }}
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,60 +1,55 @@
<template> <template>
<div class="flex h-full flex-col"> <div class="flex h-full flex-col relative">
<div class="h-full pb-10" id="scrollContainer"> <div class="h-full pb-10" id="scrollContainer">
<slot /> <slot />
</div> </div>
<div
v-if="sidebarSettings.data" <div class="relative z-20">
class="fixed flex items-center justify-around border-t border-outline-gray-2 bottom-0 z-10 w-full bg-surface-white standalone:pb-4" <!-- Dropdown menu -->
:style="{ <div
gridTemplateColumns: `repeat(${ class="fixed bottom-16 right-2 w-[80%] rounded-md bg-surface-white text-base p-5 space-y-4 shadow-md"
sidebarLinks.length + 1 v-if="showMenu"
}, minmax(0, 1fr))`, ref="menu"
}"
>
<button
v-for="tab in sidebarLinks"
:key="tab.label"
:class="isVisible(tab) ? 'block' : 'hidden'"
class="flex flex-col items-center justify-center py-3 transition active:scale-95"
@click="handleClick(tab)"
> >
<component <div
:is="icons[tab.icon]" v-for="link in otherLinks"
class="h-6 w-6 stroke-1.5" :key="link.label"
:class="[isActive(tab) ? 'text-ink-gray-9' : 'text-ink-gray-5']" class="flex items-center space-x-2 cursor-pointer"
/> @click="handleClick(link)"
</button> >
<Popover <component
trigger="hover" :is="icons[link.icon]"
popoverClass="bottom-28 mx-2" class="h-4 w-4 stroke-1.5 text-ink-gray-5"
placement="top-start" />
<div>{{ link.label }}</div>
</div>
</div>
<!-- Fixed menu -->
<div
v-if="sidebarSettings.data"
class="fixed bottom-0 left-0 w-full flex items-center justify-around border-t border-outline-gray-2 bg-surface-white standalone:pb-4 z-10"
> >
<template #target> <button
v-for="tab in sidebarLinks"
:key="tab.label"
:class="isVisible(tab) ? 'block' : 'hidden'"
class="flex flex-col items-center justify-center py-3 transition active:scale-95"
@click="handleClick(tab)"
>
<component
:is="icons[tab.icon]"
class="h-6 w-6 stroke-1.5"
:class="[isActive(tab) ? 'text-ink-gray-9' : 'text-ink-gray-5']"
/>
</button>
<button @click="toggleMenu">
<component <component
:is="icons['List']" :is="icons['List']"
class="h-6 w-6 stroke-1.5 text-ink-gray-5" class="h-6 w-6 stroke-1.5 text-ink-gray-5"
/> />
</template> </button>
<template #body-main> </div>
<div class="text-base p-5 space-y-4">
<div
v-for="link in otherLinks"
:key="link.label"
class="flex items-center space-x-2"
@click="handleClick(link)"
>
<component
:is="icons[link.icon]"
class="h-4 w-4 stroke-1.5 text-ink-gray-5"
/>
<div>
{{ link.label }}
</div>
</div>
</div>
</template>
</Popover>
</div> </div>
</div> </div>
</template> </template>
@@ -64,7 +59,6 @@ import { useRouter } from 'vue-router'
import { watch, ref, onMounted } from 'vue' import { watch, ref, onMounted } from 'vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { usersStore } from '@/stores/user' import { usersStore } from '@/stores/user'
import { Popover } from 'frappe-ui'
import * as icons from 'lucide-vue-next' import * as icons from 'lucide-vue-next'
const { logout, user, sidebarSettings } = sessionStore() const { logout, user, sidebarSettings } = sessionStore()
@@ -73,26 +67,47 @@ const router = useRouter()
let { userResource } = usersStore() let { userResource } = usersStore()
const sidebarLinks = ref(getSidebarLinks()) const sidebarLinks = ref(getSidebarLinks())
const otherLinks = ref([]) const otherLinks = ref([])
const showMenu = ref(false)
const menu = ref(null)
onMounted(() => { onMounted(() => {
sidebarSettings.reload( sidebarSettings.reload(
{}, {},
{ {
onSuccess(data) { onSuccess(data) {
Object.keys(data).forEach((key) => { filterLinksToShow(data)
if (!parseInt(data[key])) {
sidebarLinks.value = sidebarLinks.value.filter(
(link) => link.label.toLowerCase().split(' ').join('_') !== key
)
}
})
addOtherLinks() addOtherLinks()
}, },
} }
) )
}) })
const handleOutsideClick = (e) => {
if (menu.value && !menu.value.contains(e.target)) {
showMenu.value = false
}
}
watch(showMenu, (val) => {
if (val) {
setTimeout(() => {
document.addEventListener('click', handleOutsideClick)
}, 0)
} else {
document.removeEventListener('click', handleOutsideClick)
}
})
const filterLinksToShow = (data) => {
Object.keys(data).forEach((key) => {
if (!parseInt(data[key])) {
sidebarLinks.value = sidebarLinks.value.filter(
(link) => link.label.toLowerCase().split(' ').join('_') !== key
)
}
})
}
const addOtherLinks = () => { const addOtherLinks = () => {
if (user) { if (user) {
otherLinks.value.push({ otherLinks.value.push({
@@ -122,6 +137,7 @@ watch(userResource, () => {
(userResource.data.is_moderator || userResource.data.is_instructor) (userResource.data.is_moderator || userResource.data.is_instructor)
) { ) {
addQuizzes() addQuizzes()
addAssignments()
} }
}) })
@@ -133,6 +149,14 @@ const addQuizzes = () => {
}) })
} }
const addAssignments = () => {
otherLinks.value.push({
label: 'Assignments',
icon: 'Pencil',
to: 'Assignments',
})
}
let isActive = (tab) => { let isActive = (tab) => {
return tab.activeFor?.includes(router.currentRoute.value.name) return tab.activeFor?.includes(router.currentRoute.value.name)
} }
@@ -158,4 +182,8 @@ const isVisible = (tab) => {
else if (tab.label == 'Log out') return isLoggedIn else if (tab.label == 'Log out') return isLoggedIn
else return true else return true
} }
const toggleMenu = () => {
showMenu.value = !showMenu.value
}
</script> </script>

View File

@@ -6,7 +6,7 @@
}" }"
> >
<template #body> <template #body>
<div class="p-5 text-base max-h-[75vh] overflow-y-auto"> <div class="p-5 text-base">
<div class="text-lg text-ink-gray-9 font-semibold mb-5"> <div class="text-lg text-ink-gray-9 font-semibold mb-5">
{{ {{
assignmentID === 'new' assignmentID === 'new'
@@ -14,7 +14,7 @@
: __('Edit Assignment') : __('Edit Assignment')
}} }}
</div> </div>
<div class="space-y-4"> <div class="space-y-4 max-h-[75vh] overflow-y-auto">
<FormControl <FormControl
v-model="assignment.title" v-model="assignment.title"
:label="__('Title')" :label="__('Title')"

View File

@@ -0,0 +1,192 @@
<template>
<Dialog
v-model="show"
:options="{
title:
templateID == 'new'
? __('New Email Template')
: __('Edit Email Template'),
size: 'lg',
actions: [
{
label: __('Save'),
variant: 'solid',
onClick: ({ close }) => {
saveTemplate(close)
},
},
],
}"
>
<template #body-content>
<div class="space-y-4">
<FormControl
:label="__('Name')"
v-model="template.name"
type="text"
:required="true"
:placeholder="__('Batch Enrollment Confirmation')"
/>
<FormControl
:label="__('Subject')"
v-model="template.subject"
type="text"
:required="true"
:placeholder="__('Your enrollment in {{ batch_name }} is confirmed')"
/>
<FormControl
:label="__('Use HTML')"
v-model="template.use_html"
type="checkbox"
/>
<FormControl
v-if="template.use_html"
:label="__('Content')"
v-model="template.response_html"
type="textarea"
:required="true"
:rows="10"
:placeholder="
__(
'<p>Dear {{ member_name }},</p>\n\n<p>You have been enrolled in our upcoming batch {{ batch_name }}.</p>\n\n<p>Thanks,</p>\n<p>Frappe Learning</p>'
)
"
/>
<div v-else>
<div class="text-xs text-ink-gray-5 mb-2">
{{ __('Content') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:content="template.response"
@change="(val) => (template.response = val)"
:editable="true"
:fixedMenu="true"
:placeholder="
__(
'Dear {{ member_name }},\n\nYou have been enrolled in our upcoming batch {{ batch_name }}.\n\nThanks,\nFrappe Learning'
)
"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem] max-h-[18rem] overflow-y-auto"
/>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { call, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { reactive, watch } from 'vue'
import { cleanError } from '@/utils'
const props = defineProps({
templateID: {
type: String,
default: 'new',
},
})
const show = defineModel()
const emailTemplates = defineModel('emailTemplates')
const template = reactive({
name: '',
subject: '',
use_html: false,
response: '',
response_html: '',
})
const saveTemplate = (close) => {
if (props.templateID == 'new') {
createNewTemplate(close)
} else {
updateTemplate(close)
}
}
const createNewTemplate = (close) => {
emailTemplates.value.insert.submit(
{
__newname: template.name,
...template,
},
{
onSuccess() {
emailTemplates.value.reload()
refreshForm(close)
toast.success(__('Email Template created successfully'))
},
onError(err) {
refreshForm(close)
toast.error(
cleanError(err.messages[0]) || __('Error creating email template')
)
},
}
)
}
const updateTemplate = async (close) => {
if (props.templateID != template.name) {
await renameDoc()
}
setValue(close)
}
const setValue = (close) => {
emailTemplates.value.setValue.submit(
{
...template,
name: template.name,
},
{
onSuccess() {
emailTemplates.value.reload()
refreshForm(close)
toast.success(__('Email Template updated successfully'))
},
onError(err) {
refreshForm(close)
toast.error(
cleanError(err.messages[0]) || __('Error updating email template')
)
},
}
)
}
const renameDoc = async () => {
await call('frappe.client.rename_doc', {
doctype: 'Email Template',
old_name: props.templateID,
new_name: template.name,
})
}
watch(
() => props.templateID,
(val) => {
if (val !== 'new') {
emailTemplates.value?.data.forEach((row) => {
if (row.name === val) {
template.name = row.name
template.subject = row.subject
template.use_html = row.use_html
template.response = row.response
template.response_html = row.response_html
}
})
}
},
{ flush: 'post' }
)
const refreshForm = (close) => {
close()
template.name = ''
template.subject = ''
template.use_html = false
template.response = ''
template.response_html = ''
}
</script>

View File

@@ -66,7 +66,7 @@
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, createResource, Select, FormControl } from 'frappe-ui' import { Dialog, createResource, Select, FormControl, toast } from 'frappe-ui'
import { reactive, watch, inject } from 'vue' import { reactive, watch, inject } from 'vue'
import { formatTime } from '@/utils/' import { formatTime } from '@/utils/'
@@ -90,7 +90,7 @@ const props = defineProps({
}, },
}) })
let evaluation = reactive({ const evaluation = reactive({
course: '', course: '',
date: '', date: '',
start_time: '', start_time: '',
@@ -139,7 +139,7 @@ function submitEvaluation(close) {
close() close()
}, },
onError(err) { onError(err) {
let message = err.messages?.[0] || err const message = err.messages?.[0] || err
let unavailabilityMessage let unavailabilityMessage
if (typeof message === 'string') { if (typeof message === 'string') {
@@ -148,13 +148,13 @@ function submitEvaluation(close) {
unavailabilityMessage = false unavailabilityMessage = false
} }
toast.warning(__('Evaluator is unavailable')) toast.warning(__(unavailabilityMessage || 'Evaluator is unavailable'))
}, },
}) })
} }
const getCourses = () => { const getCourses = () => {
let courses = [] const courses = []
for (const course of props.courses) { for (const course of props.courses) {
if (course.evaluator) { if (course.evaluator) {
courses.push({ courses.push({
@@ -164,7 +164,7 @@ const getCourses = () => {
} }
} }
if (courses.length == 1) { if (courses.length === 1) {
evaluation.course = courses[0].value evaluation.course = courses[0].value
} }

View File

@@ -0,0 +1,115 @@
<template>
<Dialog
v-model="show"
:options="{
size: '4xl',
}"
>
<template #body>
<div class="p-5 min-h-[300px]">
<div class="text-lg font-semibold mb-4">
{{ __('Training Feedback') }}
</div>
<ListView
:columns="feedbackColumns"
:rows="feedbackList"
row-key="name"
:options="{
showTooltip: false,
rowHeight: 'h-16',
selectable: false,
}"
>
<ListHeader
class="mb-2 grid items-center space-x-4 rounded bg-surface-gray-2 p-2"
></ListHeader>
<ListRows>
<ListRow
:row="row"
v-for="row in feedbackList"
class="group feedback-list"
>
<template #default="{ column, item }">
<ListRowItem
:item="row[column.key]"
:align="column.align"
class="text-sm"
>
<template #prefix>
<div v-if="column.key == 'member_name'">
<Avatar
class="flex"
:image="row['member_image']"
:label="item"
size="sm"
/>
</div>
</template>
<div v-if="ratingKeys.includes(column.key)">
<Rating v-model="row[column.key]" :readonly="true" />
</div>
<div v-else class="leading-5">
{{ row[column.key] }}
</div>
</ListRowItem>
</template>
</ListRow>
</ListRows>
</ListView>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Dialog,
ListView,
Avatar,
ListHeader,
ListRows,
ListRow,
ListRowItem,
Rating,
} from 'frappe-ui'
import { reactive, computed } from 'vue'
const show = defineModel()
const ratingKeys = ['content', 'instructors', 'value']
const props = defineProps({
feedbackList: {
type: Array,
required: true,
},
})
const feedbackColumns = computed(() => {
return [
{
label: 'Member',
key: 'member_name',
width: '10rem',
},
{
label: 'Feedback',
key: 'feedback',
width: '15rem',
},
{
label: 'Content',
key: 'content',
width: '9rem',
},
{
label: 'Instructors',
key: 'instructors',
width: '9rem',
},
{
label: 'Value',
key: 'value',
width: '9rem',
},
]
})
</script>

View File

@@ -15,26 +15,20 @@
> >
<template #body-content> <template #body-content>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div> <Rating v-model="review.rating" :label="__('Rating')" />
<div class="mb-1.5 text-sm text-ink-gray-5"> <FormControl
{{ __('Rating') }} :label="__('Review')"
</div> type="textarea"
<Rating v-model="review.rating" /> v-model="review.review"
</div> :rows="5"
<div> />
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Review') }}
</div>
<Textarea type="text" size="md" rows="5" v-model="review.review" />
</div>
</div> </div>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup> <script setup>
import { Dialog, Textarea, createResource, toast } from 'frappe-ui' import { Dialog, FormControl, createResource, toast, Rating } from 'frappe-ui'
import { reactive } from 'vue' import { reactive } from 'vue'
import Rating from '@/components/Controls/Rating.vue'
const show = defineModel() const show = defineModel()
const reviews = defineModel('reloadReviews') const reviews = defineModel('reloadReviews')

View File

@@ -51,6 +51,11 @@
:label="activeTab.label" :label="activeTab.label"
:description="activeTab.description" :description="activeTab.description"
/> />
<EmailTemplates
v-else-if="activeTab.label === 'Email Templates'"
:label="activeTab.label"
:description="activeTab.description"
/>
<PaymentSettings <PaymentSettings
v-else-if="activeTab.label === 'Payment Gateway'" v-else-if="activeTab.label === 'Payment Gateway'"
:label="activeTab.label" :label="activeTab.label"
@@ -86,6 +91,7 @@ import SidebarLink from '@/components/SidebarLink.vue'
import Members from '@/components/Members.vue' import Members from '@/components/Members.vue'
import Evaluators from '@/components/Evaluators.vue' import Evaluators from '@/components/Evaluators.vue'
import Categories from '@/components/Categories.vue' import Categories from '@/components/Categories.vue'
import EmailTemplates from '@/components/EmailTemplates.vue'
import BrandSettings from '@/components/BrandSettings.vue' import BrandSettings from '@/components/BrandSettings.vue'
import PaymentSettings from '@/components/PaymentSettings.vue' import PaymentSettings from '@/components/PaymentSettings.vue'
@@ -122,7 +128,7 @@ const tabsStructure = computed(() => {
label: 'Enable Learning Paths', label: 'Enable Learning Paths',
name: 'enable_learning_paths', name: 'enable_learning_paths',
description: description:
'This will enforce students to go through programs assigned to them in the correct order.', 'This will ensure students follow the assigned programs in order.',
type: 'checkbox', type: 'checkbox',
}, },
{ {
@@ -139,11 +145,26 @@ const tabsStructure = computed(() => {
'If enabled, it sends google calendar invite to the student for evaluations.', 'If enabled, it sends google calendar invite to the student for evaluations.',
type: 'checkbox', type: 'checkbox',
}, },
{
type: 'Column Break',
},
{
label: 'Batch Confirmation Template',
name: 'batch_confirmation_template',
doctype: 'Email Template',
type: 'Link',
},
{
label: 'Certification Template',
name: 'certification_template',
doctype: 'Email Template',
type: 'Link',
},
{ {
label: 'Unsplash Access Key', label: 'Unsplash Access Key',
name: 'unsplash_access_key', name: 'unsplash_access_key',
description: description:
'Optional. If this is set, students can pick a cover image from the unsplash library for their profile page. https://unsplash.com/documentation#getting-started.', 'Allows users to pick a profile cover image from Unsplash. https://unsplash.com/documentation#getting-started.',
type: 'password', type: 'password',
}, },
], ],
@@ -160,6 +181,12 @@ const tabsStructure = computed(() => {
description: description:
'Configure the payment gateway and other payment related settings', 'Configure the payment gateway and other payment related settings',
fields: [ fields: [
{
label: 'Default Currency',
name: 'default_currency',
type: 'Link',
doctype: 'Currency',
},
{ {
label: 'Payment Gateway', label: 'Payment Gateway',
name: 'payment_gateway', name: 'payment_gateway',
@@ -167,10 +194,7 @@ const tabsStructure = computed(() => {
doctype: 'Payment Gateway', doctype: 'Payment Gateway',
}, },
{ {
label: 'Default Currency', type: 'Column Break',
name: 'default_currency',
type: 'Link',
doctype: 'Currency',
}, },
{ {
label: 'Apply GST for India', label: 'Apply GST for India',
@@ -207,9 +231,14 @@ const tabsStructure = computed(() => {
}, },
{ {
label: 'Categories', label: 'Categories',
description: 'Manage the members of your learning system', description: 'Double click to edit the category',
icon: 'Network', icon: 'Network',
}, },
{
label: 'Email Templates',
description: 'Manage the email templates for your learning system',
icon: 'MailPlus',
},
], ],
}, },
{ {
@@ -235,28 +264,6 @@ const tabsStructure = computed(() => {
name: 'favicon', name: 'favicon',
type: 'Upload', type: 'Upload',
}, },
{
label: 'Footer Logo',
name: 'footer_logo',
type: 'Upload',
},
{
label: 'Address',
name: 'address',
type: 'textarea',
rows: 2,
},
{
label: 'Footer "Powered By"',
name: 'footer_powered',
type: 'textarea',
rows: 4,
},
{
label: 'Copyright',
name: 'copyright',
type: 'text',
},
], ],
}, },
{ {
@@ -299,24 +306,6 @@ const tabsStructure = computed(() => {
}, },
], ],
}, },
{
label: 'Email Templates',
icon: 'MailPlus',
fields: [
{
label: 'Batch Confirmation Template',
name: 'batch_confirmation_template',
doctype: 'Email Template',
type: 'Link',
},
{
label: 'Certification Template',
name: 'certification_template',
doctype: 'Email Template',
type: 'Link',
},
],
},
{ {
label: 'Signup', label: 'Signup',
icon: 'LogIn', icon: 'LogIn',

View File

@@ -12,13 +12,13 @@
/> --> /> -->
</div> </div>
<div class="overflow-y-scroll"> <div class="overflow-y-scroll">
<div class="flex space-x-4"> <div class="flex flex-col divide-y">
<SettingFields :fields="fields" :data="data.doc" class="w-1/2" /> <SettingFields :fields="fields" :data="data.doc" />
<SettingFields <SettingFields
v-if="paymentGateway.data" v-if="paymentGateway.data"
:fields="paymentGateway.data.fields" :fields="paymentGateway.data.fields"
:data="paymentGateway.data.data" :data="paymentGateway.data.data"
class="w-1/2" class="pt-5 my-0"
/> />
</div> </div>
</div> </div>
@@ -60,9 +60,28 @@ const paymentGateway = createResource({
payment_gateway: props.data.doc.payment_gateway, payment_gateway: props.data.doc.payment_gateway,
} }
}, },
transform(data) {
arrangeFields(data.fields)
return data
},
auto: true, auto: true,
}) })
const arrangeFields = (fields) => {
fields = fields.sort((a, b) => {
if (a.type === 'Upload' && b.type !== 'Upload') {
return 1
} else if (a.type !== 'Upload' && b.type === 'Upload') {
return -1
}
return 0
})
fields.splice(3, 0, {
type: 'Column Break',
})
}
const saveSettings = createResource({ const saveSettings = createResource({
url: 'frappe.client.set_value', url: 'frappe.client.set_value',
makeParams(values) { makeParams(values) {

View File

@@ -6,7 +6,7 @@
<div v-for="(column, index) in columns" :key="index"> <div v-for="(column, index) in columns" :key="index">
<div <div
class="flex flex-col space-y-5" class="flex flex-col space-y-5"
:class="columns.length > 1 ? 'w-72' : 'w-full'" :class="columns.length > 1 ? 'w-[21rem]' : 'w-1/2'"
> >
<div v-for="field in column"> <div v-for="field in column">
<Link <Link
@@ -14,6 +14,7 @@
v-model="data[field.name]" v-model="data[field.name]"
:doctype="field.doctype" :doctype="field.doctype"
:label="__(field.label)" :label="__(field.label)"
:description="__(field.description)"
/> />
<div v-else-if="field.type == 'Code'"> <div v-else-if="field.type == 'Code'">
@@ -54,11 +55,11 @@
<div v-else> <div v-else>
<div class="flex items-center text-sm space-x-2"> <div class="flex items-center text-sm space-x-2">
<div <div
class="flex items-center justify-center rounded border border-outline-gray-modals bg-white w-[10rem] py-2" class="flex items-center justify-center rounded border border-outline-gray-1 bg-surface-gray-2 px-20 py-5"
> >
<img <img
:src="data[field.name]?.file_url || data[field.name]" :src="data[field.name]?.file_url || data[field.name]"
class="w-[80%] rounded" class="size-6 rounded"
/> />
</div> </div>
<div class="flex flex-col flex-wrap"> <div class="flex flex-col flex-wrap">

View File

@@ -88,56 +88,61 @@
:scrollToBottom="false" :scrollToBottom="false"
/> />
</div> </div>
<div v-else-if="tab.label == 'Feedback'">
<BatchFeedback :batch="batch.data.name" />
</div>
</div> </div>
</template> </template>
</Tabs> </Tabs>
</div> </div>
<div class="p-5"> <div class="p-5">
<div class="text-ink-gray-7 font-semibold mb-4"> <div class="mb-10">
{{ __('About this batch') }}: <div class="text-ink-gray-7 font-semibold mb-2">
</div> {{ __('About this batch') }}
<div </div>
v-html="batch.data.description" <div
class="leading-5 mb-4 text-ink-gray-7" v-html="batch.data.description"
></div> class="leading-5 mb-4 text-ink-gray-7"
></div>
<div class="flex items-center avatar-group overlap mb-5">
<div <div class="flex items-center avatar-group overlap mb-5">
class="h-6 mr-1" <div
:class="{ class="h-6 mr-1"
'avatar-group overlap': batch.data.instructors.length > 1, :class="{
}" 'avatar-group overlap': batch.data.instructors.length > 1,
> }"
<UserAvatar >
v-for="instructor in batch.data.instructors" <UserAvatar
:user="instructor" v-for="instructor in batch.data.instructors"
/> :user="instructor"
/>
</div>
<CourseInstructors :instructors="batch.data.instructors" />
</div>
<DateRange
:startDate="batch.data.start_date"
:endDate="batch.data.end_date"
class="mb-3"
/>
<div class="flex items-center mb-4 text-ink-gray-7">
<Clock class="h-4 w-4 stroke-1.5 mr-2" />
<span>
{{ formatTime(batch.data.start_time) }} -
{{ formatTime(batch.data.end_time) }}
</span>
</div>
<div
v-if="batch.data.timezone"
class="flex items-center mb-4 text-ink-gray-7"
>
<Globe class="h-4 w-4 stroke-1.5 mr-2" />
<span>
{{ batch.data.timezone }}
</span>
</div> </div>
<CourseInstructors :instructors="batch.data.instructors" />
</div> </div>
<DateRange <div v-if="dayjs().isSameOrAfter(dayjs(batch.data.start_date))">
:startDate="batch.data.start_date" <div class="text-ink-gray-7 font-semibold mb-2">
:endDate="batch.data.end_date" {{ __('Feedback') }}
class="mb-3" </div>
/> <BatchFeedback :batch="batch.data?.name" />
<div class="flex items-center mb-4 text-ink-gray-7">
<Clock class="h-4 w-4 stroke-1.5 mr-2" />
<span>
{{ formatTime(batch.data.start_time) }} -
{{ formatTime(batch.data.end_time) }}
</span>
</div>
<div
v-if="batch.data.timezone"
class="flex items-center mb-4 text-ink-gray-7"
>
<Globe class="h-4 w-4 stroke-1.5 mr-2" />
<span>
{{ batch.data.timezone }}
</span>
</div> </div>
</div> </div>
<AnnouncementModal <AnnouncementModal
@@ -234,6 +239,7 @@ import Discussions from '@/components/Discussions.vue'
import DateRange from '@/components/Common/DateRange.vue' import DateRange from '@/components/Common/DateRange.vue'
import BulkCertificates from '@/components/Modals/BulkCertificates.vue' import BulkCertificates from '@/components/Modals/BulkCertificates.vue'
import BatchFeedback from '@/components/BatchFeedback.vue' import BatchFeedback from '@/components/BatchFeedback.vue'
import dayjs from 'dayjs/esm'
const user = inject('$user') const user = inject('$user')
const showAnnouncementModal = ref(false) const showAnnouncementModal = ref(false)
@@ -277,11 +283,6 @@ const tabs = computed(() => {
label: 'Discussions', label: 'Discussions',
icon: MessageCircle, icon: MessageCircle,
}) })
batchTabs.push({
label: 'Feedback',
icon: ClipboardPen,
})
return batchTabs return batchTabs
}) })

View File

@@ -37,14 +37,7 @@
<BatchOverlay :batch="batch" /> <BatchOverlay :batch="batch" />
</div> </div>
</div> </div>
<!-- <div class="grid lg:grid-cols-[60%,20%] gap-4 lg:gap-20 mt-10"> <BatchOverlay :batch="batch" class="md:hidden mt-5" />
<div class="order-2 lg:order-none">
</div>
<div class="order-1 lg:order-none">
<BatchOverlay :batch="batch" />
</div>
</div> -->
<div v-if="batch.data.courses.length"> <div v-if="batch.data.courses.length">
<div class="flex items-center mt-10"> <div class="flex items-center mt-10">
<div class="text-2xl font-semibold"> <div class="text-2xl font-semibold">

View File

@@ -153,6 +153,11 @@
doctype="Email Template" doctype="Email Template"
:label="__('Email Template')" :label="__('Email Template')"
v-model="batch.confirmation_email_template" v-model="batch.confirmation_email_template"
:onCreate="
(value, close) => {
openSettings('Email Templates', close)
}
"
/> />
</div> </div>
<div class="space-y-5"> <div class="space-y-5">

View File

@@ -20,14 +20,12 @@
</header> </header>
<div class="p-5 pb-10"> <div class="p-5 pb-10">
<div <div
v-if="batchCount"
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5" class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
> >
<div class="text-lg text-ink-gray-9 font-semibold"> <div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Batches') }} {{ __('All Batches') }}
</div> </div>
<div <div
v-if="batches.data?.length || batchCount"
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4" class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
> >
<TabButtons <TabButtons
@@ -115,12 +113,10 @@ const is_student = computed(() => user.data?.is_student)
const currentTab = ref(is_student.value ? 'All' : 'Upcoming') const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
const orderBy = ref('start_date') const orderBy = ref('start_date')
const readOnlyMode = window.read_only_mode const readOnlyMode = window.read_only_mode
const batchCount = ref(0)
onMounted(() => { onMounted(() => {
setFiltersFromQuery() setFiltersFromQuery()
updateBatches() updateBatches()
getBatchCount()
categories.value = [ categories.value = [
{ {
label: '', label: '',
@@ -298,14 +294,6 @@ const canCreateBatch = () => {
return false return false
} }
const getBatchCount = () => {
call('frappe.client.get_count', {
doctype: 'LMS Batch',
}).then((data) => {
batchCount.value = data
})
}
const breadcrumbs = computed(() => [ const breadcrumbs = computed(() => [
{ {
label: __('Batches'), label: __('Batches'),

View File

@@ -20,7 +20,7 @@
:text="__('Average Rating')" :text="__('Average Rating')"
class="flex items-center" class="flex items-center"
> >
<Star class="h-5 w-5 text-gray-100 fill-orange-500" /> <Star class="size-4 text-transparent fill-yellow-500" />
<span class="ml-1 text-ink-gray-7"> <span class="ml-1 text-ink-gray-7">
{{ course.data.rating }} {{ course.data.rating }}
</span> </span>

View File

@@ -20,14 +20,12 @@
</header> </header>
<div class="p-5 pb-10"> <div class="p-5 pb-10">
<div <div
v-if="courseCount"
class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5" class="flex flex-col lg:flex-row space-y-4 lg:space-y-0 lg:items-center justify-between mb-5"
> >
<div class="text-lg text-ink-gray-9 font-semibold"> <div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Courses') }} {{ __('All Courses') }}
</div> </div>
<div <div
v-if="courses.data?.length || courseCount"
class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4" class="flex flex-col space-y-2 lg:space-y-0 lg:flex-row lg:items-center lg:space-x-4"
> >
<TabButtons :buttons="courseTabs" v-model="currentTab" /> <TabButtons :buttons="courseTabs" v-model="currentTab" />
@@ -172,6 +170,8 @@ const identifyUserPersona = async () => {
} }
const getCourseCount = () => { const getCourseCount = () => {
if (!user.data) return
call('frappe.client.get_count', { call('frappe.client.get_count', {
doctype: 'LMS Course', doctype: 'LMS Course',
}).then((data) => { }).then((data) => {

View File

@@ -1,98 +1,96 @@
import { useStorage } from "@vueuse/core"; import '../../../frappe/frappe/public/js/lib/posthog.js'
import { call } from "frappe-ui"; import { createResource } from 'frappe-ui'
import "../../../frappe/frappe/public/js/lib/posthog.js";
const APP = "lms";
const SITENAME = window.location.hostname;
declare global { declare global {
interface Window { interface Window {
posthog: any; posthog: any
} }
} }
type PosthogSettings = {
const telemetry = useStorage("telemetry", { posthog_project_id: string
enabled: false, posthog_host: string
project_id: "", enable_telemetry: boolean
host: "", telemetry_site_age: number
});
export async function init() {
await set_enabled();
if (!telemetry.value.enabled) return;
try {
await set_credentials();
window.posthog.init(telemetry.value.project_id, {
api_host: telemetry.value.host,
autocapture: false,
person_profiles: "always",
capture_pageview: true,
capture_pageleave: true,
disable_session_recording: false,
session_recording: {
maskAllInputs: false,
maskInputOptions: {
password: true,
},
},
loaded: (posthog) => {
window.posthog = posthog;
window.posthog.identify(SITENAME);
},
});
} catch (e) {
console.trace("Failed to initialize telemetry", e);
telemetry.value.enabled = false;
}
} }
async function set_enabled() {
if (telemetry.value.enabled) return;
await call("lms.lms.telemetry.is_enabled").then((res) => {
telemetry.value.enabled = res;
});
}
async function set_credentials() {
if (!telemetry.value.enabled) return;
if (telemetry.value.project_id && telemetry.value.host) return;
await call("lms.lms.telemetry.get_credentials").then((res) => {
telemetry.value.project_id = res.project_id;
telemetry.value.host = res.telemetry_host;
});
}
interface CaptureOptions { interface CaptureOptions {
data: { data: {
user: string; user: string
[key: string]: string | number | boolean | object; [key: string]: string | number | boolean | object
}; }
} }
export function capture( let posthog: typeof window.posthog = window.posthog
// Posthog Settings
let posthogSettings = createResource({
url: 'lms.lms.telemetry.get_posthog_settings',
cache: 'posthog_settings',
onSuccess: (ps: PosthogSettings) => initPosthog(ps),
})
let isTelemetryEnabled = () => {
if (!posthogSettings.data) return false
return (
posthogSettings.data.enable_telemetry &&
posthogSettings.data.posthog_project_id &&
posthogSettings.data.posthog_host
)
}
// Posthog Initialization
function initPosthog(ps: PosthogSettings) {
if (!isTelemetryEnabled()) return
posthog.init(ps.posthog_project_id, {
api_host: ps.posthog_host,
person_profiles: 'identified_only',
autocapture: false,
capture_pageview: true,
capture_pageleave: true,
enable_heatmaps: false,
disable_session_recording: false,
loaded: (ph: typeof posthog) => {
window.posthog = ph
ph.identify(window.location.hostname)
},
})
}
// Posthog Functions
function capture(
event: string, event: string,
options: CaptureOptions = { data: { user: "" } } options: CaptureOptions = { data: { user: '' } },
) { ) {
if (!telemetry.value.enabled) return; if (!isTelemetryEnabled()) return
window.posthog.capture(`${APP}_${event}`, options); window.posthog.capture(`lms_${event}`, options)
} }
export function recordSession() { function startRecording() {
if (!telemetry.value.enabled) return; if (!isTelemetryEnabled()) return
if (window.posthog && window.posthog.__loaded) { if (window.posthog?.__loaded) {
window.posthog.startSessionRecording(); window.posthog.startSessionRecording()
} }
} }
export function stopSession() { function stopRecording() {
if (!telemetry.value.enabled) return; if (!isTelemetryEnabled()) return
if ( if (window.posthog?.__loaded && window.posthog.sessionRecordingStarted()) {
window.posthog && window.posthog.stopSessionRecording()
window.posthog.__loaded && }
window.posthog.sessionRecordingStarted() }
) {
window.posthog.stopSessionRecording(); // Posthog Plugin
} function posthogPlugin(app: any) {
app.config.globalProperties.posthog = posthog
if (!window.posthog?.length) posthogSettings.fetch()
}
export {
posthog,
posthogSettings,
posthogPlugin,
capture,
startRecording,
stopRecording,
} }

View File

@@ -561,3 +561,24 @@ export const openSettings = (category, close) => {
settingsStore.activeTab = category settingsStore.activeTab = category
settingsStore.isSettingsOpen = true settingsStore.isSettingsOpen = true
} }
export const cleanError = (message) => {
// Remove HTML tags but keep the text within the tags
const cleanMessage = message.replace(/<[^>]+>/g, (match) => {
return match.replace(/<\/?[^>]+(>|$)/g, '')
})
return cleanMessage
.replace(/&nbsp;/g, ' ')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/&amp;/g, '&')
.replace(/&#x60;/g, '`')
.replace(/&#x3D;/g, '=')
.replace(/&#x2F;/g, '/')
.replace(/&#x2C;/g, ',')
.replace(/&#x3B;/g, ';')
.replace(/&#x3A;/g, ':')
}

View File

@@ -1 +1 @@
__version__ = "2.28.0" __version__ = "2.28.1"

View File

@@ -356,6 +356,7 @@
"fieldtype": "Section Break" "fieldtype": "Section Break"
} }
], ],
"grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [ "links": [
{ {
@@ -371,8 +372,8 @@
"link_fieldname": "batch_name" "link_fieldname": "batch_name"
} }
], ],
"modified": "2025-02-18 15:43:18.512504", "modified": "2025-05-21 13:30:28.904260",
"modified_by": "Administrator", "modified_by": "sayali@frappe.io",
"module": "LMS", "module": "LMS",
"name": "LMS Batch", "name": "LMS Batch",
"owner": "Administrator", "owner": "Administrator",
@@ -412,8 +413,18 @@
"role": "Batch Evaluator", "role": "Batch Evaluator",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "LMS Student",
"share": 1
} }
], ],
"row_format": "Dynamic",
"show_title_field_in_link": 1, "show_title_field_in_link": 1,
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",

View File

@@ -103,7 +103,7 @@ class LMSBatch(Document):
frappe.throw(_("Seat count cannot be negative.")) frappe.throw(_("Seat count cannot be negative."))
students = frappe.db.count("LMS Batch Enrollment", {"batch": self.name}) students = frappe.db.count("LMS Batch Enrollment", {"batch": self.name})
if cint(self.seat_count) < students: if cint(self.seat_count) and cint(self.seat_count) < students:
frappe.throw(_("There are no seats available in this batch.")) frappe.throw(_("There are no seats available in this batch."))
def validate_timetable(self): def validate_timetable(self):

View File

@@ -73,10 +73,11 @@
"read_only": 1 "read_only": 1
} }
], ],
"grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2025-01-13 19:02:58.259908", "modified": "2025-05-21 15:58:51.667270",
"modified_by": "Administrator", "modified_by": "sayali@frappe.io",
"module": "LMS", "module": "LMS",
"name": "LMS Batch Feedback", "name": "LMS Batch Feedback",
"owner": "Administrator", "owner": "Administrator",
@@ -106,7 +107,9 @@
"write": 1 "write": 1
} }
], ],
"row_format": "Dynamic",
"sort_field": "creation", "sort_field": "creation",
"sort_order": "DESC", "sort_order": "DESC",
"states": [] "states": [],
"title_field": "member"
} }

View File

@@ -1,18 +1,12 @@
import frappe import frappe
from frappe.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD
@frappe.whitelist() @frappe.whitelist()
def is_enabled(): def get_posthog_settings():
return bool(
frappe.get_system_settings("enable_telemetry")
and frappe.conf.get("posthog_host")
and frappe.conf.get("posthog_project_id")
)
@frappe.whitelist()
def get_credentials():
return { return {
"project_id": frappe.conf.get("posthog_project_id"), "posthog_project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD),
"telemetry_host": frappe.conf.get("posthog_host"), "posthog_host": frappe.conf.get(POSTHOG_HOST_FIELD),
"enable_telemetry": frappe.get_system_settings("enable_telemetry"),
"telemetry_site_age": frappe.utils.telemetry.site_age(),
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,11 +2,11 @@
"name": "frappe_lms", "name": "frappe_lms",
"version": "1.0.0", "version": "1.0.0",
"description": "Easy to use, open-source, Learning Management System", "description": "Easy to use, open-source, Learning Management System",
"type": "module",
"workspaces": [ "workspaces": [
"frappe-ui", "frappe-ui",
"frontend" "frontend"
], ],
"type": "module",
"scripts": { "scripts": {
"test-local": "cypress open --e2e --browser chrome", "test-local": "cypress open --e2e --browser chrome",
"postinstall": "cd frontend && yarn install --check-files", "postinstall": "cd frontend && yarn install --check-files",
@@ -26,7 +26,8 @@
"homepage": "https://github.com/frappe/lms#readme", "homepage": "https://github.com/frappe/lms#readme",
"devDependencies": { "devDependencies": {
"cypress": "^13.9.0", "cypress": "^13.9.0",
"cypress-file-upload": "^5.0.8" "cypress-file-upload": "^5.0.8",
"cypress-real-events": "^1.14.0"
}, },
"dependencies": { "dependencies": {
"pre-commit": "^1.2.2" "pre-commit": "^1.2.2"

561
yarn.lock
View File

@@ -35,7 +35,7 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz#a7054dcc145a967dd4dc8fee845a57c1316c9df8"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/parser@^7.25.3": "@babel/parser@^7.27.2":
version "7.27.2" version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127"
integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw== integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==
@@ -262,9 +262,9 @@
"@codexteam/icons" "^0.0.6" "@codexteam/icons" "^0.0.6"
"@editorjs/table@^2.4.2": "@editorjs/table@^2.4.2":
version "2.4.4" version "2.4.5"
resolved "https://registry.yarnpkg.com/@editorjs/table/-/table-2.4.4.tgz#724cbebd2c99b929bd41d31a147fdaf690ae2c81" resolved "https://registry.yarnpkg.com/@editorjs/table/-/table-2.4.5.tgz#bb2ce9962935088e3d24a20e799acb9526b90e7b"
integrity sha512-2wWjxk48C9Z7uaBZIS5dML81c2VsD47Va9J4RDd2UboxcnYV8OZ4oYxKafH5RJIGPj8ykGaROIzyDVR1N4e7Cg== integrity sha512-pF48R2wc5m0c+N+RjtCLXBGZd23Rl7EjfSFpmcSViwNsu5RwMgYGrEiQ8mzVh98mbvYQwXm/NYBi9DEUUs970A==
dependencies: dependencies:
"@codexteam/icons" "^0.0.6" "@codexteam/icons" "^0.0.6"
@@ -496,20 +496,20 @@
mlly "^1.7.4" mlly "^1.7.4"
"@inquirer/confirm@^5.0.0": "@inquirer/confirm@^5.0.0":
version "5.1.10" version "5.1.12"
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.10.tgz#de3732cb7ae9333bd3e354afee6a6ef8cf28d951" resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.12.tgz#387037889a5a558ceefe52e978228630aa6e7d0e"
integrity sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g== integrity sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==
dependencies: dependencies:
"@inquirer/core" "^10.1.11" "@inquirer/core" "^10.1.13"
"@inquirer/type" "^3.0.6" "@inquirer/type" "^3.0.7"
"@inquirer/core@^10.1.11": "@inquirer/core@^10.1.13":
version "10.1.11" version "10.1.13"
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.11.tgz#4022032b5b6b35970e1c3fcfc522bc250ef8810d" resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.13.tgz#8f1ecfaba288fd2d705c7ac0690371464cf687b0"
integrity sha512-BXwI/MCqdtAhzNQlBEFE7CEflhPkl/BqvAuV/aK6lW3DClIfYVDWPP/kXuXHtBWC7/EEbNqd/1BGq2BGBBnuxw== integrity sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==
dependencies: dependencies:
"@inquirer/figures" "^1.0.11" "@inquirer/figures" "^1.0.12"
"@inquirer/type" "^3.0.6" "@inquirer/type" "^3.0.7"
ansi-escapes "^4.3.2" ansi-escapes "^4.3.2"
cli-width "^4.1.0" cli-width "^4.1.0"
mute-stream "^2.0.0" mute-stream "^2.0.0"
@@ -517,27 +517,27 @@
wrap-ansi "^6.2.0" wrap-ansi "^6.2.0"
yoctocolors-cjs "^2.1.2" yoctocolors-cjs "^2.1.2"
"@inquirer/figures@^1.0.11": "@inquirer/figures@^1.0.12":
version "1.0.11" version "1.0.12"
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.11.tgz#4744e6db95288fea1dead779554859710a959a21" resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.12.tgz#667d6254cc7ba3b0c010a323d78024a1d30c6053"
integrity sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw== integrity sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==
"@inquirer/type@^3.0.6": "@inquirer/type@^3.0.7":
version "3.0.6" version "3.0.7"
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.6.tgz#2500e435fc2014c5250eec3279f42b70b64089bd" resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.7.tgz#b46bcf377b3172dbc768fdbd053e6492ad801a09"
integrity sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA== integrity sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==
"@internationalized/date@^3.5.0", "@internationalized/date@^3.5.4": "@internationalized/date@^3.5.0", "@internationalized/date@^3.5.4":
version "3.8.0" version "3.8.1"
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.8.0.tgz#24fb301029224351381aa87cba853ca1093af094" resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.8.1.tgz#fb3709440060a9efa0722615e83550e682e83221"
integrity sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw== integrity sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==
dependencies: dependencies:
"@swc/helpers" "^0.5.0" "@swc/helpers" "^0.5.0"
"@internationalized/number@^3.5.0", "@internationalized/number@^3.5.3": "@internationalized/number@^3.5.0", "@internationalized/number@^3.5.3":
version "3.6.1" version "3.6.2"
resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.1.tgz#7c13cc55eb546aa3d42b8d5e7ac7db69a082fec7" resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.2.tgz#504bf772238420c06b63ec58957c1cfcf6d92755"
integrity sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g== integrity sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==
dependencies: dependencies:
"@swc/helpers" "^0.5.0" "@swc/helpers" "^0.5.0"
@@ -694,105 +694,105 @@
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f" resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg== integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==
"@rollup/rollup-android-arm-eabi@4.40.2": "@rollup/rollup-android-arm-eabi@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz#c228d00a41f0dbd6fb8b7ea819bbfbf1c1157a10" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz#f39f09f60d4a562de727c960d7b202a2cf797424"
integrity sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg== integrity sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==
"@rollup/rollup-android-arm64@4.40.2": "@rollup/rollup-android-arm64@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz#e2b38d0c912169fd55d7e38d723aada208d37256" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz#d19af7e23760717f1d879d4ca3d2cd247742dff2"
integrity sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw== integrity sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==
"@rollup/rollup-darwin-arm64@4.40.2": "@rollup/rollup-darwin-arm64@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz#1fddb3690f2ae33df16d334c613377f05abe4878" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz#1c3a2fbf205d80641728e05f4a56c909e95218b7"
integrity sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w== integrity sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==
"@rollup/rollup-darwin-x64@4.40.2": "@rollup/rollup-darwin-x64@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz#818298d11c8109e1112590165142f14be24b396d" resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz#aa66d2ba1a25e609500e13bef06dc0e71cc0c0d4"
integrity sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ== integrity sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==
"@rollup/rollup-freebsd-arm64@4.40.2": "@rollup/rollup-freebsd-arm64@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz#91a28dc527d5bed7f9ecf0e054297b3012e19618" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz#df10a7b6316a0ef1028c6ca71a081124c537e30d"
integrity sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ== integrity sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==
"@rollup/rollup-freebsd-x64@4.40.2": "@rollup/rollup-freebsd-x64@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz#28acadefa76b5c7bede1576e065b51d335c62c62" resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz#a3fdce8a05e95b068cbcb46e4df5185e407d0c35"
integrity sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q== integrity sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==
"@rollup/rollup-linux-arm-gnueabihf@4.40.2": "@rollup/rollup-linux-arm-gnueabihf@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz#819691464179cbcd9a9f9d3dc7617954840c6186" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz#49f766c55383bd0498014a9d76924348c2f3890c"
integrity sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q== integrity sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==
"@rollup/rollup-linux-arm-musleabihf@4.40.2": "@rollup/rollup-linux-arm-musleabihf@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz#d149207039e4189e267e8724050388effc80d704" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz#1d4d7d32fc557e17d52e1857817381ea365e2959"
integrity sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg== integrity sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==
"@rollup/rollup-linux-arm64-gnu@4.40.2": "@rollup/rollup-linux-arm64-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz#fa72ebddb729c3c6d88973242f1a2153c83e86ec" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz#f4fc317268441e9589edad3be8f62b6c03009bc1"
integrity sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg== integrity sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==
"@rollup/rollup-linux-arm64-musl@4.40.2": "@rollup/rollup-linux-arm64-musl@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz#2054216e34469ab8765588ebf343d531fc3c9228" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz#63a1f1b0671cb17822dabae827fef0e443aebeb7"
integrity sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg== integrity sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==
"@rollup/rollup-linux-loongarch64-gnu@4.40.2": "@rollup/rollup-linux-loongarch64-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz#818de242291841afbfc483a84f11e9c7a11959bc" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz#c659b01cc6c0730b547571fc3973e1e955369f98"
integrity sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw== integrity sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==
"@rollup/rollup-linux-powerpc64le-gnu@4.40.2": "@rollup/rollup-linux-powerpc64le-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz#0bb4cb8fc4a2c635f68c1208c924b2145eb647cb" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz#612e746f9ad7e58480f964d65e0d6c3f4aae69a8"
integrity sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q== integrity sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==
"@rollup/rollup-linux-riscv64-gnu@4.40.2": "@rollup/rollup-linux-riscv64-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz#4b3b8e541b7b13e447ae07774217d98c06f6926d" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz#4610dbd1dcfbbae32fbc10c20ae7387acb31110c"
integrity sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg== integrity sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==
"@rollup/rollup-linux-riscv64-musl@4.40.2": "@rollup/rollup-linux-riscv64-musl@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz#e065405e67d8bd64a7d0126c931bd9f03910817f" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz#054911fab40dc83fafc21e470193c058108f19d8"
integrity sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg== integrity sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==
"@rollup/rollup-linux-s390x-gnu@4.40.2": "@rollup/rollup-linux-s390x-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz#dda3265bbbfe16a5d0089168fd07f5ebb2a866fe" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz#98896eca8012547c7f04bd07eaa6896825f9e1a5"
integrity sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ== integrity sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==
"@rollup/rollup-linux-x64-gnu@4.40.2": "@rollup/rollup-linux-x64-gnu@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz#90993269b8b995b4067b7b9d72ff1c360ef90a17" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz#01cf56844a1e636ee80dfb364e72c2b7142ad896"
integrity sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng== integrity sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==
"@rollup/rollup-linux-x64-musl@4.40.2": "@rollup/rollup-linux-x64-musl@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz#fdf5b09fd121eb8d977ebb0fda142c7c0167b8de" resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz#e67c7531df6dff0b4c241101d4096617fbca87c3"
integrity sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA== integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==
"@rollup/rollup-win32-arm64-msvc@4.40.2": "@rollup/rollup-win32-arm64-msvc@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz#6397e1e012db64dfecfed0774cb9fcf89503d716" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz#7eeada98444e580674de6989284e4baacd48ea65"
integrity sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg== integrity sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==
"@rollup/rollup-win32-ia32-msvc@4.40.2": "@rollup/rollup-win32-ia32-msvc@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz#df0991464a52a35506103fe18d29913bf8798a0c" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz#516c4b54f80587b4a390aaf4940b40870271d35d"
integrity sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA== integrity sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==
"@rollup/rollup-win32-x64-msvc@4.40.2": "@rollup/rollup-win32-x64-msvc@4.41.1":
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz#8dae04d01a2cbd84d6297d99356674c6b993f0fc" resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz#848f99b0d9936d92221bb6070baeff4db6947a30"
integrity sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA== integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==
"@socket.io/component-emitter@~3.1.0": "@socket.io/component-emitter@~3.1.0":
version "3.1.2" version "3.1.2"
@@ -850,17 +850,17 @@
lodash.merge "^4.6.2" lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10" postcss-selector-parser "6.0.10"
"@tanstack/virtual-core@3.13.8": "@tanstack/virtual-core@3.13.9":
version "3.13.8" version "3.13.9"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.13.8.tgz#6346e688521c1f086f508ccbebaad0b472a2aefb" resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.13.9.tgz#62b4d2d4351d658101664beacf088fbd061190bf"
integrity sha512-BT6w89Hqy7YKaWewYzmecXQzcJh6HTBbKYJIIkMaNU49DZ06LoTV3z32DWWEdUsgW6n1xTmwTLs4GtWrZC261w== integrity sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==
"@tanstack/vue-virtual@^3.0.0-beta.60", "@tanstack/vue-virtual@^3.12.0", "@tanstack/vue-virtual@^3.8.1": "@tanstack/vue-virtual@^3.0.0-beta.60", "@tanstack/vue-virtual@^3.12.0", "@tanstack/vue-virtual@^3.8.1":
version "3.13.8" version "3.13.9"
resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.13.8.tgz#5df214b258e6f62ce775a2bfa8d8919043a2bb60" resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.13.9.tgz#4f5eaab36a511a93123f27c41aca8d0bac51d1ac"
integrity sha512-CqyjKVc88YlE8JPth8a5Gi4CUoYrwJ2PZxtFbhoekx8Z2qqymxX2jzkbUMKFsX4EVNET90D5bLsG3epyozbzcg== integrity sha512-HsvHaOo+o52cVcPhomKDZ3CMpTF/B2qg+BhPHIQJwzn4VIqDyt/rRVqtIomG6jE83IFsE2vlr6cmx7h3dHA0SA==
dependencies: dependencies:
"@tanstack/virtual-core" "3.13.8" "@tanstack/virtual-core" "3.13.9"
"@tiptap/core@^2.11.7", "@tiptap/core@^2.12.0": "@tiptap/core@^2.11.7", "@tiptap/core@^2.12.0":
version "2.12.0" version "2.12.0"
@@ -1170,9 +1170,9 @@
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/node@*": "@types/node@*":
version "22.15.17" version "22.15.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055" resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.21.tgz#196ef14fe20d87f7caf1e7b39832767f9a995b77"
integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw== integrity sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==
dependencies: dependencies:
undici-types "~6.21.0" undici-types "~6.21.0"
@@ -1287,90 +1287,90 @@
loupe "^3.1.2" loupe "^3.1.2"
tinyrainbow "^1.2.0" tinyrainbow "^1.2.0"
"@vue/compiler-core@3.5.13": "@vue/compiler-core@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05" resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.14.tgz#3676685c04c48a5b4a5515b3b2842e98342c555c"
integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q== integrity sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==
dependencies: dependencies:
"@babel/parser" "^7.25.3" "@babel/parser" "^7.27.2"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
entities "^4.5.0" entities "^4.5.0"
estree-walker "^2.0.2" estree-walker "^2.0.2"
source-map-js "^1.2.0" source-map-js "^1.2.1"
"@vue/compiler-dom@3.5.13": "@vue/compiler-dom@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58" resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz#bbf27421f80f7b8873000edceecd817c4abf438a"
integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA== integrity sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==
dependencies: dependencies:
"@vue/compiler-core" "3.5.13" "@vue/compiler-core" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
"@vue/compiler-sfc@3.5.13": "@vue/compiler-sfc@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46" resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz#fc3db30a1c744139d41bb57bb451d783415fce4b"
integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ== integrity sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==
dependencies: dependencies:
"@babel/parser" "^7.25.3" "@babel/parser" "^7.27.2"
"@vue/compiler-core" "3.5.13" "@vue/compiler-core" "3.5.14"
"@vue/compiler-dom" "3.5.13" "@vue/compiler-dom" "3.5.14"
"@vue/compiler-ssr" "3.5.13" "@vue/compiler-ssr" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
estree-walker "^2.0.2" estree-walker "^2.0.2"
magic-string "^0.30.11" magic-string "^0.30.17"
postcss "^8.4.48" postcss "^8.5.3"
source-map-js "^1.2.0" source-map-js "^1.2.1"
"@vue/compiler-ssr@3.5.13": "@vue/compiler-ssr@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba" resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz#013174ee6bbf3ee291a6df247a3feb6eb43d808b"
integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA== integrity sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==
dependencies: dependencies:
"@vue/compiler-dom" "3.5.13" "@vue/compiler-dom" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
"@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4": "@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4":
version "6.6.4" version "6.6.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343" resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g== integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
"@vue/reactivity@3.5.13": "@vue/reactivity@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f" resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.14.tgz#814fb4ba84a9560d2752b9982fdd2b76e4a5e5a3"
integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg== integrity sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==
dependencies: dependencies:
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
"@vue/runtime-core@3.5.13": "@vue/runtime-core@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455" resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.14.tgz#f4084cad032be3452d8f137035fcd93c182f7149"
integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw== integrity sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==
dependencies: dependencies:
"@vue/reactivity" "3.5.13" "@vue/reactivity" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
"@vue/runtime-dom@3.5.13": "@vue/runtime-dom@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215" resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz#59ea4a5fe3ed93fb8f725c1c722a0fe8d8ae16cf"
integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog== integrity sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==
dependencies: dependencies:
"@vue/reactivity" "3.5.13" "@vue/reactivity" "3.5.14"
"@vue/runtime-core" "3.5.13" "@vue/runtime-core" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
csstype "^3.1.3" csstype "^3.1.3"
"@vue/server-renderer@3.5.13": "@vue/server-renderer@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7" resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.14.tgz#adcaf30ddcf0064a28ce832d29f430bd0db3ef18"
integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA== integrity sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==
dependencies: dependencies:
"@vue/compiler-ssr" "3.5.13" "@vue/compiler-ssr" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
"@vue/shared@3.5.13": "@vue/shared@3.5.14":
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f" resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.14.tgz#8fcdc6c69661a1163c173cafb6129c3f8ad01122"
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ== integrity sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==
"@vueuse/core@^10.11.0", "@vueuse/core@^10.4.1": "@vueuse/core@^10.11.0", "@vueuse/core@^10.4.1":
version "10.11.1" version "10.11.1"
@@ -1563,9 +1563,9 @@ argparse@^2.0.1:
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.2.4: aria-hidden@^1.2.4:
version "1.2.4" version "1.2.6"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522" resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a"
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==
dependencies: dependencies:
tslib "^2.0.0" tslib "^2.0.0"
@@ -2107,6 +2107,11 @@ cypress-file-upload@^5.0.8:
resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1" resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"
integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g== integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
cypress-real-events@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/cypress-real-events/-/cypress-real-events-1.14.0.tgz#c5495db50a2bd247f4accde983af7153566945d3"
integrity sha512-XmI8y3OZLh6cjRroPalzzS++iv+pGCaD9G9kfIbtspgv7GVsDt30dkZvSXfgZb4rAN+3pOkMVB7e0j4oXydW7Q==
cypress@^13.9.0: cypress@^13.9.0:
version "13.17.0" version "13.17.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d" resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d"
@@ -2184,10 +2189,10 @@ debug@2.6.9:
dependencies: dependencies:
ms "2.0.0" ms "2.0.0"
debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0: debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0, debug@^4.4.1:
version "4.4.0" version "4.4.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies: dependencies:
ms "^2.1.3" ms "^2.1.3"
@@ -2322,9 +2327,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.149: electron-to-chromium@^1.5.149:
version "1.5.152" version "1.5.157"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz#bcdd39567e291b930ec26b930031137a05593695" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz#553b122522ac7bba6f1a0dd7d50b14f297736f75"
integrity sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg== integrity sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==
emoji-regex@^10.3.0: emoji-regex@^10.3.0:
version "10.4.0" version "10.4.0"
@@ -2662,9 +2667,9 @@ finalhandler@1.1.2:
unpipe "~1.0.0" unpipe "~1.0.0"
flexsearch@*: flexsearch@*:
version "0.8.164" version "0.8.204"
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.164.tgz#2d1277249d6dec8eb745358fa64543ddbddc9e05" resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.204.tgz#843f295e8f2aa3b3326d4bbee3ebe761a4a73cec"
integrity sha512-tauQG+NlwSWW6uL3BIJdHNEXYiei2xTR3H/mrjNadAOwvXgiLbLvLzQWSJvD31Yn0+1lAxG2NVRndywGnDZZiA== integrity sha512-Vh+WUZfUHsVP6w4o5uAkYle8Gz/oEuztSWvpSY3h71AE8ox+goTQ2X5YG4x6VlKKfubkMwhewk8kBTOVKMObHA==
flexsearch@0.7.21: flexsearch@0.7.21:
version "0.7.21" version "0.7.21"
@@ -2699,6 +2704,58 @@ fraction.js@^4.3.7:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
frappe-ui@^0.1.147:
version "0.1.147"
resolved "https://registry.yarnpkg.com/frappe-ui/-/frappe-ui-0.1.147.tgz#c4fe061b52d8e45a24de34a7d36257d2277aa965"
integrity sha512-4yoT2Qw8M8NbDjgnYt83YuyMIGVVCdN+gIRCSZD9NpLQZpyyf0QJOsSrEIwkRdtV644L+4XknA0pv3UgFOkHtQ==
dependencies:
"@floating-ui/vue" "^1.1.6"
"@headlessui/vue" "^1.7.14"
"@popperjs/core" "^2.11.2"
"@tailwindcss/forms" "^0.5.3"
"@tailwindcss/typography" "^0.5.16"
"@tiptap/core" "^2.11.7"
"@tiptap/extension-code-block" "^2.11.9"
"@tiptap/extension-code-block-lowlight" "^2.11.5"
"@tiptap/extension-color" "^2.0.3"
"@tiptap/extension-heading" "^2.12.0"
"@tiptap/extension-highlight" "^2.0.3"
"@tiptap/extension-image" "^2.0.3"
"@tiptap/extension-link" "^2.0.3"
"@tiptap/extension-mention" "^2.0.3"
"@tiptap/extension-placeholder" "^2.0.3"
"@tiptap/extension-table" "^2.0.3"
"@tiptap/extension-table-cell" "^2.0.3"
"@tiptap/extension-table-header" "^2.0.3"
"@tiptap/extension-table-row" "^2.0.3"
"@tiptap/extension-text-align" "^2.0.3"
"@tiptap/extension-text-style" "^2.0.3"
"@tiptap/extension-typography" "^2.0.3"
"@tiptap/pm" "^2.0.3"
"@tiptap/starter-kit" "^2.0.3"
"@tiptap/suggestion" "^2.0.3"
"@tiptap/vue-3" "^2.0.3"
"@vueuse/core" "^10.4.1"
dayjs "^1.11.13"
echarts "^5.6.0"
feather-icons "^4.28.0"
idb-keyval "^6.2.0"
lowlight "^3.3.0"
lucide-static "^0.479.0"
ora "5.4.1"
prettier "^3.3.2"
prosemirror-model "^1.25.1"
prosemirror-state "^1.4.3"
prosemirror-view "^1.39.2"
radix-vue "^1.5.3"
reka-ui "^2.0.2"
showdown "^2.1.0"
socket.io-client "^4.5.1"
tippy.js "^6.3.7"
typescript "^5.0.2"
unplugin-icons "^22.1.0"
unplugin-vue-components "^28.4.1"
fs-extra@^10.1.0: fs-extra@^10.1.0:
version "10.1.0" version "10.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
@@ -3421,7 +3478,7 @@ lucide-vue-next@^0.383.0:
resolved "https://registry.yarnpkg.com/lucide-vue-next/-/lucide-vue-next-0.383.0.tgz#7222eea85c185634ceb6d494d5153a6868805a07" resolved "https://registry.yarnpkg.com/lucide-vue-next/-/lucide-vue-next-0.383.0.tgz#7222eea85c185634ceb6d494d5153a6868805a07"
integrity sha512-paQmd2cHAye7Zl/lA0avZN2efZxFkMehfoori1BiHKX//KQG4DVuy00yl4YHVQ6h1B4EsR+QDRCpVUtwvKUBRw== integrity sha512-paQmd2cHAye7Zl/lA0avZN2efZxFkMehfoori1BiHKX//KQG4DVuy00yl4YHVQ6h1B4EsR+QDRCpVUtwvKUBRw==
magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.17: magic-string@^0.30.12, magic-string@^0.30.17:
version "0.30.17" version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA== integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
@@ -3579,9 +3636,9 @@ ms@^2.1.1, ms@^2.1.3:
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
msw@^2.7.0: msw@^2.7.0:
version "2.8.2" version "2.8.4"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.8.2.tgz#a35545275403da472f4ed2152cd3c77000db544c" resolved "https://registry.yarnpkg.com/msw/-/msw-2.8.4.tgz#e61f50f5bc891e5b81655e4450650b587b761dd5"
integrity sha512-ugu8RBgUj6//RD0utqDDPdS+QIs36BKYkDAM6u59hcMVtFM4PM0vW4l3G1R+1uCWP2EWFUG8reT/gPXVEtx7/w== integrity sha512-GLU8gx0o7RBG/3x/eTnnLd5S5ZInxXRRRMN8GJwaPZ4jpJTxzQfWGvwr90e8L5dkKJnz+gT4gQYCprLy/c4kVw==
dependencies: dependencies:
"@bundled-es-modules/cookie" "^2.0.1" "@bundled-es-modules/cookie" "^2.0.1"
"@bundled-es-modules/statuses" "^1.0.1" "@bundled-es-modules/statuses" "^1.0.1"
@@ -3617,9 +3674,9 @@ mz@^2.7.0:
thenify-all "^1.0.0" thenify-all "^1.0.0"
nano-spawn@^1.0.0: nano-spawn@^1.0.0:
version "1.0.1" version "1.0.2"
resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.1.tgz#c8e4c1e133e567e3efba44041dcfb12113d861b6" resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.2.tgz#9853795681f0e96ef6f39104c2e4347b6ba79bf6"
integrity sha512-BfcvzBlUTxSDWfT+oH7vd6CbUV+rThLLHCIym/QO6GGLBsyVXleZs00fto2i2jzC/wPiBYk5jyOmpXWg4YopiA== integrity sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==
nanoid@^3.3.8: nanoid@^3.3.8:
version "3.3.11" version "3.3.11"
@@ -3974,7 +4031,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.4.21, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.4.48, postcss@^8.4.5: postcss@^8.4.21, postcss@^8.4.43, postcss@^8.4.47, postcss@^8.4.5, postcss@^8.5.3:
version "8.5.3" version "8.5.3"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A== integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
@@ -4163,9 +4220,9 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor
prosemirror-model "^1.21.0" prosemirror-model "^1.21.0"
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.37.0, prosemirror-view@^1.39.1, prosemirror-view@^1.39.2: prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.37.0, prosemirror-view@^1.39.1, prosemirror-view@^1.39.2:
version "1.39.2" version "1.39.3"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.39.2.tgz#178743c9694fec5ed498d48e46d4a31bc1ef0936" resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.39.3.tgz#54fa4b8ab4fd75ad0075dc6dc0be1745429d5a5c"
integrity sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg== integrity sha512-bY/7kg0LzRE7ytR0zRdSMWX3sknEjw68l836ffLPMh0OG3OYnNuBDUSF3v0vjvnzgYjgY9ZH/RypbARURlcMFA==
dependencies: dependencies:
prosemirror-model "^1.20.0" prosemirror-model "^1.20.0"
prosemirror-state "^1.0.0" prosemirror-state "^1.0.0"
@@ -4355,32 +4412,32 @@ rfdc@^1.3.0, rfdc@^1.4.1:
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rollup@^4.20.0: rollup@^4.20.0:
version "4.40.2" version "4.41.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.40.2.tgz#778e88b7a197542682b3e318581f7697f55f0619" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.41.1.tgz#46ddc1b33cf1b0baa99320d3b0b4973dc2253b6a"
integrity sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg== integrity sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==
dependencies: dependencies:
"@types/estree" "1.0.7" "@types/estree" "1.0.7"
optionalDependencies: optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.40.2" "@rollup/rollup-android-arm-eabi" "4.41.1"
"@rollup/rollup-android-arm64" "4.40.2" "@rollup/rollup-android-arm64" "4.41.1"
"@rollup/rollup-darwin-arm64" "4.40.2" "@rollup/rollup-darwin-arm64" "4.41.1"
"@rollup/rollup-darwin-x64" "4.40.2" "@rollup/rollup-darwin-x64" "4.41.1"
"@rollup/rollup-freebsd-arm64" "4.40.2" "@rollup/rollup-freebsd-arm64" "4.41.1"
"@rollup/rollup-freebsd-x64" "4.40.2" "@rollup/rollup-freebsd-x64" "4.41.1"
"@rollup/rollup-linux-arm-gnueabihf" "4.40.2" "@rollup/rollup-linux-arm-gnueabihf" "4.41.1"
"@rollup/rollup-linux-arm-musleabihf" "4.40.2" "@rollup/rollup-linux-arm-musleabihf" "4.41.1"
"@rollup/rollup-linux-arm64-gnu" "4.40.2" "@rollup/rollup-linux-arm64-gnu" "4.41.1"
"@rollup/rollup-linux-arm64-musl" "4.40.2" "@rollup/rollup-linux-arm64-musl" "4.41.1"
"@rollup/rollup-linux-loongarch64-gnu" "4.40.2" "@rollup/rollup-linux-loongarch64-gnu" "4.41.1"
"@rollup/rollup-linux-powerpc64le-gnu" "4.40.2" "@rollup/rollup-linux-powerpc64le-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-gnu" "4.40.2" "@rollup/rollup-linux-riscv64-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-musl" "4.40.2" "@rollup/rollup-linux-riscv64-musl" "4.41.1"
"@rollup/rollup-linux-s390x-gnu" "4.40.2" "@rollup/rollup-linux-s390x-gnu" "4.41.1"
"@rollup/rollup-linux-x64-gnu" "4.40.2" "@rollup/rollup-linux-x64-gnu" "4.41.1"
"@rollup/rollup-linux-x64-musl" "4.40.2" "@rollup/rollup-linux-x64-musl" "4.41.1"
"@rollup/rollup-win32-arm64-msvc" "4.40.2" "@rollup/rollup-win32-arm64-msvc" "4.41.1"
"@rollup/rollup-win32-ia32-msvc" "4.40.2" "@rollup/rollup-win32-ia32-msvc" "4.41.1"
"@rollup/rollup-win32-x64-msvc" "4.40.2" "@rollup/rollup-win32-x64-msvc" "4.41.1"
fsevents "~2.3.2" fsevents "~2.3.2"
rope-sequence@^1.3.0: rope-sequence@^1.3.0:
@@ -4628,7 +4685,7 @@ sortablejs@1.14.0:
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8" resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w== integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
source-map-js@^1.2.0, source-map-js@^1.2.1: source-map-js@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
@@ -4914,10 +4971,10 @@ tinyexec@^1.0.1:
resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1" resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-1.0.1.tgz#70c31ab7abbb4aea0a24f55d120e5990bfa1e0b1"
integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==
tinyglobby@^0.2.12: tinyglobby@^0.2.14:
version "0.2.13" version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw== integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==
dependencies: dependencies:
fdir "^6.4.4" fdir "^6.4.4"
picomatch "^4.0.2" picomatch "^4.0.2"
@@ -5104,23 +5161,23 @@ unplugin-utils@^0.2.4:
picomatch "^4.0.2" picomatch "^4.0.2"
unplugin-vue-components@^28.4.1: unplugin-vue-components@^28.4.1:
version "28.5.0" version "28.7.0"
resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz#33585a24c98939d1abe56bd69217bc7187ba329f" resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-28.7.0.tgz#e61e5a267a951fbace3190e0dfc61268a00f1b3a"
integrity sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg== integrity sha512-3SuWAHlTjOiZckqRBGXRdN/k6IMmKyt2Ch5/+DKwYaT321H0ItdZDvW4r8/YkEKQpN9TN3F/SZ0W342gQROC3Q==
dependencies: dependencies:
chokidar "^3.6.0" chokidar "^3.6.0"
debug "^4.4.0" debug "^4.4.1"
local-pkg "^1.1.1" local-pkg "^1.1.1"
magic-string "^0.30.17" magic-string "^0.30.17"
mlly "^1.7.4" mlly "^1.7.4"
tinyglobby "^0.2.12" tinyglobby "^0.2.14"
unplugin "^2.3.2" unplugin "^2.3.4"
unplugin-utils "^0.2.4" unplugin-utils "^0.2.4"
unplugin@^2.2.0, unplugin@^2.3.2: unplugin@^2.2.0, unplugin@^2.3.4:
version "2.3.3" version "2.3.4"
resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.3.tgz#f83507e4484008e400f3d831a628eaede22c954f" resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.4.tgz#f3cf35f36656404cb9078be59f993941d649d87c"
integrity sha512-DN4DgiS13HFrAapoRmmoa9B35QzmQVRH2k58HelO28htXVNEEFZ8CGlZL0aRHXOXtz9McwY6lqaZjcc15uWMow== integrity sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg==
dependencies: dependencies:
acorn "^8.14.1" acorn "^8.14.1"
picomatch "^4.0.2" picomatch "^4.0.2"
@@ -5278,15 +5335,15 @@ vue3-apexcharts@^1.8.0:
integrity sha512-5tSD4mXTBbIJ9ir+58qHE6oNtIe0RNgqIRYMKpcsIaxkKtwUww4JhvPkpUFlmiW4OJbbdklgjleXq1lfcM4gdA== integrity sha512-5tSD4mXTBbIJ9ir+58qHE6oNtIe0RNgqIRYMKpcsIaxkKtwUww4JhvPkpUFlmiW4OJbbdklgjleXq1lfcM4gdA==
vue@^3.3.0, vue@^3.4.23, vue@^3.5.13: vue@^3.3.0, vue@^3.4.23, vue@^3.5.13:
version "3.5.13" version "3.5.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a" resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.14.tgz#0ddf16d20cc20adaedfb5e77bca64c488bf5ee27"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ== integrity sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==
dependencies: dependencies:
"@vue/compiler-dom" "3.5.13" "@vue/compiler-dom" "3.5.14"
"@vue/compiler-sfc" "3.5.13" "@vue/compiler-sfc" "3.5.14"
"@vue/runtime-dom" "3.5.13" "@vue/runtime-dom" "3.5.14"
"@vue/server-renderer" "3.5.13" "@vue/server-renderer" "3.5.14"
"@vue/shared" "3.5.13" "@vue/shared" "3.5.14"
vuedraggable@4.1.0: vuedraggable@4.1.0:
version "4.1.0" version "4.1.0"
@@ -5459,9 +5516,9 @@ yallist@^2.1.2:
integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
yaml@^2.3.4, yaml@^2.7.1: yaml@^2.3.4, yaml@^2.7.1:
version "2.7.1" version "2.8.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.1.tgz#44a247d1b88523855679ac7fa7cda6ed7e135cf6" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"
integrity sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ== integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
yargs-parser@^21.1.1: yargs-parser@^21.1.1:
version "21.1.1" version "21.1.1"