diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 254ef1e7..bfe2b8b4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,10 +3,15 @@ on:
push:
branches:
- main
+ - develop
+ - main-hotfix
pull_request: {}
jobs:
tests:
+ name: Server Tests
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
services:
redis-cache:
image: redis:alpine
@@ -30,13 +35,13 @@ jobs:
steps:
- uses: actions/checkout@v2
- name: setup python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v6
with:
- python-version: '3.10'
+ python-version: '3.14'
- name: setup node
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v6
with:
- node-version: '18'
+ node-version: '24'
check-latest: true
- name: setup cache for bench
uses: actions/cache@v4
@@ -69,6 +74,9 @@ jobs:
- name: setup requirements
working-directory: /home/runner/frappe-bench
run: bench setup requirements --dev
+ - name: block endpoints
+ working-directory: /home/runner/frappe-bench
+ run: bench --site frappe.local set-config block_endpoints 1
- name: allow tests
working-directory: /home/runner/frappe-bench
run: bench --site frappe.local set-config allow_tests true
@@ -77,4 +85,27 @@ jobs:
run: bench --site frappe.local build
- name: run tests
working-directory: /home/runner/frappe-bench
- run: bench --site frappe.local run-tests --app lms
\ No newline at end of file
+ run: bench --site frappe.local run-tests --app lms --coverage
+ - name: Upload coverage data
+ uses: actions/upload-artifact@v4
+ with:
+ path: /home/runner/frappe-bench/sites/coverage.xml
+
+ coverage:
+ name: Coverage Wrap Up
+ needs: tests
+ runs-on: ubuntu-latest
+ steps:
+ - name: Clone
+ uses: actions/checkout@v3
+
+ - name: Download artifacts
+ uses: actions/download-artifact@v4
+
+ - name: Upload coverage data
+ uses: codecov/codecov-action@v5
+ with:
+ name: Server
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: true
+ verbose: true
\ No newline at end of file
diff --git a/.github/workflows/generate-pot-file.yml b/.github/workflows/generate-pot-file.yml
index 4c5d81e1..eea3e93a 100644
--- a/.github/workflows/generate-pot-file.yml
+++ b/.github/workflows/generate-pot-file.yml
@@ -22,9 +22,14 @@ jobs:
ref: ${{ matrix.branch }}
- name: Setup Python
- uses: actions/setup-python@v5
+ uses: actions/setup-python@v6
with:
- python-version: "3.12"
+ python-version: "3.14"
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: 24
- name: Run script to update POT file
run: |
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index b0ecb717..06b29eb7 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -16,9 +16,9 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 200
- - uses: actions/setup-node@v4
+ - uses: actions/setup-node@v6
with:
- node-version: 20
+ node-version: 24
check-latest: true
- name: Check commit titles
@@ -35,9 +35,9 @@ jobs:
- uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v6
with:
- python-version: '3.10'
+ python-version: '3.14'
- name: Cache pip
uses: actions/cache@v4
diff --git a/.github/workflows/make_release_pr.yml b/.github/workflows/make_release_pr.yml
index 4c0890c1..9a4528b9 100644
--- a/.github/workflows/make_release_pr.yml
+++ b/.github/workflows/make_release_pr.yml
@@ -18,9 +18,9 @@ jobs:
owner: frappe
repo: lms
title: |-
- "chore: merge 'develop' into 'main'"
+ "chore: merge 'main-hotfix' into 'main'"
body: "Automated weekly release"
base: main
- head: develop
+ head: main-hotfix
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/on_release.yml b/.github/workflows/on_release.yml
index cc7b1c89..623fd9f8 100644
--- a/.github/workflows/on_release.yml
+++ b/.github/workflows/on_release.yml
@@ -15,9 +15,9 @@ jobs:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
- uses: actions/setup-node@v4
+ uses: actions/setup-node@v6
with:
- node-version: 20
+ node-version: 24
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 6ff03b93..111c5918 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -4,7 +4,10 @@ on:
pull_request:
workflow_dispatch:
push:
- branches: [ main ]
+ branches:
+ - main
+ - develop
+ - main-hotfix
permissions:
# Do not change this as GITHUB_TOKEN is being used by roulette
@@ -36,9 +39,9 @@ jobs:
uses: actions/checkout@v3
- name: Setup Python
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v6
with:
- python-version: '3.11'
+ python-version: '3.14'
- name: Check for valid Python & Merge Conflicts
run: |
@@ -48,9 +51,9 @@ jobs:
exit 1
fi
- - uses: actions/setup-node@v3
+ - uses: actions/setup-node@v6
with:
- node-version: 18
+ node-version: 24
check-latest: true
- name: Add to Hosts
diff --git a/.gitignore b/.gitignore
index 95242ce7..c532a8dc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,4 +12,6 @@ node_modules
package-lock.json
lms/public/frontend
lms/www/lms.html
-frappe-ui
\ No newline at end of file
+lms/www/_lms.html
+frappe-ui
+frappe-semgrep-rules
\ No newline at end of file
diff --git a/.mergify.yml b/.mergify.yml
new file mode 100644
index 00000000..098cfd0a
--- /dev/null
+++ b/.mergify.yml
@@ -0,0 +1,30 @@
+pull_request_rules:
+ - name: backport to develop
+ conditions:
+ - label="backport develop"
+ actions:
+ backport:
+ branches:
+ - develop
+ assignees:
+ - "{{ author }}"
+
+ - name: backport to main-hotfix
+ conditions:
+ - label="backport main-hotfix"
+ actions:
+ backport:
+ branches:
+ - main-hotfix
+ assignees:
+ - "{{ author }}"
+
+ - name: backport to main
+ conditions:
+ - label="backport main"
+ actions:
+ backport:
+ branches:
+ - main
+ assignees:
+ - "{{ author }}"
diff --git a/.releaserc b/.releaserc
index 4f5437e0..6c6d1954 100644
--- a/.releaserc
+++ b/.releaserc
@@ -1,5 +1,5 @@
{
- "branches": ["develop"],
+ "branches": ["main"],
"plugins": [
"@semantic-release/commit-analyzer", {
"preset": "angular"
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 00000000..4f6dfa59
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,2 @@
+ignore:
+ - "**/test_helper.py"
\ No newline at end of file
diff --git a/cypress/e2e/batch_creation.cy.js b/cypress/e2e/batch_creation.cy.js
index 9ec55c2c..f0fe6ed1 100644
--- a/cypress/e2e/batch_creation.cy.js
+++ b/cypress/e2e/batch_creation.cy.js
@@ -27,6 +27,10 @@ describe("Batch Creation", () => {
cy.get("input[placeholder='Jane']").type(randomName);
cy.get("button").contains("Add").click();
+ // Open Settings
+ cy.get("span").contains("Learning").click();
+ cy.get("span").contains("Settings").click();
+
// Add evaluator
cy.get("[data-dismissable-layer]")
.find("span")
@@ -48,26 +52,23 @@ describe("Batch Creation", () => {
// Create a batch
cy.get("button").contains("Create").click();
+ cy.get("span").contains("New Batch").click();
cy.wait(500);
- cy.url().should("include", "/batches/new/edit");
cy.get("label").contains("Title").type("Test Batch");
-
cy.get("label").contains("Start Date").type("2030-10-01");
cy.get("label").contains("End Date").type("2030-10-31");
cy.get("label").contains("Start Time").type("10:00");
cy.get("label").contains("End Time").type("11:00");
cy.get("label").contains("Timezone").type("IST");
cy.get("label").contains("Seat Count").type("10");
- cy.get("label").contains("Published").click();
cy.get("label")
- .contains("Short Description")
+ .contains("Description")
.type("Test Batch Short Description to test the UI");
cy.get("div[contenteditable=true").invoke(
"text",
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
);
-
/* Instructor */
cy.get("label")
.contains("Instructors")
@@ -85,13 +86,14 @@ describe("Batch Creation", () => {
cy.get("[id^=headlessui-combobox-option-").first().click();
});
});
-
+ cy.button("Save").click();
+ cy.get("label").contains("Published").click();
cy.button("Save").click();
cy.wait(1000);
let batchName;
cy.url().then((url) => {
console.log(url);
- batchName = url.split("/").pop();
+ batchName = url.split("/").pop().split("#")[0];
cy.wrap(batchName).as("batchName");
});
cy.wait(500);
@@ -110,7 +112,7 @@ describe("Batch Creation", () => {
.click();
cy.get("@batchName").then((batchName) => {
- cy.get(`a[href='/lms/batches/details/${batchName}'`).within(() => {
+ cy.get(`a[href='/lms/batches/${batchName}'`).within(() => {
cy.get("div").contains("Test Batch").should("be.visible");
cy.get("div")
.contains("Test Batch Short Description to test the UI")
@@ -123,14 +125,11 @@ describe("Batch Creation", () => {
.should("be.visible");
cy.get("span").contains("IST").should("be.visible");
cy.get("a").contains("Evaluator").should("be.visible");
- cy.get("div")
- .contains("10")
- .should("be.visible")
- .get("span")
- .contains("Seats Left")
- .should("be.visible");
+ cy.contains("div:visible", "10 Seats Left").should(
+ "be.visible"
+ );
});
- cy.get(`a[href='/lms/batches/details/${batchName}'`).click();
+ cy.get(`a[href='/lms/batches/${batchName}'`).click();
});
cy.get("div").contains("Test Batch").should("be.visible");
@@ -152,17 +151,22 @@ describe("Batch Creation", () => {
"Test Batch Description. I need a very big description to test the UI. This is a very big description. It contains more than once sentence. Its meant to be this long as this is a UI test. Its unbearably long and I'm not sure why I'm typing this much. I'm just going to keep typing until I feel like its long enough. I think its long enough now. I'm going to stop typing now."
)
.should("be.visible");
- cy.get("button:visible").contains("Manage Batch").click();
+ cy.get("button:visible").contains("Dashboard").click();
/* Add student to batch */
- cy.get("button").contains("Add").click();
- cy.get('div[role="dialog"]').first().find("button").eq(1).click();
- cy.get("input[id^='headlessui-combobox-input-v-']").type(randomEmail);
+ cy.get("button").contains("Enroll").click();
+ cy.get('div[role="dialog"]')
+ .first()
+ .find("div[label='Student']")
+ .find("div")
+ .first()
+ .click();
+ cy.get("input[placeholder='Search']").type(randomEmail);
cy.get("div").contains(randomEmail).click();
cy.get("button").contains("Submit").click();
// Verify Seat Count
- cy.get("span").contains("Details").click();
+ cy.get("button:visible").contains("Overview").click();
cy.contains("div:visible", "9 Seats Left").should("be.visible");
});
});
diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js
index a0d14591..b5f5ae73 100644
--- a/cypress/e2e/course_creation.cy.js
+++ b/cypress/e2e/course_creation.cy.js
@@ -9,8 +9,8 @@ describe("Course Creation", () => {
// Create a course
cy.get("button").contains("Create").click();
+ cy.get("span").contains("New Course").click();
cy.wait(500);
- cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course");
cy.get("label")
@@ -34,27 +34,13 @@ describe("Course Creation", () => {
});
});
- cy.get("label")
- .contains("Preview Video")
- .type("https://www.youtube.com/embed/-LPmw2Znl2c");
- cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
- cy.get("label")
- .contains("Category")
- .parent()
- .within(() => {
- cy.get("button").click();
- });
- cy.get("[id^=headlessui-combobox-option-")
- .should("be.visible")
- .first()
- .click();
-
/* Instructor */
cy.get("label")
.contains("Instructors")
.parent()
.within(() => {
cy.get("input").click().type("frappe");
+ cy.wait(500);
cy.get("input")
.invoke("attr", "aria-controls")
.as("instructor_list_id");
@@ -67,13 +53,29 @@ describe("Course Creation", () => {
});
});
+ cy.button("Save").last().click();
+
+ // Edit Course Details
+ cy.wait(500);
+ cy.get("label")
+ .contains("Preview Video")
+ .type("https://www.youtube.com/embed/-LPmw2Znl2c");
+ cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
+ cy.get("label")
+ .contains("Category")
+ .parent()
+ .within(() => {
+ cy.get("button").click();
+ });
+ cy.get("div").contains("Business").click();
+
cy.get("label").contains("Published").click();
cy.get("label").contains("Published On").type("2021-01-01");
cy.button("Save").click();
// Add Chapter
cy.wait(1000);
- cy.button("Add Chapter").click();
+ cy.button("Add").click();
cy.wait(1000);
cy.get("[data-dismissable-layer]")
diff --git a/frappe-ui b/frappe-ui
index f1bde9bc..78025c67 160000
--- a/frappe-ui
+++ b/frappe-ui
@@ -1 +1 @@
-Subproject commit f1bde9bcb271af47e9f5de190a18dff8604f5312
+Subproject commit 78025c679490705b8df9fc0162985f5f3c808568
diff --git a/frontend/auto-imports.d.ts b/frontend/auto-imports.d.ts
index 9d240079..3568bad8 100644
--- a/frontend/auto-imports.d.ts
+++ b/frontend/auto-imports.d.ts
@@ -6,5 +6,7 @@
// biome-ignore lint: disable
export {}
declare global {
-
+ const LucideGithub: typeof import('~icons/lucide/github').default
+ const LucideLinkedin: typeof import('~icons/lucide/linkedin').default
+ const LucideTwitter: typeof import('~icons/lucide/twitter').default
}
diff --git a/frontend/components.d.ts b/frontend/components.d.ts
index 1f5ac102..42b89f05 100644
--- a/frontend/components.d.ts
+++ b/frontend/components.d.ts
@@ -8,13 +8,10 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
- Annoucements: typeof import('./src/components/Annoucements.vue')['default']
- AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
- Apps: typeof import('./src/components/Apps.vue')['default']
- AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
+ Apps: typeof import('./src/components/Sidebar/Apps.vue')['default']
+ AppSidebar: typeof import('./src/components/Sidebar/AppSidebar.vue')['default']
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
AssessmentPlugin: typeof import('./src/components/AssessmentPlugin.vue')['default']
- Assessments: typeof import('./src/components/Assessments.vue')['default']
Assignment: typeof import('./src/components/Assignment.vue')['default']
AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default']
AudioBlock: typeof import('./src/components/AudioBlock.vue')['default']
@@ -23,16 +20,8 @@ declare module 'vue' {
BadgeAssignments: typeof import('./src/components/Settings/BadgeAssignments.vue')['default']
BadgeForm: typeof import('./src/components/Settings/BadgeForm.vue')['default']
Badges: typeof import('./src/components/Settings/Badges.vue')['default']
- BatchCard: typeof import('./src/components/BatchCard.vue')['default']
BatchCourseModal: typeof import('./src/components/Modals/BatchCourseModal.vue')['default']
- BatchCourses: typeof import('./src/components/BatchCourses.vue')['default']
- BatchDashboard: typeof import('./src/components/BatchDashboard.vue')['default']
- BatchFeedback: typeof import('./src/components/BatchFeedback.vue')['default']
- BatchOverlay: typeof import('./src/components/BatchOverlay.vue')['default']
- BatchStudentProgress: typeof import('./src/components/Modals/BatchStudentProgress.vue')['default']
- BatchStudents: typeof import('./src/components/BatchStudents.vue')['default']
BrandSettings: typeof import('./src/components/Settings/BrandSettings.vue')['default']
- BulkCertificates: typeof import('./src/components/Modals/BulkCertificates.vue')['default']
Categories: typeof import('./src/components/Settings/Categories.vue')['default']
CertificationLinks: typeof import('./src/components/CertificationLinks.vue')['default']
ChapterModal: typeof import('./src/components/Modals/ChapterModal.vue')['default']
@@ -41,12 +30,18 @@ declare module 'vue' {
CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default']
CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.vue')['default']
ColorSwatches: typeof import('./src/components/Controls/ColorSwatches.vue')['default']
+ CommandPalette: typeof import('./src/components/CommandPalette/CommandPalette.vue')['default']
+ CommandPaletteGroup: typeof import('./src/components/CommandPalette/CommandPaletteGroup.vue')['default']
+ Configuration: typeof import('./src/components/Sidebar/Configuration.vue')['default']
ContactUsEmail: typeof import('./src/components/ContactUsEmail.vue')['default']
+ CouponDetails: typeof import('./src/components/Settings/Coupons/CouponDetails.vue')['default']
+ CouponItems: typeof import('./src/components/Settings/Coupons/CouponItems.vue')['default']
+ CouponList: typeof import('./src/components/Settings/Coupons/CouponList.vue')['default']
+ Coupons: typeof import('./src/components/Settings/Coupons/Coupons.vue')['default']
CourseCard: typeof import('./src/components/CourseCard.vue')['default']
CourseCardOverlay: typeof import('./src/components/CourseCardOverlay.vue')['default']
CourseInstructors: typeof import('./src/components/CourseInstructors.vue')['default']
CourseOutline: typeof import('./src/components/CourseOutline.vue')['default']
- CourseProgressSummary: typeof import('./src/components/Modals/CourseProgressSummary.vue')['default']
CourseReviews: typeof import('./src/components/CourseReviews.vue')['default']
CreateOutline: typeof import('./src/components/CreateOutline.vue')['default']
DateRange: typeof import('./src/components/Common/DateRange.vue')['default']
@@ -75,7 +70,6 @@ declare module 'vue' {
LessonContent: typeof import('./src/components/LessonContent.vue')['default']
LessonHelp: typeof import('./src/components/LessonHelp.vue')['default']
Link: typeof import('./src/components/Controls/Link.vue')['default']
- LiveClass: typeof import('./src/components/LiveClass.vue')['default']
LiveClassAttendance: typeof import('./src/components/Modals/LiveClassAttendance.vue')['default']
LiveClassModal: typeof import('./src/components/Modals/LiveClassModal.vue')['default']
LMSLogo: typeof import('./src/components/Icons/LMSLogo.vue')['default']
@@ -86,6 +80,7 @@ declare module 'vue' {
NoSidebarLayout: typeof import('./src/components/NoSidebarLayout.vue')['default']
Notes: typeof import('./src/components/Notes/Notes.vue')['default']
NotPermitted: typeof import('./src/components/NotPermitted.vue')['default']
+ NumberChartGraph: typeof import('./src/components/NumberChartGraph.vue')['default']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentGatewayDetails: typeof import('./src/components/Settings/PaymentGatewayDetails.vue')['default']
PaymentGateways: typeof import('./src/components/Settings/PaymentGateways.vue')['default']
@@ -103,18 +98,19 @@ declare module 'vue' {
SettingDetails: typeof import('./src/components/Settings/SettingDetails.vue')['default']
SettingFields: typeof import('./src/components/Settings/SettingFields.vue')['default']
Settings: typeof import('./src/components/Settings/Settings.vue')['default']
- SidebarLink: typeof import('./src/components/SidebarLink.vue')['default']
+ SidebarLink: typeof import('./src/components/Sidebar/SidebarLink.vue')['default']
StudentHeatmap: typeof import('./src/components/StudentHeatmap.vue')['default']
StudentModal: typeof import('./src/components/Modals/StudentModal.vue')['default']
Tags: typeof import('./src/components/Tags.vue')['default']
- TransactionDetails: typeof import('./src/components/Settings/TransactionDetails.vue')['default']
- Transactions: typeof import('./src/components/Settings/Transactions.vue')['default']
+ TransactionDetails: typeof import('./src/components/Settings/Transactions/TransactionDetails.vue')['default']
+ TransactionList: typeof import('./src/components/Settings/Transactions/TransactionList.vue')['default']
+ Transactions: typeof import('./src/components/Settings/Transactions/Transactions.vue')['default']
UnsplashImageBrowser: typeof import('./src/components/UnsplashImageBrowser.vue')['default']
UpcomingEvaluations: typeof import('./src/components/UpcomingEvaluations.vue')['default']
Uploader: typeof import('./src/components/Controls/Uploader.vue')['default']
UploadPlugin: typeof import('./src/components/UploadPlugin.vue')['default']
UserAvatar: typeof import('./src/components/UserAvatar.vue')['default']
- UserDropdown: typeof import('./src/components/UserDropdown.vue')['default']
+ UserDropdown: typeof import('./src/components/Sidebar/UserDropdown.vue')['default']
VideoBlock: typeof import('./src/components/VideoBlock.vue')['default']
VideoStatistics: typeof import('./src/components/Modals/VideoStatistics.vue')['default']
ZoomAccountModal: typeof import('./src/components/Modals/ZoomAccountModal.vue')['default']
diff --git a/frontend/package.json b/frontend/package.json
index 83805f7c..fc19b151 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,55 +6,57 @@
"scripts": {
"dev": "vite",
"serve": "vite preview",
- "build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry",
- "copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html"
+ "build": "vite build --base=/assets/lms/frontend/ && yarn copy-html-entry && yarn copy-colors-json",
+ "copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/_lms.html",
+ "copy-colors-json": "cp node_modules/frappe-ui/tailwind/colors.json src/utils/frappe-ui-colors.json"
},
"dependencies": {
- "@codemirror/lang-html": "^6.4.9",
- "@codemirror/lang-javascript": "^6.2.4",
- "@codemirror/lang-json": "^6.0.1",
- "@codemirror/lang-python": "^6.2.1",
- "@editorjs/checklist": "^1.6.0",
- "@editorjs/code": "^2.9.0",
- "@editorjs/editorjs": "^2.29.0",
- "@editorjs/embed": "^2.7.0",
- "@editorjs/header": "^2.8.1",
- "@editorjs/inline-code": "^1.5.0",
- "@editorjs/nested-list": "^1.4.2",
- "@editorjs/paragraph": "^2.11.3",
- "@editorjs/simple-image": "^1.6.0",
- "@editorjs/table": "^2.4.2",
- "@vueuse/router": "^12.7.0",
- "ace-builds": "^1.36.2",
- "apexcharts": "^4.3.0",
- "chart.js": "^4.4.1",
- "codemirror": "^6.0.1",
- "dayjs": "^1.11.6",
- "dompurify": "^3.2.6",
- "feather-icons": "^4.28.0",
- "frappe-ui": "^0.1.201",
- "highlight.js": "^11.11.1",
- "lucide-vue-next": "^0.383.0",
- "markdown-it": "^14.0.0",
- "pinia": "^2.0.33",
- "plyr": "^3.7.8",
- "socket.io-client": "^4.7.2",
- "tailwindcss": "3.4.15",
- "thememirror": "^2.0.1",
- "typescript": "^5.7.2",
- "vue": "^3.4.23",
- "vue-chartjs": "^5.3.0",
- "vue-codemirror": "^6.1.1",
- "vue-draggable-next": "^2.2.1",
- "vue-router": "^4.0.12",
- "vue3-apexcharts": "^1.8.0",
+ "@codemirror/lang-html": "6.4.9",
+ "@codemirror/lang-javascript": "6.2.4",
+ "@codemirror/lang-json": "6.0.1",
+ "@codemirror/lang-python": "6.2.1",
+ "@editorjs/checklist": "1.6.0",
+ "@editorjs/code": "2.9.0",
+ "@editorjs/editorjs": "2.29.0",
+ "@editorjs/embed": "2.7.0",
+ "@editorjs/header": "2.8.1",
+ "@editorjs/inline-code": "1.5.0",
+ "@editorjs/nested-list": "1.4.2",
+ "@editorjs/paragraph": "2.11.3",
+ "@editorjs/simple-image": "1.6.0",
+ "@editorjs/table": "2.4.2",
+ "@vueuse/core": "^14.1.0",
+ "ace-builds": "1.36.2",
+ "apexcharts": "4.3.0",
+ "chart.js": "4.4.1",
+ "codemirror": "6.0.1",
+ "dayjs": "1.11.10",
+ "dompurify": "3.2.6",
+ "feather-icons": "4.28.0",
+ "frappe-ui": "^0.1.261",
+ "highlight.js": "11.11.1",
+ "lucide-vue-next": "0.383.0",
+ "markdown-it": "14.0.0",
+ "pinia": "2.0.33",
+ "plyr": "3.7.8",
+ "socket.io-client": "4.7.2",
+ "thememirror": "2.0.1",
+ "typescript": "5.7.2",
+ "vue": "^3.5.27",
+ "vue-chartjs": "5.3.0",
+ "vue-codemirror": "6.1.1",
+ "vue-draggable-next": "2.2.1",
+ "vue-router": "^4.6.4",
+ "vue3-apexcharts": "1.8.0",
"vuedraggable": "4.1.0"
},
"devDependencies": {
- "@vitejs/plugin-vue": "^5.0.3",
- "autoprefixer": "^10.4.2",
- "postcss": "^8.4.5",
- "vite": "^5.0.11",
- "vite-plugin-pwa": "^1.0.2"
+ "@vitejs/plugin-vue": "5.0.3",
+ "autoprefixer": "10.4.2",
+ "postcss": "8.4.5",
+ "tailwindcss": "^3.4.15",
+ "unplugin-auto-import": "^20.3.0",
+ "vite": "5.0.11",
+ "vite-plugin-pwa": "^1.2.0"
}
}
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index a39b20fe..697aa346 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -3,18 +3,17 @@