Compare commits

..

61 Commits

Author SHA1 Message Date
Frappe PR Bot
3025ea9a7b chore(release): Bumped to Version 2.29.0 2025-05-26 10:05:36 +00:00
Jannat Patel
e4f1e7b093 Merge pull request #1536 from pateljannat/telemetry-fixes
chore: fix posthog init condition
2025-05-26 15:21:34 +05:30
Jannat Patel
d0a0597087 chore: removed unused imports 2025-05-26 15:08:41 +05:30
Jannat Patel
c9ccf9a1b5 chore: fix posthog init condition 2025-05-26 15:02:51 +05:30
Jannat Patel
69107d4441 Merge pull request #1535 from pateljannat/refactor-batch-charts
refactor: use frappe-ui for batch progress charts
2025-05-26 12:43:15 +05:30
Jannat Patel
e25afc1ef7 chore: fixed formating 2025-05-26 12:32:34 +05:30
Jannat Patel
9babfd150e fix: course count on batch dashboard 2025-05-26 12:25:16 +05:30
Jannat Patel
532dbbea4a fix: restricted minimum chart interval to 1 2025-05-26 11:34:52 +05:30
Jannat Patel
0d284d05d9 Merge pull request #1534 from pateljannat/issues-110
fix: misc issues
2025-05-26 11:22:16 +05:30
Jannat Patel
28fccae3ac Merge pull request #1532 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-05-26 11:05:01 +05:30
Jannat Patel
3a4a6da69c Merge pull request #1531 from frappe/pot_develop_2025-05-23
chore: update POT file
2025-05-26 11:04:50 +05:30
Jannat Patel
4ea07a95e7 fix: show batch CTA's on mobile 2025-05-26 11:04:00 +05:30
Jannat Patel
80ceb49358 fix: login menu now works on all browsers and devices 2025-05-26 10:58:17 +05:30
Jannat Patel
589337116a fix: added dependencies for onboarding steps 2025-05-26 09:59:05 +05:30
Jannat Patel
cb50067223 chore: Chinese Simplified translations 2025-05-24 23:16:12 +05:30
Jannat Patel
4d63266d88 chore: Serbian (Latin) translations 2025-05-24 23:16:11 +05:30
Jannat Patel
90dd33ce21 chore: Serbian (Latin) translations 2025-05-23 23:13:03 +05:30
frappe-pr-bot
763b849ddf chore: update POT file 2025-05-23 16:04:27 +00:00
Jannat Patel
9c76c54283 Merge pull request #1528 from pateljannat/email-template-list
feat: email template in settings
2025-05-23 15:35:11 +05:30
Md Hussain Nagaria
5cb17b3a36 Merge pull request #1529 from frappe/misc-fixes 2025-05-23 11:01:25 +02:00
Hussain Nagaria
2f7b5d1cbb fix: use unavailabilityMessage if set 2025-05-23 14:28:47 +05:30
Hussain Nagaria
4fe14eb2e9 fix: early return cleanup 2025-05-23 14:26:22 +05:30
Jannat Patel
eb089f2b58 fix: return payment fields data after transform 2025-05-23 14:25:29 +05:30
Hussain Nagaria
4f0ac98eea fix: toast used but not imported 2025-05-23 14:02:04 +05:30
Hussain Nagaria
af19940fa1 fix: some code semantics 2025-05-23 14:00:11 +05:30
Jannat Patel
5635d2a325 feat: email template update and deletion 2025-05-23 13:28:18 +05:30
Jannat Patel
5e2de35693 refactor: layout of payment fields 2025-05-22 21:39:49 +05:30
Jannat Patel
ef7180f23f Merge pull request #1523 from pateljannat/issues-109
refactor: misc enhancements
2025-05-22 12:12:57 +05:30
Jannat Patel
f939973d4f fix: don't validate number of students if seat count is 0 in batch 2025-05-22 12:03:07 +05:30
Jannat Patel
63f327733e refactor: category list in settings 2025-05-22 11:54:54 +05:30
Jannat Patel
c1fb807fe4 fix: show onboarding banner when redirected from other pages 2025-05-21 17:52:24 +05:30
Jannat Patel
b7ddf44267 test: close onboaring popover before creating course 2025-05-21 17:35:48 +05:30
Jannat Patel
6d4c72ea5e fix: rating input style on course details page 2025-05-21 16:28:04 +05:30
Jannat Patel
3db11b9372 refactor: moved batch feedback to sidebar 2025-05-21 16:08:49 +05:30
Jannat Patel
b8714f4abe refactor: batch progress chart will now use frappe-ui components 2025-05-21 13:13:42 +05:30
Jannat Patel
7ccbe74bbe chore: fixed conflicts 2025-05-20 19:09:19 +05:30
Jannat Patel
ea3ae3516b Merge pull request #1513 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-05-20 19:05:38 +05:30
Jannat Patel
d33af3ca52 chore: Esperanto translations 2025-05-19 21:33:54 +05:30
Jannat Patel
291c3fa908 chore: Serbian (Latin) translations 2025-05-19 21:33:53 +05:30
Jannat Patel
a51fa58122 chore: Bosnian translations 2025-05-19 21:33:52 +05:30
Jannat Patel
65a3967abd chore: Croatian translations 2025-05-19 21:33:50 +05:30
Jannat Patel
e1e5c94a43 chore: Thai translations 2025-05-19 21:33:49 +05:30
Jannat Patel
f15127eceb chore: Chinese Simplified translations 2025-05-19 21:33:47 +05:30
Jannat Patel
071a238b71 chore: Persian translations 2025-05-19 21:33:46 +05:30
Jannat Patel
050b052156 chore: Portuguese, Brazilian translations 2025-05-19 21:33:44 +05:30
Jannat Patel
8f65cca776 chore: Turkish translations 2025-05-19 21:33:43 +05:30
Jannat Patel
66624a8c47 chore: Swedish translations 2025-05-19 21:33:41 +05:30
Jannat Patel
c8b9a415e6 chore: Russian translations 2025-05-19 21:33:40 +05:30
Jannat Patel
a1dcb4c203 chore: Portuguese translations 2025-05-19 21:33:39 +05:30
Jannat Patel
d4edc3e622 chore: Polish translations 2025-05-19 21:33:37 +05:30
Jannat Patel
e2b8c3ee0e chore: Hungarian translations 2025-05-19 21:33:35 +05:30
Jannat Patel
c37816e90d chore: German translations 2025-05-19 21:33:34 +05:30
Jannat Patel
a35cfcdca7 chore: Arabic translations 2025-05-19 21:33:32 +05:30
Jannat Patel
d381646226 chore: Spanish translations 2025-05-19 21:33:31 +05:30
Jannat Patel
285e7afec2 chore: French translations 2025-05-19 21:33:30 +05:30
Jannat Patel
df7d678c32 Merge pull request #1510 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-05-19 14:24:21 +05:30
Jannat Patel
f36f7e58de Merge pull request #1511 from frappe/pot_develop_2025-05-16
chore: update POT file
2025-05-19 14:24:10 +05:30
Jannat Patel
0e16c834d8 chore: Serbian (Latin) translations 2025-05-18 21:37:27 +05:30
frappe-pr-bot
31a3256128 chore: update POT file 2025-05-16 16:04:20 +00:00
Jannat Patel
aa8f70da28 chore: Swedish translations 2025-05-16 21:09:45 +05:30
Jannat Patel
0d41a1ae70 refactor: use frappe-ui for batch progress charts 2025-05-12 10:37:26 +05:30
57 changed files with 8114 additions and 8208 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,13 +18,13 @@
</div> </div>
<div class="overflow-y-auto"> <div class="overflow-y-auto">
<SettingFields :fields="fields" :data="data.data" /> <SettingFields :fields="fields" :data="data.data" />
</div>
<div class="flex flex-row-reverse mt-auto"> <div class="flex flex-row-reverse mt-auto">
<Button variant="solid" :loading="saveSettings.loading" @click="update"> <Button variant="solid" :loading="saveSettings.loading" @click="update">
{{ __('Update') }} {{ __('Update') }}
</Button> </Button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { createResource, Button, Badge } from 'frappe-ui' import { createResource, Button, Badge } from 'frappe-ui'

