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

View File

@@ -25,6 +25,7 @@
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import "cypress-file-upload";
import "cypress-real-events";
Cypress.Commands.add("login", (email, password) => {
if (!email) {
@@ -68,3 +69,11 @@ Cypress.Commands.add("paste", { prevSubject: true }, (subject, text) => {
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']
EditCoverImage: typeof import('./src/components/Modals/EditCoverImage.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']
EvaluationModal: typeof import('./src/components/Modals/EvaluationModal.vue')['default']
Evaluators: typeof import('./src/components/Evaluators.vue')['default']
Event: typeof import('./src/components/Modals/Event.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']
IconPicker: typeof import('./src/components/Controls/IconPicker.vue')['default']
IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default']

View File

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

View File

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

View File

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

View File

@@ -1,44 +1,49 @@
<template>
<div v-if="user.data?.is_student">
<div
v-if="feedbackList.data?.length"
class="bg-surface-blue-2 text-blue-700 p-2 rounded-md mb-5"
>
{{ __('Thank you for providing your feedback!') }}
</div>
<div v-else class="flex justify-between items-center mb-5">
<div class="text-lg font-semibold">
{{ __('Help Us Improve') }}
<div>
<div class="leading-5 mb-4">
<div v-if="readOnly">
{{ __('Thank you for providing your feedback.') }}
<span
@click="showFeedbackForm = !showFeedbackForm"
class="underline cursor-pointer"
>{{ __('Click here') }}</span
>
{{ __('to view your feedback.') }}
</div>
<div v-else>
{{ __('Help us improve by providing your feedback.') }}
</div>
</div>
<Button @click="submitFeedback()">
{{ __('Submit') }}
</Button>
</div>
<div class="space-y-8">
<div class="flex items-center justify-between">
<Rating
v-for="key in ratingKeys"
v-model="feedback[key]"
:label="__(convertToTitleCase(key))"
<div class="space-y-4" :class="showFeedbackForm ? 'block' : 'hidden'">
<div class="space-y-4">
<Rating
v-for="key in ratingKeys"
v-model="feedback[key]"
:label="__(convertToTitleCase(key))"
:readonly="readOnly"
/>
</div>
<FormControl
v-model="feedback.feedback"
type="textarea"
:label="__('Feedback')"
:rows="9"
:readonly="readOnly"
/>
<Button v-if="!readOnly" @click="submitFeedback">
{{ __('Submit Feedback') }}
</Button>
</div>
<FormControl
v-model="feedback.feedback"
type="textarea"
:label="__('Feedback')"
:rows="7"
:readonly="readOnly"
/>
</div>
</div>
<div v-else-if="feedbackList.data?.length">
<div class="text-lg font-semibold mb-5">
{{ __('Average of Feedback Received') }}
<div class="leading-5 text-sm mb-2 mt-5">
{{ __('Average Feedback Received') }}
</div>
<div class="flex items-center justify-between mb-10">
<div class="space-y-4">
<Rating
v-for="key in ratingKeys"
v-model="average[key]"
@@ -47,81 +52,32 @@
/>
</div>
<div class="text-lg font-semibold mb-5">
{{ __('All Feedback') }}
</div>
<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>
<Button variant="outline" class="mt-5" @click="showAllFeedback = true">
{{ __('View all feedback') }}
</Button>
</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.') }}
</div>
<FeedbackModal
v-if="feedbackList.data?.length"
v-model="showAllFeedback"
:feedbackList="feedbackList.data"
/>
</template>
<script setup>
import { computed, inject, onMounted, reactive, ref, watch } from 'vue'
import { inject, onMounted, reactive, ref, watch } from 'vue'
import { convertToTitleCase } from '@/utils'
import {
Avatar,
Button,
createListResource,
FormControl,
ListView,
ListHeader,
ListRows,
ListRow,
ListRowItem,
Rating,
} from 'frappe-ui'
import { Button, createListResource, FormControl, Rating } from 'frappe-ui'
import FeedbackModal from '@/components/Modals/FeedbackModal.vue'
const user = inject('$user')
const ratingKeys = ['content', 'instructors', 'value']
const readOnly = ref(false)
const average = reactive({})
const feedback = reactive({})
const showFeedbackForm = ref(true)
const showAllFeedback = ref(false)
const props = defineProps({
batch: {
@@ -167,6 +123,7 @@ watch(
if (feedbackList.data.length) {
let data = feedbackList.data
readOnly.value = true
showFeedbackForm.value = false
ratingKeys.forEach((key) => {
average[key] = 0
@@ -201,40 +158,11 @@ const submitFeedback = () => {
{
onSuccess: () => {
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>
<style>
.feedback-list > button > div {

View File

@@ -6,103 +6,59 @@
</div>
</div>
<div class="grid grid-cols-4 gap-5 mb-8">
<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">
<User class="w-5 h-5 stroke-1.5" />
</div>
<div class="flex items-center space-x-2">
<span class="font-semibold">
{{ students.data?.length }}
</span>
<span class="">
{{ __('Students') }}
</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">
<GraduationCap class="w-5 h-5 stroke-1.5" />
</div>
<div class="flex items-center space-x-2">
<span class="font-semibold">
{{ 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"
<NumberChart
class="border rounded-md"
:config="{ title: __('Students'), value: students.data?.length || 0 }"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Certified'),
value: certificationCount.data || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{
title: __('Courses'),
value: batch.data.courses?.length || 0,
}"
/>
<NumberChart
class="border rounded-md"
:config="{ title: __('Assessments'), value: assessmentCount || 0 }"
/>
<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>
<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>
@@ -214,6 +170,7 @@
<script setup>
import {
Avatar,
AxisChart,
Button,
createResource,
FeatherIcon,
@@ -224,6 +181,7 @@ import {
ListRows,
ListView,
ListRowItem,
NumberChart,
toast,
} from 'frappe-ui'
import {
@@ -245,7 +203,6 @@ const showStudentModal = ref(false)
const showStudentProgressModal = ref(false)
const selectedStudent = ref(null)
const chartData = ref(null)
const chartOptions = ref(null)
const showProgressChart = ref(false)
const assessmentCount = ref(0)
const readOnlyMode = window.read_only_mode
@@ -333,96 +290,49 @@ const removeStudents = (selections, unselectAll) => {
}
const getChartData = () => {
let categories = {}
let tasks = []
let data = []
if (!students.data?.length) return []
Object.keys(students.data[0].courses).forEach((course) => {
categories[course] = {
value: 0,
type: 'course',
label: course,
}
students.data.forEach((row) => {
tasks = countAssessments(row, tasks)
tasks = countCourses(row, tasks)
})
Object.keys(students.data?.[0].assessments).forEach((assessment) => {
categories[assessment] = {
value: 0,
type: 'assessment',
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
}
tasks.forEach((task) => {
data.push({
task: task.label,
value: task.value,
})
})
chartOptions.value = getChartOptions(categories)
return [
{
name: __('Completed by Students'),
data: Object.values(categories).map((item) => item.value),
},
]
return data
}
const getChartOptions = (categories) => {
const courseColor = theme.colors.green[700]
const assessmentColor = theme.colors.blue[700]
const maxY =
students.data?.length % 5
? students.data?.length + (5 - (students.data?.length % 5))
: students.data?.length
const countAssessments = (row, tasks) => {
Object.keys(row.assessments).forEach((assessment) => {
if (row.assessments[assessment].result === 'Pass') {
tasks.filter((task) => task.label === assessment).length
? tasks.filter((task) => task.label === assessment)[0].value++
: tasks.push({
value: 1,
label: assessment,
})
}
})
return tasks
}
return {
chart: {
type: 'bar',
toolbar: {
show: false,
},
},
plotOptions: {
bar: {
distributed: true,
borderRadius: 3,
borderRadiusApplication: 'end',
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 */
},
}
const countCourses = (row, tasks) => {
Object.keys(row.courses).forEach((course) => {
if (row.courses[course] === 100) {
tasks.filter((task) => task.label === course).length
? tasks.filter((task) => task.label === course)[0].value++
: tasks.push({
value: 1,
label: course,
})
}
})
return tasks
}
watch(students, () => {
@@ -442,8 +352,3 @@ const certificationCount = createResource({
auto: true,
})
</script>
<style>
.apexcharts-legend {
display: none !important;
}
</style>

View File

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

View File

@@ -1,16 +1,32 @@
<template>
<div class="flex flex-col min-h-0">
<div class="flex items-center justify-between">
<div class="text-xl font-semibold mb-5 text-ink-gray-9">
{{ label }}
<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">
<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>
<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
@@ -29,13 +45,39 @@
</div>
<div class="overflow-y-scroll">
<div class="text-base space-y-2">
<FormControl
:value="cat.category"
type="text"
v-for="cat in categories.data"
@change.stop="(e) => update(cat.name, e.target.value)"
/>
<div class="divide-y space-y-2">
<div
v-for="(cat, index) in categories.data"
:key="cat.name"
class="pt-2"
>
<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>
@@ -44,16 +86,22 @@
import {
Button,
FormControl,
LoadingIndicator,
createListResource,
createResource,
debounce,
toast,
} from 'frappe-ui'
import { Plus, X } from 'lucide-vue-next'
import { Plus, Trash2, X } from 'lucide-vue-next'
import { ref } from 'vue'
import { cleanError } from '@/utils'
const showForm = ref(false)
const category = ref(null)
const categoryInput = ref(null)
const saving = ref(false)
const editing = ref(null)
const editedValue = ref('')
const editInputRef = ref([])
const props = defineProps({
label: {
@@ -72,25 +120,20 @@ const categories = createListResource({
auto: true,
})
const newCategory = createResource({
url: 'frappe.client.insert',
makeParams(values) {
return {
doc: {
doctype: 'LMS Category',
category: category.value,
},
}
},
})
const addCategory = () => {
newCategory.submit(
{},
categories.insert.submit(
{
category: category.value,
},
{
onSuccess(data) {
categories.reload()
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) => {
saving.value = true
updateCategory.submit(
{
name: name,
@@ -122,9 +166,51 @@ const update = (name, value) => {
},
{
onSuccess() {
saving.value = false
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>

View File

@@ -116,7 +116,7 @@
v-if="parseInt(course.data.rating) > 0"
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">
{{ course.data.rating }} {{ __('Rating') }}
</span>

View File

@@ -35,14 +35,14 @@
<span class="text-ink-gray-7">
{{ review.creation }}
</span>
<div class="flex mt-2">
<div class="flex mt-2 space-x-1">
<Star
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="
index <= Math.ceil(review.rating)
? 'fill-orange-500'
: 'fill-gray-600'
? 'fill-yellow-500'
: 'fill-gray-300'
"
/>
</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>
<div>
<div class="flex min-h-0 flex-col text-base">
<div class="flex items-center justify-between mb-4">
<div>
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
@@ -39,25 +39,27 @@
</Button>
</div>
<div class="divide-y">
<div
v-for="evaluator in evaluators.data"
@click="openProfile(evaluator.username)"
class="cursor-pointer"
>
<div class="flex items-center justify-between py-3">
<div class="flex items-center space-x-3">
<Avatar
:image="evaluator.user_image"
:label="evaluator.full_name"
size="lg"
/>
<div>
<div class="text-base font-semibold text-ink-gray-9">
{{ evaluator.full_name }}
</div>
<div class="text-xs text-ink-gray-5">
{{ evaluator.evaluator }}
<div class="overflow-y-scroll">
<div class="divide-y">
<div
v-for="evaluator in evaluators.data"
@click="openProfile(evaluator.username)"
class="cursor-pointer"
>
<div class="flex items-center justify-between py-3">
<div class="flex items-center space-x-3">
<Avatar
:image="evaluator.user_image"
:label="evaluator.full_name"
size="lg"
/>
<div>
<div class="text-base font-semibold text-ink-gray-9">
{{ evaluator.full_name }}
</div>
<div class="text-xs text-ink-gray-5">
{{ evaluator.evaluator }}
</div>
</div>
</div>
</div>

View File

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

View File

@@ -6,7 +6,7 @@
}"
>
<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">
{{
assignmentID === 'new'
@@ -14,7 +14,7 @@
: __('Edit Assignment')
}}
</div>
<div class="space-y-4">
<div class="space-y-4 max-h-[75vh] overflow-y-auto">
<FormControl
v-model="assignment.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>
</template>
<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 { formatTime } from '@/utils/'
@@ -90,7 +90,7 @@ const props = defineProps({
},
})
let evaluation = reactive({
const evaluation = reactive({
course: '',
date: '',
start_time: '',
@@ -139,7 +139,7 @@ function submitEvaluation(close) {
close()
},
onError(err) {
let message = err.messages?.[0] || err
const message = err.messages?.[0] || err
let unavailabilityMessage
if (typeof message === 'string') {
@@ -148,13 +148,13 @@ function submitEvaluation(close) {
unavailabilityMessage = false
}
toast.warning(__('Evaluator is unavailable'))
toast.warning(__(unavailabilityMessage || 'Evaluator is unavailable'))
},
})
}
const getCourses = () => {
let courses = []
const courses = []
for (const course of props.courses) {
if (course.evaluator) {
courses.push({
@@ -164,7 +164,7 @@ const getCourses = () => {
}
}
if (courses.length == 1) {
if (courses.length === 1) {
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>
<div class="flex flex-col gap-4">
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Rating') }}
</div>
<Rating v-model="review.rating" />
</div>
<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>
<Rating v-model="review.rating" :label="__('Rating')" />
<FormControl
:label="__('Review')"
type="textarea"
v-model="review.review"
:rows="5"
/>
</div>
</template>
</Dialog>
</template>
<script setup>
import { Dialog, Textarea, createResource, toast } from 'frappe-ui'
import { Dialog, FormControl, createResource, toast, Rating } from 'frappe-ui'
import { reactive } from 'vue'
import Rating from '@/components/Controls/Rating.vue'
const show = defineModel()
const reviews = defineModel('reloadReviews')

View File

@@ -51,6 +51,11 @@
:label="activeTab.label"
:description="activeTab.description"
/>
<EmailTemplates
v-else-if="activeTab.label === 'Email Templates'"
:label="activeTab.label"
:description="activeTab.description"
/>
<PaymentSettings
v-else-if="activeTab.label === 'Payment Gateway'"
:label="activeTab.label"
@@ -86,6 +91,7 @@ import SidebarLink from '@/components/SidebarLink.vue'
import Members from '@/components/Members.vue'
import Evaluators from '@/components/Evaluators.vue'
import Categories from '@/components/Categories.vue'
import EmailTemplates from '@/components/EmailTemplates.vue'
import BrandSettings from '@/components/BrandSettings.vue'
import PaymentSettings from '@/components/PaymentSettings.vue'
@@ -122,7 +128,7 @@ const tabsStructure = computed(() => {
label: 'Enable Learning Paths',
name: 'enable_learning_paths',
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',
},
{
@@ -139,11 +145,26 @@ const tabsStructure = computed(() => {
'If enabled, it sends google calendar invite to the student for evaluations.',
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',
name: 'unsplash_access_key',
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',
},
],
@@ -160,6 +181,12 @@ const tabsStructure = computed(() => {
description:
'Configure the payment gateway and other payment related settings',
fields: [
{
label: 'Default Currency',
name: 'default_currency',
type: 'Link',
doctype: 'Currency',
},
{
label: 'Payment Gateway',
name: 'payment_gateway',
@@ -167,10 +194,7 @@ const tabsStructure = computed(() => {
doctype: 'Payment Gateway',
},
{
label: 'Default Currency',
name: 'default_currency',
type: 'Link',
doctype: 'Currency',
type: 'Column Break',
},
{
label: 'Apply GST for India',
@@ -207,9 +231,14 @@ const tabsStructure = computed(() => {
},
{
label: 'Categories',
description: 'Manage the members of your learning system',
description: 'Double click to edit the category',
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',
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',
icon: 'LogIn',

View File

@@ -12,13 +12,13 @@
/> -->
</div>
<div class="overflow-y-scroll">
<div class="flex space-x-4">
<SettingFields :fields="fields" :data="data.doc" class="w-1/2" />
<div class="flex flex-col divide-y">
<SettingFields :fields="fields" :data="data.doc" />
<SettingFields
v-if="paymentGateway.data"
:fields="paymentGateway.data.fields"
:data="paymentGateway.data.data"
class="w-1/2"
class="pt-5 my-0"
/>
</div>
</div>
@@ -60,9 +60,28 @@ const paymentGateway = createResource({
payment_gateway: props.data.doc.payment_gateway,
}
},
transform(data) {
arrangeFields(data.fields)
return data
},
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({
url: 'frappe.client.set_value',
makeParams(values) {

View File

@@ -6,7 +6,7 @@
<div v-for="(column, index) in columns" :key="index">
<div
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">
<Link
@@ -14,6 +14,7 @@
v-model="data[field.name]"
:doctype="field.doctype"
:label="__(field.label)"
:description="__(field.description)"
/>
<div v-else-if="field.type == 'Code'">
@@ -54,11 +55,11 @@
<div v-else>
<div class="flex items-center text-sm space-x-2">
<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
:src="data[field.name]?.file_url || data[field.name]"
class="w-[80%] rounded"
class="size-6 rounded"
/>
</div>
<div class="flex flex-col flex-wrap">

View File

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

View File

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

View File

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

View File

@@ -20,14 +20,12 @@
</header>
<div class="p-5 pb-10">
<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"
>
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Batches') }}
</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"
>
<TabButtons
@@ -115,12 +113,10 @@ const is_student = computed(() => user.data?.is_student)
const currentTab = ref(is_student.value ? 'All' : 'Upcoming')
const orderBy = ref('start_date')
const readOnlyMode = window.read_only_mode
const batchCount = ref(0)
onMounted(() => {
setFiltersFromQuery()
updateBatches()
getBatchCount()
categories.value = [
{
label: '',
@@ -298,14 +294,6 @@ const canCreateBatch = () => {
return false
}
const getBatchCount = () => {
call('frappe.client.get_count', {
doctype: 'LMS Batch',
}).then((data) => {
batchCount.value = data
})
}
const breadcrumbs = computed(() => [
{
label: __('Batches'),

View File

@@ -20,7 +20,7 @@
:text="__('Average Rating')"
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">
{{ course.data.rating }}
</span>

View File

@@ -20,14 +20,12 @@
</header>
<div class="p-5 pb-10">
<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"
>
<div class="text-lg text-ink-gray-9 font-semibold">
{{ __('All Courses') }}
</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"
>
<TabButtons :buttons="courseTabs" v-model="currentTab" />
@@ -172,6 +170,8 @@ const identifyUserPersona = async () => {
}
const getCourseCount = () => {
if (!user.data) return
call('frappe.client.get_count', {
doctype: 'LMS Course',
}).then((data) => {

View File

@@ -1,98 +1,96 @@
import { useStorage } from "@vueuse/core";
import { call } from "frappe-ui";
import "../../../frappe/frappe/public/js/lib/posthog.js";
const APP = "lms";
const SITENAME = window.location.hostname;
import '../../../frappe/frappe/public/js/lib/posthog.js'
import { createResource } from 'frappe-ui'
declare global {
interface Window {
posthog: any;
posthog: any
}
}
const telemetry = useStorage("telemetry", {
enabled: false,
project_id: "",
host: "",
});
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;
}
type PosthogSettings = {
posthog_project_id: string
posthog_host: string
enable_telemetry: boolean
telemetry_site_age: number
}
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 {
data: {
user: string;
[key: string]: string | number | boolean | object;
};
user: string
[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,
options: CaptureOptions = { data: { user: "" } }
options: CaptureOptions = { data: { user: '' } },
) {
if (!telemetry.value.enabled) return;
window.posthog.capture(`${APP}_${event}`, options);
if (!isTelemetryEnabled()) return
window.posthog.capture(`lms_${event}`, options)
}
export function recordSession() {
if (!telemetry.value.enabled) return;
if (window.posthog && window.posthog.__loaded) {
window.posthog.startSessionRecording();
function startRecording() {
if (!isTelemetryEnabled()) return
if (window.posthog?.__loaded) {
window.posthog.startSessionRecording()
}
}
export function stopSession() {
if (!telemetry.value.enabled) return;
if (
window.posthog &&
window.posthog.__loaded &&
window.posthog.sessionRecordingStarted()
) {
window.posthog.stopSessionRecording();
}
function stopRecording() {
if (!isTelemetryEnabled()) return
if (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.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"
}
],
"grid_page_length": 50,
"index_web_pages_for_search": 1,
"links": [
{
@@ -371,8 +372,8 @@
"link_fieldname": "batch_name"
}
],
"modified": "2025-02-18 15:43:18.512504",
"modified_by": "Administrator",
"modified": "2025-05-21 13:30:28.904260",
"modified_by": "sayali@frappe.io",
"module": "LMS",
"name": "LMS Batch",
"owner": "Administrator",
@@ -412,12 +413,22 @@
"role": "Batch Evaluator",
"share": 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,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"title_field": "title",
"track_changes": 1
}
}

View File

@@ -103,7 +103,7 @@ class LMSBatch(Document):
frappe.throw(_("Seat count cannot be negative."))
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."))
def validate_timetable(self):

View File

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

View File

@@ -1,18 +1,12 @@
import frappe
from frappe.utils.telemetry import POSTHOG_HOST_FIELD, POSTHOG_PROJECT_FIELD
@frappe.whitelist()
def is_enabled():
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():
def get_posthog_settings():
return {
"project_id": frappe.conf.get("posthog_project_id"),
"telemetry_host": frappe.conf.get("posthog_host"),
"posthog_project_id": frappe.conf.get(POSTHOG_PROJECT_FIELD),
"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",
"version": "1.0.0",
"description": "Easy to use, open-source, Learning Management System",
"type": "module",
"workspaces": [
"frappe-ui",
"frontend"
],
"type": "module",
"scripts": {
"test-local": "cypress open --e2e --browser chrome",
"postinstall": "cd frontend && yarn install --check-files",
@@ -26,7 +26,8 @@
"homepage": "https://github.com/frappe/lms#readme",
"devDependencies": {
"cypress": "^13.9.0",
"cypress-file-upload": "^5.0.8"
"cypress-file-upload": "^5.0.8",
"cypress-real-events": "^1.14.0"
},
"dependencies": {
"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"
integrity sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==
"@babel/parser@^7.25.3":
"@babel/parser@^7.27.2":
version "7.27.2"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.2.tgz#577518bedb17a2ce4212afd052e01f7df0941127"
integrity sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==
@@ -262,9 +262,9 @@
"@codexteam/icons" "^0.0.6"
"@editorjs/table@^2.4.2":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@editorjs/table/-/table-2.4.4.tgz#724cbebd2c99b929bd41d31a147fdaf690ae2c81"
integrity sha512-2wWjxk48C9Z7uaBZIS5dML81c2VsD47Va9J4RDd2UboxcnYV8OZ4oYxKafH5RJIGPj8ykGaROIzyDVR1N4e7Cg==
version "2.4.5"
resolved "https://registry.yarnpkg.com/@editorjs/table/-/table-2.4.5.tgz#bb2ce9962935088e3d24a20e799acb9526b90e7b"
integrity sha512-pF48R2wc5m0c+N+RjtCLXBGZd23Rl7EjfSFpmcSViwNsu5RwMgYGrEiQ8mzVh98mbvYQwXm/NYBi9DEUUs970A==
dependencies:
"@codexteam/icons" "^0.0.6"
@@ -496,20 +496,20 @@
mlly "^1.7.4"
"@inquirer/confirm@^5.0.0":
version "5.1.10"
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.10.tgz#de3732cb7ae9333bd3e354afee6a6ef8cf28d951"
integrity sha512-FxbQ9giWxUWKUk2O5XZ6PduVnH2CZ/fmMKMBkH71MHJvWr7WL5AHKevhzF1L5uYWB2P548o1RzVxrNd3dpmk6g==
version "5.1.12"
resolved "https://registry.yarnpkg.com/@inquirer/confirm/-/confirm-5.1.12.tgz#387037889a5a558ceefe52e978228630aa6e7d0e"
integrity sha512-dpq+ielV9/bqgXRUbNH//KsY6WEw9DrGPmipkpmgC1Y46cwuBTNx7PXFWTjc3MQ+urcc0QxoVHcMI0FW4Ok0hg==
dependencies:
"@inquirer/core" "^10.1.11"
"@inquirer/type" "^3.0.6"
"@inquirer/core" "^10.1.13"
"@inquirer/type" "^3.0.7"
"@inquirer/core@^10.1.11":
version "10.1.11"
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.11.tgz#4022032b5b6b35970e1c3fcfc522bc250ef8810d"
integrity sha512-BXwI/MCqdtAhzNQlBEFE7CEflhPkl/BqvAuV/aK6lW3DClIfYVDWPP/kXuXHtBWC7/EEbNqd/1BGq2BGBBnuxw==
"@inquirer/core@^10.1.13":
version "10.1.13"
resolved "https://registry.yarnpkg.com/@inquirer/core/-/core-10.1.13.tgz#8f1ecfaba288fd2d705c7ac0690371464cf687b0"
integrity sha512-1viSxebkYN2nJULlzCxES6G9/stgHSepZ9LqqfdIGPHj5OHhiBUXVS0a6R0bEC2A+VL4D9w6QB66ebCr6HGllA==
dependencies:
"@inquirer/figures" "^1.0.11"
"@inquirer/type" "^3.0.6"
"@inquirer/figures" "^1.0.12"
"@inquirer/type" "^3.0.7"
ansi-escapes "^4.3.2"
cli-width "^4.1.0"
mute-stream "^2.0.0"
@@ -517,27 +517,27 @@
wrap-ansi "^6.2.0"
yoctocolors-cjs "^2.1.2"
"@inquirer/figures@^1.0.11":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.11.tgz#4744e6db95288fea1dead779554859710a959a21"
integrity sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==
"@inquirer/figures@^1.0.12":
version "1.0.12"
resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.12.tgz#667d6254cc7ba3b0c010a323d78024a1d30c6053"
integrity sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==
"@inquirer/type@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.6.tgz#2500e435fc2014c5250eec3279f42b70b64089bd"
integrity sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==
"@inquirer/type@^3.0.7":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@inquirer/type/-/type-3.0.7.tgz#b46bcf377b3172dbc768fdbd053e6492ad801a09"
integrity sha512-PfunHQcjwnju84L+ycmcMKB/pTPIngjUJvfnRhKY6FKPuYXlM4aQCb/nIdTFR6BEhMjFvngzvng/vBAJMZpLSA==
"@internationalized/date@^3.5.0", "@internationalized/date@^3.5.4":
version "3.8.0"
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.8.0.tgz#24fb301029224351381aa87cba853ca1093af094"
integrity sha512-J51AJ0fEL68hE4CwGPa6E0PO6JDaVLd8aln48xFCSy7CZkZc96dGEGmLs2OEEbBxcsVZtfrqkXJwI2/MSG8yKw==
version "3.8.1"
resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.8.1.tgz#fb3709440060a9efa0722615e83550e682e83221"
integrity sha512-PgVE6B6eIZtzf9Gu5HvJxRK3ufUFz9DhspELuhW/N0GuMGMTLvPQNRkHP2hTuP9lblOk+f+1xi96sPiPXANXAA==
dependencies:
"@swc/helpers" "^0.5.0"
"@internationalized/number@^3.5.0", "@internationalized/number@^3.5.3":
version "3.6.1"
resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.1.tgz#7c13cc55eb546aa3d42b8d5e7ac7db69a082fec7"
integrity sha512-UVsb4bCwbL944E0SX50CHFtWEeZ2uB5VozZ5yDXJdq6iPZsZO5p+bjVMZh2GxHf4Bs/7xtDCcPwEa2NU9DaG/g==
version "3.6.2"
resolved "https://registry.yarnpkg.com/@internationalized/number/-/number-3.6.2.tgz#504bf772238420c06b63ec58957c1cfcf6d92755"
integrity sha512-E5QTOlMg9wo5OrKdHD6edo1JJlIoOsylh0+mbf0evi1tHJwMZfJSaBpGtnJV9N7w3jeiioox9EG/EWRWPh82vg==
dependencies:
"@swc/helpers" "^0.5.0"
@@ -694,105 +694,105 @@
resolved "https://registry.yarnpkg.com/@remirror/core-constants/-/core-constants-3.0.0.tgz#96fdb89d25c62e7b6a5d08caf0ce5114370e3b8f"
integrity sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==
"@rollup/rollup-android-arm-eabi@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.2.tgz#c228d00a41f0dbd6fb8b7ea819bbfbf1c1157a10"
integrity sha512-JkdNEq+DFxZfUwxvB58tHMHBHVgX23ew41g1OQinthJ+ryhdRk67O31S7sYw8u2lTjHUPFxwar07BBt1KHp/hg==
"@rollup/rollup-android-arm-eabi@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.1.tgz#f39f09f60d4a562de727c960d7b202a2cf797424"
integrity sha512-NELNvyEWZ6R9QMkiytB4/L4zSEaBC03KIXEghptLGLZWJ6VPrL63ooZQCOnlx36aQPGhzuOMwDerC1Eb2VmrLw==
"@rollup/rollup-android-arm64@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.2.tgz#e2b38d0c912169fd55d7e38d723aada208d37256"
integrity sha512-13unNoZ8NzUmnndhPTkWPWbX3vtHodYmy+I9kuLxN+F+l+x3LdVF7UCu8TWVMt1POHLh6oDHhnOA04n8oJZhBw==
"@rollup/rollup-android-arm64@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.1.tgz#d19af7e23760717f1d879d4ca3d2cd247742dff2"
integrity sha512-DXdQe1BJ6TK47ukAoZLehRHhfKnKg9BjnQYUu9gzhI8Mwa1d2fzxA1aw2JixHVl403bwp1+/o/NhhHtxWJBgEA==
"@rollup/rollup-darwin-arm64@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.2.tgz#1fddb3690f2ae33df16d334c613377f05abe4878"
integrity sha512-Gzf1Hn2Aoe8VZzevHostPX23U7N5+4D36WJNHK88NZHCJr7aVMG4fadqkIf72eqVPGjGc0HJHNuUaUcxiR+N/w==
"@rollup/rollup-darwin-arm64@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.1.tgz#1c3a2fbf205d80641728e05f4a56c909e95218b7"
integrity sha512-5afxvwszzdulsU2w8JKWwY8/sJOLPzf0e1bFuvcW5h9zsEg+RQAojdW0ux2zyYAz7R8HvvzKCjLNJhVq965U7w==
"@rollup/rollup-darwin-x64@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.2.tgz#818298d11c8109e1112590165142f14be24b396d"
integrity sha512-47N4hxa01a4x6XnJoskMKTS8XZ0CZMd8YTbINbi+w03A2w4j1RTlnGHOz/P0+Bg1LaVL6ufZyNprSg+fW5nYQQ==
"@rollup/rollup-darwin-x64@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.1.tgz#aa66d2ba1a25e609500e13bef06dc0e71cc0c0d4"
integrity sha512-egpJACny8QOdHNNMZKf8xY0Is6gIMz+tuqXlusxquWu3F833DcMwmGM7WlvCO9sB3OsPjdC4U0wHw5FabzCGZg==
"@rollup/rollup-freebsd-arm64@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.2.tgz#91a28dc527d5bed7f9ecf0e054297b3012e19618"
integrity sha512-8t6aL4MD+rXSHHZUR1z19+9OFJ2rl1wGKvckN47XFRVO+QL/dUSpKA2SLRo4vMg7ELA8pzGpC+W9OEd1Z/ZqoQ==
"@rollup/rollup-freebsd-arm64@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.1.tgz#df10a7b6316a0ef1028c6ca71a081124c537e30d"
integrity sha512-DBVMZH5vbjgRk3r0OzgjS38z+atlupJ7xfKIDJdZZL6sM6wjfDNo64aowcLPKIx7LMQi8vybB56uh1Ftck/Atg==
"@rollup/rollup-freebsd-x64@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.2.tgz#28acadefa76b5c7bede1576e065b51d335c62c62"
integrity sha512-C+AyHBzfpsOEYRFjztcYUFsH4S7UsE9cDtHCtma5BK8+ydOZYgMmWg1d/4KBytQspJCld8ZIujFMAdKG1xyr4Q==
"@rollup/rollup-freebsd-x64@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.1.tgz#a3fdce8a05e95b068cbcb46e4df5185e407d0c35"
integrity sha512-3FkydeohozEskBxNWEIbPfOE0aqQgB6ttTkJ159uWOFn42VLyfAiyD9UK5mhu+ItWzft60DycIN1Xdgiy8o/SA==
"@rollup/rollup-linux-arm-gnueabihf@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.2.tgz#819691464179cbcd9a9f9d3dc7617954840c6186"
integrity sha512-de6TFZYIvJwRNjmW3+gaXiZ2DaWL5D5yGmSYzkdzjBDS3W+B9JQ48oZEsmMvemqjtAFzE16DIBLqd6IQQRuG9Q==
"@rollup/rollup-linux-arm-gnueabihf@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.1.tgz#49f766c55383bd0498014a9d76924348c2f3890c"
integrity sha512-wC53ZNDgt0pqx5xCAgNunkTzFE8GTgdZ9EwYGVcg+jEjJdZGtq9xPjDnFgfFozQI/Xm1mh+D9YlYtl+ueswNEg==
"@rollup/rollup-linux-arm-musleabihf@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.2.tgz#d149207039e4189e267e8724050388effc80d704"
integrity sha512-urjaEZubdIkacKc930hUDOfQPysezKla/O9qV+O89enqsqUmQm8Xj8O/vh0gHg4LYfv7Y7UsE3QjzLQzDYN1qg==
"@rollup/rollup-linux-arm-musleabihf@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.1.tgz#1d4d7d32fc557e17d52e1857817381ea365e2959"
integrity sha512-jwKCca1gbZkZLhLRtsrka5N8sFAaxrGz/7wRJ8Wwvq3jug7toO21vWlViihG85ei7uJTpzbXZRcORotE+xyrLA==
"@rollup/rollup-linux-arm64-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.2.tgz#fa72ebddb729c3c6d88973242f1a2153c83e86ec"
integrity sha512-KlE8IC0HFOC33taNt1zR8qNlBYHj31qGT1UqWqtvR/+NuCVhfufAq9fxO8BMFC22Wu0rxOwGVWxtCMvZVLmhQg==
"@rollup/rollup-linux-arm64-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.1.tgz#f4fc317268441e9589edad3be8f62b6c03009bc1"
integrity sha512-g0UBcNknsmmNQ8V2d/zD2P7WWfJKU0F1nu0k5pW4rvdb+BIqMm8ToluW/eeRmxCared5dD76lS04uL4UaNgpNA==
"@rollup/rollup-linux-arm64-musl@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.2.tgz#2054216e34469ab8765588ebf343d531fc3c9228"
integrity sha512-j8CgxvfM0kbnhu4XgjnCWJQyyBOeBI1Zq91Z850aUddUmPeQvuAy6OiMdPS46gNFgy8gN1xkYyLgwLYZG3rBOg==
"@rollup/rollup-linux-arm64-musl@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.1.tgz#63a1f1b0671cb17822dabae827fef0e443aebeb7"
integrity sha512-XZpeGB5TKEZWzIrj7sXr+BEaSgo/ma/kCgrZgL0oo5qdB1JlTzIYQKel/RmhT6vMAvOdM2teYlAaOGJpJ9lahg==
"@rollup/rollup-linux-loongarch64-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.2.tgz#818de242291841afbfc483a84f11e9c7a11959bc"
integrity sha512-Ybc/1qUampKuRF4tQXc7G7QY9YRyeVSykfK36Y5Qc5dmrIxwFhrOzqaVTNoZygqZ1ZieSWTibfFhQ5qK8jpWxw==
"@rollup/rollup-linux-loongarch64-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.1.tgz#c659b01cc6c0730b547571fc3973e1e955369f98"
integrity sha512-bkCfDJ4qzWfFRCNt5RVV4DOw6KEgFTUZi2r2RuYhGWC8WhCA8lCAJhDeAmrM/fdiAH54m0mA0Vk2FGRPyzI+tw==
"@rollup/rollup-linux-powerpc64le-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.2.tgz#0bb4cb8fc4a2c635f68c1208c924b2145eb647cb"
integrity sha512-3FCIrnrt03CCsZqSYAOW/k9n625pjpuMzVfeI+ZBUSDT3MVIFDSPfSUgIl9FqUftxcUXInvFah79hE1c9abD+Q==
"@rollup/rollup-linux-powerpc64le-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.1.tgz#612e746f9ad7e58480f964d65e0d6c3f4aae69a8"
integrity sha512-3mr3Xm+gvMX+/8EKogIZSIEF0WUu0HL9di+YWlJpO8CQBnoLAEL/roTCxuLncEdgcfJcvA4UMOf+2dnjl4Ut1A==
"@rollup/rollup-linux-riscv64-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.2.tgz#4b3b8e541b7b13e447ae07774217d98c06f6926d"
integrity sha512-QNU7BFHEvHMp2ESSY3SozIkBPaPBDTsfVNGx3Xhv+TdvWXFGOSH2NJvhD1zKAT6AyuuErJgbdvaJhYVhVqrWTg==
"@rollup/rollup-linux-riscv64-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.1.tgz#4610dbd1dcfbbae32fbc10c20ae7387acb31110c"
integrity sha512-3rwCIh6MQ1LGrvKJitQjZFuQnT2wxfU+ivhNBzmxXTXPllewOF7JR1s2vMX/tWtUYFgphygxjqMl76q4aMotGw==
"@rollup/rollup-linux-riscv64-musl@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.2.tgz#e065405e67d8bd64a7d0126c931bd9f03910817f"
integrity sha512-5W6vNYkhgfh7URiXTO1E9a0cy4fSgfE4+Hl5agb/U1sa0kjOLMLC1wObxwKxecE17j0URxuTrYZZME4/VH57Hg==
"@rollup/rollup-linux-riscv64-musl@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.1.tgz#054911fab40dc83fafc21e470193c058108f19d8"
integrity sha512-LdIUOb3gvfmpkgFZuccNa2uYiqtgZAz3PTzjuM5bH3nvuy9ty6RGc/Q0+HDFrHrizJGVpjnTZ1yS5TNNjFlklw==
"@rollup/rollup-linux-s390x-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.2.tgz#dda3265bbbfe16a5d0089168fd07f5ebb2a866fe"
integrity sha512-B7LKIz+0+p348JoAL4X/YxGx9zOx3sR+o6Hj15Y3aaApNfAshK8+mWZEf759DXfRLeL2vg5LYJBB7DdcleYCoQ==
"@rollup/rollup-linux-s390x-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.1.tgz#98896eca8012547c7f04bd07eaa6896825f9e1a5"
integrity sha512-oIE6M8WC9ma6xYqjvPhzZYk6NbobIURvP/lEbh7FWplcMO6gn7MM2yHKA1eC/GvYwzNKK/1LYgqzdkZ8YFxR8g==
"@rollup/rollup-linux-x64-gnu@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.2.tgz#90993269b8b995b4067b7b9d72ff1c360ef90a17"
integrity sha512-lG7Xa+BmBNwpjmVUbmyKxdQJ3Q6whHjMjzQplOs5Z+Gj7mxPtWakGHqzMqNER68G67kmCX9qX57aRsW5V0VOng==
"@rollup/rollup-linux-x64-gnu@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.1.tgz#01cf56844a1e636ee80dfb364e72c2b7142ad896"
integrity sha512-cWBOvayNvA+SyeQMp79BHPK8ws6sHSsYnK5zDcsC3Hsxr1dgTABKjMnMslPq1DvZIp6uO7kIWhiGwaTdR4Og9A==
"@rollup/rollup-linux-x64-musl@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.2.tgz#fdf5b09fd121eb8d977ebb0fda142c7c0167b8de"
integrity sha512-tD46wKHd+KJvsmije4bUskNuvWKFcTOIM9tZ/RrmIvcXnbi0YK/cKS9FzFtAm7Oxi2EhV5N2OpfFB348vSQRXA==
"@rollup/rollup-linux-x64-musl@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.1.tgz#e67c7531df6dff0b4c241101d4096617fbca87c3"
integrity sha512-y5CbN44M+pUCdGDlZFzGGBSKCA4A/J2ZH4edTYSSxFg7ce1Xt3GtydbVKWLlzL+INfFIZAEg1ZV6hh9+QQf9YQ==
"@rollup/rollup-win32-arm64-msvc@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.2.tgz#6397e1e012db64dfecfed0774cb9fcf89503d716"
integrity sha512-Bjv/HG8RRWLNkXwQQemdsWw4Mg+IJ29LK+bJPW2SCzPKOUaMmPEppQlu/Fqk1d7+DX3V7JbFdbkh/NMmurT6Pg==
"@rollup/rollup-win32-arm64-msvc@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.1.tgz#7eeada98444e580674de6989284e4baacd48ea65"
integrity sha512-lZkCxIrjlJlMt1dLO/FbpZbzt6J/A8p4DnqzSa4PWqPEUUUnzXLeki/iyPLfV0BmHItlYgHUqJe+3KiyydmiNQ==
"@rollup/rollup-win32-ia32-msvc@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.2.tgz#df0991464a52a35506103fe18d29913bf8798a0c"
integrity sha512-dt1llVSGEsGKvzeIO76HToiYPNPYPkmjhMHhP00T9S4rDern8P2ZWvWAQUEJ+R1UdMWJ/42i/QqJ2WV765GZcA==
"@rollup/rollup-win32-ia32-msvc@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.1.tgz#516c4b54f80587b4a390aaf4940b40870271d35d"
integrity sha512-+psFT9+pIh2iuGsxFYYa/LhS5MFKmuivRsx9iPJWNSGbh2XVEjk90fmpUEjCnILPEPJnikAU6SFDiEUyOv90Pg==
"@rollup/rollup-win32-x64-msvc@4.40.2":
version "4.40.2"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.2.tgz#8dae04d01a2cbd84d6297d99356674c6b993f0fc"
integrity sha512-bwspbWB04XJpeElvsp+DCylKfF4trJDa2Y9Go8O6A7YLX2LIKGcNK/CYImJN6ZP4DcuOHB4Utl3iCbnR62DudA==
"@rollup/rollup-win32-x64-msvc@4.41.1":
version "4.41.1"
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.1.tgz#848f99b0d9936d92221bb6070baeff4db6947a30"
integrity sha512-Wq2zpapRYLfi4aKxf2Xff0tN+7slj2d4R87WEzqw7ZLsVvO5zwYCIuEGSZYiK41+GlwUo1HiR+GdkLEJnCKTCw==
"@socket.io/component-emitter@~3.1.0":
version "3.1.2"
@@ -850,17 +850,17 @@
lodash.merge "^4.6.2"
postcss-selector-parser "6.0.10"
"@tanstack/virtual-core@3.13.8":
version "3.13.8"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.13.8.tgz#6346e688521c1f086f508ccbebaad0b472a2aefb"
integrity sha512-BT6w89Hqy7YKaWewYzmecXQzcJh6HTBbKYJIIkMaNU49DZ06LoTV3z32DWWEdUsgW6n1xTmwTLs4GtWrZC261w==
"@tanstack/virtual-core@3.13.9":
version "3.13.9"
resolved "https://registry.yarnpkg.com/@tanstack/virtual-core/-/virtual-core-3.13.9.tgz#62b4d2d4351d658101664beacf088fbd061190bf"
integrity sha512-3jztt0jpaoJO5TARe2WIHC1UQC3VMLAFUW5mmMo0yrkwtDB2AQP0+sh10BVUpWrnvHjSLvzFizydtEGLCJKFoQ==
"@tanstack/vue-virtual@^3.0.0-beta.60", "@tanstack/vue-virtual@^3.12.0", "@tanstack/vue-virtual@^3.8.1":
version "3.13.8"
resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.13.8.tgz#5df214b258e6f62ce775a2bfa8d8919043a2bb60"
integrity sha512-CqyjKVc88YlE8JPth8a5Gi4CUoYrwJ2PZxtFbhoekx8Z2qqymxX2jzkbUMKFsX4EVNET90D5bLsG3epyozbzcg==
version "3.13.9"
resolved "https://registry.yarnpkg.com/@tanstack/vue-virtual/-/vue-virtual-3.13.9.tgz#4f5eaab36a511a93123f27c41aca8d0bac51d1ac"
integrity sha512-HsvHaOo+o52cVcPhomKDZ3CMpTF/B2qg+BhPHIQJwzn4VIqDyt/rRVqtIomG6jE83IFsE2vlr6cmx7h3dHA0SA==
dependencies:
"@tanstack/virtual-core" "3.13.8"
"@tanstack/virtual-core" "3.13.9"
"@tiptap/core@^2.11.7", "@tiptap/core@^2.12.0":
version "2.12.0"
@@ -1170,9 +1170,9 @@
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/node@*":
version "22.15.17"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.17.tgz#355ccec95f705b664e4332bb64a7f07db30b7055"
integrity sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==
version "22.15.21"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.15.21.tgz#196ef14fe20d87f7caf1e7b39832767f9a995b77"
integrity sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==
dependencies:
undici-types "~6.21.0"
@@ -1287,90 +1287,90 @@
loupe "^3.1.2"
tinyrainbow "^1.2.0"
"@vue/compiler-core@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz#b0ae6c4347f60c03e849a05d34e5bf747c9bda05"
integrity sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==
"@vue/compiler-core@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.5.14.tgz#3676685c04c48a5b4a5515b3b2842e98342c555c"
integrity sha512-k7qMHMbKvoCXIxPhquKQVw3Twid3Kg4s7+oYURxLGRd56LiuHJVrvFKI4fm2AM3c8apqODPfVJGoh8nePbXMRA==
dependencies:
"@babel/parser" "^7.25.3"
"@vue/shared" "3.5.13"
"@babel/parser" "^7.27.2"
"@vue/shared" "3.5.14"
entities "^4.5.0"
estree-walker "^2.0.2"
source-map-js "^1.2.0"
source-map-js "^1.2.1"
"@vue/compiler-dom@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58"
integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==
"@vue/compiler-dom@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.14.tgz#bbf27421f80f7b8873000edceecd817c4abf438a"
integrity sha512-1aOCSqxGOea5I80U2hQJvXYpPm/aXo95xL/m/mMhgyPUsKe9jhjwWpziNAw7tYRnbz1I61rd9Mld4W9KmmRoug==
dependencies:
"@vue/compiler-core" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-core" "3.5.14"
"@vue/shared" "3.5.14"
"@vue/compiler-sfc@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
"@vue/compiler-sfc@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.14.tgz#fc3db30a1c744139d41bb57bb451d783415fce4b"
integrity sha512-9T6m/9mMr81Lj58JpzsiSIjBgv2LiVoWjIVa7kuXHICUi8LiDSIotMpPRXYJsXKqyARrzjT24NAwttrMnMaCXA==
dependencies:
"@babel/parser" "^7.25.3"
"@vue/compiler-core" "3.5.13"
"@vue/compiler-dom" "3.5.13"
"@vue/compiler-ssr" "3.5.13"
"@vue/shared" "3.5.13"
"@babel/parser" "^7.27.2"
"@vue/compiler-core" "3.5.14"
"@vue/compiler-dom" "3.5.14"
"@vue/compiler-ssr" "3.5.14"
"@vue/shared" "3.5.14"
estree-walker "^2.0.2"
magic-string "^0.30.11"
postcss "^8.4.48"
source-map-js "^1.2.0"
magic-string "^0.30.17"
postcss "^8.5.3"
source-map-js "^1.2.1"
"@vue/compiler-ssr@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz#e771adcca6d3d000f91a4277c972a996d07f43ba"
integrity sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==
"@vue/compiler-ssr@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.5.14.tgz#013174ee6bbf3ee291a6df247a3feb6eb43d808b"
integrity sha512-Y0G7PcBxr1yllnHuS/NxNCSPWnRGH4Ogrp0tsLA5QemDZuJLs99YjAKQ7KqkHE0vCg4QTKlQzXLKCMF7WPSl7Q==
dependencies:
"@vue/compiler-dom" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-dom" "3.5.14"
"@vue/shared" "3.5.14"
"@vue/devtools-api@^6.6.3", "@vue/devtools-api@^6.6.4":
version "6.6.4"
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
"@vue/reactivity@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f"
integrity sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==
"@vue/reactivity@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.14.tgz#814fb4ba84a9560d2752b9982fdd2b76e4a5e5a3"
integrity sha512-7cK1Hp343Fu/SUCCO52vCabjvsYu7ZkOqyYu7bXV9P2yyfjUMUXHZafEbq244sP7gf+EZEz+77QixBTuEqkQQw==
dependencies:
"@vue/shared" "3.5.13"
"@vue/shared" "3.5.14"
"@vue/runtime-core@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz#1fafa4bf0b97af0ebdd9dbfe98cd630da363a455"
integrity sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==
"@vue/runtime-core@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.5.14.tgz#f4084cad032be3452d8f137035fcd93c182f7149"
integrity sha512-w9JWEANwHXNgieAhxPpEpJa+0V5G0hz3NmjAZwlOebtfKyp2hKxKF0+qSh0Xs6/PhfGihuSdqMprMVcQU/E6ag==
dependencies:
"@vue/reactivity" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/reactivity" "3.5.14"
"@vue/shared" "3.5.14"
"@vue/runtime-dom@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz#610fc795de9246300e8ae8865930d534e1246215"
integrity sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==
"@vue/runtime-dom@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.5.14.tgz#59ea4a5fe3ed93fb8f725c1c722a0fe8d8ae16cf"
integrity sha512-lCfR++IakeI35TVR80QgOelsUIdcKjd65rWAMfdSlCYnaEY5t3hYwru7vvcWaqmrK+LpI7ZDDYiGU5V3xjMacw==
dependencies:
"@vue/reactivity" "3.5.13"
"@vue/runtime-core" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/reactivity" "3.5.14"
"@vue/runtime-core" "3.5.14"
"@vue/shared" "3.5.14"
csstype "^3.1.3"
"@vue/server-renderer@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz#429ead62ee51de789646c22efe908e489aad46f7"
integrity sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==
"@vue/server-renderer@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.5.14.tgz#adcaf30ddcf0064a28ce832d29f430bd0db3ef18"
integrity sha512-Rf/ISLqokIvcySIYnv3tNWq40PLpNLDLSJwwVWzG6MNtyIhfbcrAxo5ZL9nARJhqjZyWWa40oRb2IDuejeuv6w==
dependencies:
"@vue/compiler-ssr" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-ssr" "3.5.14"
"@vue/shared" "3.5.14"
"@vue/shared@3.5.13":
version "3.5.13"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
"@vue/shared@3.5.14":
version "3.5.14"
resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.14.tgz#8fcdc6c69661a1163c173cafb6129c3f8ad01122"
integrity sha512-oXTwNxVfc9EtP1zzXAlSlgARLXNC84frFYkS0HHz0h3E4WZSP9sywqjqzGCP9Y34M8ipNmd380pVgmMuwELDyQ==
"@vueuse/core@^10.11.0", "@vueuse/core@^10.4.1":
version "10.11.1"
@@ -1563,9 +1563,9 @@ argparse@^2.0.1:
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522"
integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==
version "1.2.6"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.6.tgz#73051c9b088114c795b1ea414e9c0fff874ffc1a"
integrity sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==
dependencies:
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"
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:
version "13.17.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.17.0.tgz#34c3d68080c4497eace0f353bd1629587a5f600d"
@@ -2184,10 +2189,10 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.1.1, debug@^4.3.4, debug@^4.3.7, debug@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
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.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.1.tgz#e5a8bc6cbc4c6cd3e64308b0693a3d4fa550189b"
integrity sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==
dependencies:
ms "^2.1.3"
@@ -2322,9 +2327,9 @@ ee-first@1.1.1:
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.149:
version "1.5.152"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz#bcdd39567e291b930ec26b930031137a05593695"
integrity sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==
version "1.5.157"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz#553b122522ac7bba6f1a0dd7d50b14f297736f75"
integrity sha512-/0ybgsQd1muo8QlnuTpKwtl0oX5YMlUGbm8xyqgDU00motRkKFFbUJySAQBWcY79rVqNLWIWa87BGVGClwAB2w==
emoji-regex@^10.3.0:
version "10.4.0"
@@ -2662,9 +2667,9 @@ finalhandler@1.1.2:
unpipe "~1.0.0"
flexsearch@*:
version "0.8.164"
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.164.tgz#2d1277249d6dec8eb745358fa64543ddbddc9e05"
integrity sha512-tauQG+NlwSWW6uL3BIJdHNEXYiei2xTR3H/mrjNadAOwvXgiLbLvLzQWSJvD31Yn0+1lAxG2NVRndywGnDZZiA==
version "0.8.204"
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.8.204.tgz#843f295e8f2aa3b3326d4bbee3ebe761a4a73cec"
integrity sha512-Vh+WUZfUHsVP6w4o5uAkYle8Gz/oEuztSWvpSY3h71AE8ox+goTQ2X5YG4x6VlKKfubkMwhewk8kBTOVKMObHA==
flexsearch@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"
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:
version "10.1.0"
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"
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"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
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==
msw@^2.7.0:
version "2.8.2"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.8.2.tgz#a35545275403da472f4ed2152cd3c77000db544c"
integrity sha512-ugu8RBgUj6//RD0utqDDPdS+QIs36BKYkDAM6u59hcMVtFM4PM0vW4l3G1R+1uCWP2EWFUG8reT/gPXVEtx7/w==
version "2.8.4"
resolved "https://registry.yarnpkg.com/msw/-/msw-2.8.4.tgz#e61f50f5bc891e5b81655e4450650b587b761dd5"
integrity sha512-GLU8gx0o7RBG/3x/eTnnLd5S5ZInxXRRRMN8GJwaPZ4jpJTxzQfWGvwr90e8L5dkKJnz+gT4gQYCprLy/c4kVw==
dependencies:
"@bundled-es-modules/cookie" "^2.0.1"
"@bundled-es-modules/statuses" "^1.0.1"
@@ -3617,9 +3674,9 @@ mz@^2.7.0:
thenify-all "^1.0.0"
nano-spawn@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.1.tgz#c8e4c1e133e567e3efba44041dcfb12113d861b6"
integrity sha512-BfcvzBlUTxSDWfT+oH7vd6CbUV+rThLLHCIym/QO6GGLBsyVXleZs00fto2i2jzC/wPiBYk5jyOmpXWg4YopiA==
version "1.0.2"
resolved "https://registry.yarnpkg.com/nano-spawn/-/nano-spawn-1.0.2.tgz#9853795681f0e96ef6f39104c2e4347b6ba79bf6"
integrity sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==
nanoid@^3.3.8:
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"
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"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
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-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"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.39.2.tgz#178743c9694fec5ed498d48e46d4a31bc1ef0936"
integrity sha512-BmOkml0QWNob165gyUxXi5K5CVUgVPpqMEAAml/qzgKn9boLUWVPzQ6LtzXw8Cn1GtRQX4ELumPxqtLTDaAKtg==
version "1.39.3"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.39.3.tgz#54fa4b8ab4fd75ad0075dc6dc0be1745429d5a5c"
integrity sha512-bY/7kg0LzRE7ytR0zRdSMWX3sknEjw68l836ffLPMh0OG3OYnNuBDUSF3v0vjvnzgYjgY9ZH/RypbARURlcMFA==
dependencies:
prosemirror-model "^1.20.0"
prosemirror-state "^1.0.0"
@@ -4355,32 +4412,32 @@ rfdc@^1.3.0, rfdc@^1.4.1:
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
rollup@^4.20.0:
version "4.40.2"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.40.2.tgz#778e88b7a197542682b3e318581f7697f55f0619"
integrity sha512-tfUOg6DTP4rhQ3VjOO6B4wyrJnGOX85requAXvqYTHsOgb2TFJdZ3aWpT8W2kPoypSGP7dZUyzxJ9ee4buM5Fg==
version "4.41.1"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.41.1.tgz#46ddc1b33cf1b0baa99320d3b0b4973dc2253b6a"
integrity sha512-cPmwD3FnFv8rKMBc1MxWCwVQFxwf1JEmSX3iQXrRVVG15zerAIXRjMFVWnd5Q5QvgKF7Aj+5ykXFhUl+QGnyOw==
dependencies:
"@types/estree" "1.0.7"
optionalDependencies:
"@rollup/rollup-android-arm-eabi" "4.40.2"
"@rollup/rollup-android-arm64" "4.40.2"
"@rollup/rollup-darwin-arm64" "4.40.2"
"@rollup/rollup-darwin-x64" "4.40.2"
"@rollup/rollup-freebsd-arm64" "4.40.2"
"@rollup/rollup-freebsd-x64" "4.40.2"
"@rollup/rollup-linux-arm-gnueabihf" "4.40.2"
"@rollup/rollup-linux-arm-musleabihf" "4.40.2"
"@rollup/rollup-linux-arm64-gnu" "4.40.2"
"@rollup/rollup-linux-arm64-musl" "4.40.2"
"@rollup/rollup-linux-loongarch64-gnu" "4.40.2"
"@rollup/rollup-linux-powerpc64le-gnu" "4.40.2"
"@rollup/rollup-linux-riscv64-gnu" "4.40.2"
"@rollup/rollup-linux-riscv64-musl" "4.40.2"
"@rollup/rollup-linux-s390x-gnu" "4.40.2"
"@rollup/rollup-linux-x64-gnu" "4.40.2"
"@rollup/rollup-linux-x64-musl" "4.40.2"
"@rollup/rollup-win32-arm64-msvc" "4.40.2"
"@rollup/rollup-win32-ia32-msvc" "4.40.2"
"@rollup/rollup-win32-x64-msvc" "4.40.2"
"@rollup/rollup-android-arm-eabi" "4.41.1"
"@rollup/rollup-android-arm64" "4.41.1"
"@rollup/rollup-darwin-arm64" "4.41.1"
"@rollup/rollup-darwin-x64" "4.41.1"
"@rollup/rollup-freebsd-arm64" "4.41.1"
"@rollup/rollup-freebsd-x64" "4.41.1"
"@rollup/rollup-linux-arm-gnueabihf" "4.41.1"
"@rollup/rollup-linux-arm-musleabihf" "4.41.1"
"@rollup/rollup-linux-arm64-gnu" "4.41.1"
"@rollup/rollup-linux-arm64-musl" "4.41.1"
"@rollup/rollup-linux-loongarch64-gnu" "4.41.1"
"@rollup/rollup-linux-powerpc64le-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-gnu" "4.41.1"
"@rollup/rollup-linux-riscv64-musl" "4.41.1"
"@rollup/rollup-linux-s390x-gnu" "4.41.1"
"@rollup/rollup-linux-x64-gnu" "4.41.1"
"@rollup/rollup-linux-x64-musl" "4.41.1"
"@rollup/rollup-win32-arm64-msvc" "4.41.1"
"@rollup/rollup-win32-ia32-msvc" "4.41.1"
"@rollup/rollup-win32-x64-msvc" "4.41.1"
fsevents "~2.3.2"
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"
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"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
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"
integrity sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==
tinyglobby@^0.2.12:
version "0.2.13"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e"
integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
tinyglobby@^0.2.14:
version "0.2.14"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d"
integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==
dependencies:
fdir "^6.4.4"
picomatch "^4.0.2"
@@ -5104,23 +5161,23 @@ unplugin-utils@^0.2.4:
picomatch "^4.0.2"
unplugin-vue-components@^28.4.1:
version "28.5.0"
resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-28.5.0.tgz#33585a24c98939d1abe56bd69217bc7187ba329f"
integrity sha512-o7fMKU/uI8NiP+E0W62zoduuguWqB0obTfHFtbr1AP2uo2lhUPnPttWUE92yesdiYfo9/0hxIrj38FMc1eaySg==
version "28.7.0"
resolved "https://registry.yarnpkg.com/unplugin-vue-components/-/unplugin-vue-components-28.7.0.tgz#e61e5a267a951fbace3190e0dfc61268a00f1b3a"
integrity sha512-3SuWAHlTjOiZckqRBGXRdN/k6IMmKyt2Ch5/+DKwYaT321H0ItdZDvW4r8/YkEKQpN9TN3F/SZ0W342gQROC3Q==
dependencies:
chokidar "^3.6.0"
debug "^4.4.0"
debug "^4.4.1"
local-pkg "^1.1.1"
magic-string "^0.30.17"
mlly "^1.7.4"
tinyglobby "^0.2.12"
unplugin "^2.3.2"
tinyglobby "^0.2.14"
unplugin "^2.3.4"
unplugin-utils "^0.2.4"
unplugin@^2.2.0, unplugin@^2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.3.tgz#f83507e4484008e400f3d831a628eaede22c954f"
integrity sha512-DN4DgiS13HFrAapoRmmoa9B35QzmQVRH2k58HelO28htXVNEEFZ8CGlZL0aRHXOXtz9McwY6lqaZjcc15uWMow==
unplugin@^2.2.0, unplugin@^2.3.4:
version "2.3.4"
resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.4.tgz#f3cf35f36656404cb9078be59f993941d649d87c"
integrity sha512-m4PjxTurwpWfpMomp8AptjD5yj8qEZN5uQjjGM3TAs9MWWD2tXSSNNj6jGR2FoVGod4293ytyV6SwBbertfyJg==
dependencies:
acorn "^8.14.1"
picomatch "^4.0.2"
@@ -5278,15 +5335,15 @@ vue3-apexcharts@^1.8.0:
integrity sha512-5tSD4mXTBbIJ9ir+58qHE6oNtIe0RNgqIRYMKpcsIaxkKtwUww4JhvPkpUFlmiW4OJbbdklgjleXq1lfcM4gdA==
vue@^3.3.0, vue@^3.4.23, vue@^3.5.13:
version "3.5.13"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
version "3.5.14"
resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.14.tgz#0ddf16d20cc20adaedfb5e77bca64c488bf5ee27"
integrity sha512-LbOm50/vZFG6Mhy6KscQYXZMQ0LMCC/y40HDJPPvGFQ+i/lUH+PJHR6C3assgOQiXdl6tAfsXHbXYVBZZu65ew==
dependencies:
"@vue/compiler-dom" "3.5.13"
"@vue/compiler-sfc" "3.5.13"
"@vue/runtime-dom" "3.5.13"
"@vue/server-renderer" "3.5.13"
"@vue/shared" "3.5.13"
"@vue/compiler-dom" "3.5.14"
"@vue/compiler-sfc" "3.5.14"
"@vue/runtime-dom" "3.5.14"
"@vue/server-renderer" "3.5.14"
"@vue/shared" "3.5.14"
vuedraggable@4.1.0:
version "4.1.0"
@@ -5459,9 +5516,9 @@ yallist@^2.1.2:
integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==
yaml@^2.3.4, yaml@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.1.tgz#44a247d1b88523855679ac7fa7cda6ed7e135cf6"
integrity sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==
version "2.8.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.8.0.tgz#15f8c9866211bdc2d3781a0890e44d4fa1a5fff6"
integrity sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==
yargs-parser@^21.1.1:
version "21.1.1"