View File

@@ -1,9 +1,24 @@
<template> <template>
<div class="flex flex-col min-h-0"> <div class="flex flex-col min-h-0 text-base">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between mb-5">
<div class="text-xl font-semibold mb-5 text-ink-gray-9"> <div class="flex flex-col space-y-2">
<div class="text-xl font-semibold text-ink-gray-9">
{{ label }} {{ label }}
</div> </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()"> <Button @click="() => showCategoryForm()">
<template #prefix> <template #prefix>
<Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" /> <Plus v-if="!showForm" class="h-3 w-3 stroke-1.5" />
@@ -12,6 +27,7 @@
{{ showForm ? __('Close') : __('New') }} {{ showForm ? __('Close') : __('New') }}
</Button> </Button>
</div> </div>
</div>
<div <div
v-if="showForm" v-if="showForm"
@@ -29,31 +45,63 @@
</div> </div>
<div class="overflow-y-scroll"> <div class="overflow-y-scroll">
<div class="text-base space-y-2"> <div class="divide-y space-y-2">
<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 <FormControl
:value="cat.category" v-else
:ref="(el) => (editInputRef[index] = el)"
v-model="editedValue"
type="text" type="text"
v-for="cat in categories.data" class="w-full"
@change.stop="(e) => update(cat.name, e.target.value)" @keyup.enter="saveChanges(cat.name, editedValue)"
/> />
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup> <script setup>
import { import {
Button, Button,
FormControl, FormControl,
LoadingIndicator,
createListResource, createListResource,
createResource, createResource,
debounce, toast,
} from 'frappe-ui' } from 'frappe-ui'
import { Plus, X } from 'lucide-vue-next' import { Plus, Trash2, X } from 'lucide-vue-next'
import { ref } from 'vue' import { ref } from 'vue'
import { cleanError } from '@/utils'
const showForm = ref(false) const showForm = ref(false)
const category = ref(null) const category = ref(null)
const categoryInput = ref(null) const categoryInput = ref(null)
const saving = ref(false)
const editing = ref(null)
const editedValue = ref('')
const editInputRef = ref([])
const props = defineProps({ const props = defineProps({
label: { label: {
@@ -72,25 +120,20 @@ const categories = createListResource({
auto: true, auto: true,
}) })
const newCategory = createResource({ const addCategory = () => {
url: 'frappe.client.insert', categories.insert.submit(
makeParams(values) { {
return {
doc: {
doctype: 'LMS Category',
category: category.value, category: category.value,
}, },
}
},
})
const addCategory = () => {
newCategory.submit(
{},
{ {
onSuccess(data) { onSuccess(data) {
categories.reload() categories.reload()
category.value = null category.value = null
showForm.value = false
toast.success(__('Category added successfully'))
},
onError(err) {
toast.error(__(cleanError(err.messages[0]) || 'Unable to add category'))
}, },
} }
) )
@@ -115,6 +158,7 @@ const updateCategory = createResource({
}) })
const update = (name, value) => { const update = (name, value) => {
saving.value = true
updateCategory.submit( updateCategory.submit(
{ {
name: name, name: name,
@@ -122,9 +166,51 @@ const update = (name, value) => {
}, },
{ {
onSuccess() { onSuccess() {
saving.value = false
categories.reload() categories.reload()
editing.value = null
editedValue.value = ''
toast.success(__('Category updated successfully'))
},
onError(err) {
saving.value = false
editing.value = null
editedValue.value = ''
toast.error(
__(cleanError(err.messages[0]) || 'Unable to update category')
)
}, },
} }
) )
} }
const deleteCategory = (name) => {
saving.value = true
categories.delete.submit(name, {
onSuccess() {
saving.value = false
categories.reload()
toast.success(__('Category deleted successfully'))
},
onError(err) {
saving.value = false
toast.error(
__(cleanError(err.messages[0]) || 'Unable to delete category')
)
},
})
}
const saveChanges = (name, value) => {
saving.value = true
update(name, value)
}
const allowEdit = (cat, index) => {
editing.value = cat
editedValue.value = cat.category
setTimeout(() => {
editInputRef.value[index].$el.querySelector('input').focus()
}, 0)
}
</script> </script>

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
<template> <template>
<div> <div class="flex min-h-0 flex-col text-base">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<div> <div>
<div class="text-xl font-semibold mb-1 text-ink-gray-9"> <div class="text-xl font-semibold mb-1 text-ink-gray-9">
@@ -39,6 +39,7 @@
</Button> </Button>
</div> </div>
<div class="overflow-y-scroll">
<div class="divide-y"> <div class="divide-y">
<div <div
v-for="evaluator in evaluators.data" v-for="evaluator in evaluators.data"
@@ -65,6 +66,7 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { createResource, Button, FormControl, call, Avatar } from 'frappe-ui' import { createResource, Button, FormControl, call, Avatar } from 'frappe-ui'

View File

@@ -1,16 +1,34 @@
<template> <template>
<div class="flex h-full flex-col"> <div class="flex h-full flex-col relative">
<div class="h-full pb-10" id="scrollContainer"> <div class="h-full pb-10" id="scrollContainer">
<slot /> <slot />
</div> </div>
<div 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"
>
<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 <div
v-if="sidebarSettings.data" 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" 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"
:style="{
gridTemplateColumns: `repeat(${
sidebarLinks.length + 1
}, minmax(0, 1fr))`,
}"
> >
<button <button
v-for="tab in sidebarLinks" v-for="tab in sidebarLinks"
@@ -25,46 +43,22 @@
:class="[isActive(tab) ? 'text-ink-gray-9' : 'text-ink-gray-5']" :class="[isActive(tab) ? 'text-ink-gray-9' : 'text-ink-gray-5']"
/> />
</button> </button>
<Popover <button @click="toggleMenu">
trigger="hover"
popoverClass="bottom-28 mx-2"
placement="top-start"
>
<template #target>
<component <component
:is="icons['List']" :is="icons['List']"
class="h-6 w-6 stroke-1.5 text-ink-gray-5" class="h-6 w-6 stroke-1.5 text-ink-gray-5"
/> />
</template> </button>
<template #body-main>
<div 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> </div>
</div> </div>
</template> </template>
</Popover>
</div>
</div>
</template>
<script setup> <script setup>
import { getSidebarLinks } from '../utils' import { getSidebarLinks } from '../utils'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { watch, ref, onMounted } from 'vue' import { watch, ref, onMounted } from 'vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { usersStore } from '@/stores/user' import { usersStore } from '@/stores/user'
import { Popover } from 'frappe-ui'
import * as icons from 'lucide-vue-next' import * as icons from 'lucide-vue-next'
const { logout, user, sidebarSettings } = sessionStore() const { logout, user, sidebarSettings } = sessionStore()
@@ -73,12 +67,38 @@ const router = useRouter()
let { userResource } = usersStore() let { userResource } = usersStore()
const sidebarLinks = ref(getSidebarLinks()) const sidebarLinks = ref(getSidebarLinks())
const otherLinks = ref([]) const otherLinks = ref([])
const showMenu = ref(false)
const menu = ref(null)
onMounted(() => { onMounted(() => {
sidebarSettings.reload( sidebarSettings.reload(
{}, {},
{ {
onSuccess(data) { onSuccess(data) {
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) => { Object.keys(data).forEach((key) => {
if (!parseInt(data[key])) { if (!parseInt(data[key])) {
sidebarLinks.value = sidebarLinks.value.filter( sidebarLinks.value = sidebarLinks.value.filter(
@@ -86,12 +106,7 @@ onMounted(() => {
) )
} }
}) })
addOtherLinks()
},
} }
)
})
const addOtherLinks = () => { const addOtherLinks = () => {
if (user) { if (user) {
@@ -122,6 +137,7 @@ watch(userResource, () => {
(userResource.data.is_moderator || userResource.data.is_instructor) (userResource.data.is_moderator || userResource.data.is_instructor)
) { ) {
addQuizzes() addQuizzes()
addAssignments()
} }
}) })
@@ -133,6 +149,14 @@ const addQuizzes = () => {
}) })
} }
const addAssignments = () => {
otherLinks.value.push({
label: 'Assignments',
icon: 'Pencil',
to: 'Assignments',
})
}
let isActive = (tab) => { let isActive = (tab) => {
return tab.activeFor?.includes(router.currentRoute.value.name) return tab.activeFor?.includes(router.currentRoute.value.name)
} }
@@ -158,4 +182,8 @@ const isVisible = (tab) => {
else if (tab.label == 'Log out') return isLoggedIn else if (tab.label == 'Log out') return isLoggedIn
else return true else return true
} }
const toggleMenu = () => {
showMenu.value = !showMenu.value
}
</script> </script>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -88,16 +88,14 @@
:scrollToBottom="false" :scrollToBottom="false"
/> />
</div> </div>
<div v-else-if="tab.label == 'Feedback'">
<BatchFeedback :batch="batch.data.name" />
</div>
</div> </div>
</template> </template>
</Tabs> </Tabs>
</div> </div>
<div class="p-5"> <div class="p-5">
<div class="text-ink-gray-7 font-semibold mb-4"> <div class="mb-10">
{{ __('About this batch') }}: <div class="text-ink-gray-7 font-semibold mb-2">
{{ __('About this batch') }}
</div> </div>
<div <div
v-html="batch.data.description" v-html="batch.data.description"
@@ -140,6 +138,13 @@
</span> </span>
</div> </div>
</div> </div>
<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 <AnnouncementModal
v-model="showAnnouncementModal" v-model="showAnnouncementModal"
:batch="batch.data.name" :batch="batch.data.name"
@@ -234,6 +239,7 @@ import Discussions from '@/components/Discussions.vue'
import DateRange from '@/components/Common/DateRange.vue' import DateRange from '@/components/Common/DateRange.vue'
import BulkCertificates from '@/components/Modals/BulkCertificates.vue' import BulkCertificates from '@/components/Modals/BulkCertificates.vue'
import BatchFeedback from '@/components/BatchFeedback.vue' import BatchFeedback from '@/components/BatchFeedback.vue'
import dayjs from 'dayjs/esm'
const user = inject('$user') const user = inject('$user')
const showAnnouncementModal = ref(false) const showAnnouncementModal = ref(false)
@@ -277,11 +283,6 @@ const tabs = computed(() => {
label: 'Discussions', label: 'Discussions',
icon: MessageCircle, icon: MessageCircle,
}) })
batchTabs.push({
label: 'Feedback',
icon: ClipboardPen,
})
return batchTabs return batchTabs
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

561
yarn.lock
View File

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