diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh index beb0cd34..17786a03 100644 --- a/.github/helper/install_dependencies.sh +++ b/.github/helper/install_dependencies.sh @@ -5,7 +5,7 @@ echo "Setting Up System Dependencies..." sudo apt update sudo apt remove mysql-server mysql-client -sudo apt-get install libcups2-dev redis-server mariadb-client +sudo apt-get install libcups2-dev redis-server mariadb-client libmariadb-dev install_wkhtmltopdf() { wget -q https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb diff --git a/README.md b/README.md index 6c0989b9..d96c1ea5 100644 --- a/README.md +++ b/README.md @@ -118,6 +118,10 @@ Replace the following parameters with your values: The script will set up a production-ready instance of Frappe Learning with all the necessary configurations in about 5 minutes. +**Note:** To avoid a `404 Page Not Found` error: +- If hosting on a **public server**, make sure your DNS **A record** points to your server's IP. +- If hosting **locally**, map your domain to `127.0.0.1` in your `/etc/hosts` file: + ## Development Setup ### Docker diff --git a/cypress/e2e/batch_creation.cy.js b/cypress/e2e/batch_creation.cy.js index 5b62e6e2..a6528969 100644 --- a/cypress/e2e/batch_creation.cy.js +++ b/cypress/e2e/batch_creation.cy.js @@ -23,8 +23,8 @@ describe("Batch Creation", () => { const randomEmail = `testuser_${dateNow}@example.com`; const randomName = `Test User ${dateNow}`; - cy.get("input[placeholder='Email']").type(randomEmail); - cy.get("input[placeholder='First Name']").type(randomName); + cy.get("input[placeholder='jane@doe.com']").type(randomEmail); + cy.get("input[placeholder='Jane']").type(randomName); cy.get("button").contains("Add").click(); // Add evaluator @@ -39,7 +39,7 @@ describe("Batch Creation", () => { .click(); const randomEvaluator = `evaluator${dateNow}@example.com`; - cy.get("input[placeholder='Email']").type(randomEvaluator); + cy.get("input[placeholder='jane@doe.com']").type(randomEvaluator); cy.get("button").contains("Add").click(); cy.get("div").contains(randomEvaluator).should("be.visible").click(); @@ -47,7 +47,7 @@ describe("Batch Creation", () => { cy.closeOnboardingModal(); // Create a batch - cy.get("button").contains("New").click(); + cy.get("button").contains("Create").click(); cy.wait(500); cy.url().should("include", "/batches/new/edit"); cy.get("label").contains("Title").type("Test Batch"); diff --git a/cypress/e2e/course_creation.cy.js b/cypress/e2e/course_creation.cy.js index 2530b3db..e8120051 100644 --- a/cypress/e2e/course_creation.cy.js +++ b/cypress/e2e/course_creation.cy.js @@ -8,7 +8,7 @@ describe("Course Creation", () => { cy.closeOnboardingModal(); // Create a course - cy.get("button").contains("New").click(); + cy.get("button").contains("Create").click(); cy.wait(500); cy.url().should("include", "/courses/new/edit"); diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 09f63fdd..3de0da5d 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -19,6 +19,10 @@ declare module 'vue' { AssignmentForm: typeof import('./src/components/Modals/AssignmentForm.vue')['default'] AudioBlock: typeof import('./src/components/AudioBlock.vue')['default'] Autocomplete: typeof import('./src/components/Controls/Autocomplete.vue')['default'] + BadgeAssignmentForm: typeof import('./src/components/Settings/BadgeAssignmentForm.vue')['default'] + 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'] @@ -32,12 +36,15 @@ declare module 'vue' { 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'] + ChildTable: typeof import('./src/components/Controls/ChildTable.vue')['default'] + Code: typeof import('./src/components/Controls/Code.vue')['default'] CodeEditor: typeof import('./src/components/Controls/CodeEditor.vue')['default'] CollapseSidebar: typeof import('./src/components/Icons/CollapseSidebar.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'] @@ -96,10 +103,12 @@ declare module 'vue' { Tags: typeof import('./src/components/Tags.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'] 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'] ZoomSettings: typeof import('./src/components/Settings/ZoomSettings.vue')['default'] } diff --git a/frontend/package.json b/frontend/package.json index bb7a91da..dd367954 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,10 @@ "copy-html-entry": "cp ../lms/public/frontend/index.html ../lms/www/lms.html" }, "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", @@ -24,10 +28,10 @@ "ace-builds": "^1.36.2", "apexcharts": "^4.3.0", "chart.js": "^4.4.1", - "codemirror-editor-vue3": "^2.8.0", + "codemirror": "^6.0.1", "dayjs": "^1.11.6", "feather-icons": "^4.28.0", - "frappe-ui": "^0.1.147", + "frappe-ui": "^0.1.163", "highlight.js": "^11.11.1", "lucide-vue-next": "^0.383.0", "markdown-it": "^14.0.0", @@ -35,9 +39,11 @@ "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", diff --git a/frontend/public/Remove.mp4 b/frontend/public/Remove.mp4 new file mode 100644 index 00000000..1f6f8389 Binary files /dev/null and b/frontend/public/Remove.mp4 differ diff --git a/frontend/src/components/AppSidebar.vue b/frontend/src/components/AppSidebar.vue index e2374354..3d7ce6f2 100644 --- a/frontend/src/components/AppSidebar.vue +++ b/frontend/src/components/AppSidebar.vue @@ -225,7 +225,7 @@ import { IntermediateStepModal, } from 'frappe-ui/frappe' -const { user, sidebarSettings } = sessionStore() +const { user } = sessionStore() const { userResource } = usersStore() let sidebarStore = useSidebar() const socket = inject('$socket') @@ -236,6 +236,7 @@ const isModerator = ref(false) const isInstructor = ref(false) const pageToEdit = ref(null) const settingsStore = useSettings() +const { sidebarSettings } = settingsStore const showOnboarding = ref(false) const showIntermediateModal = ref(false) const currentStep = ref({}) @@ -313,7 +314,7 @@ const addNotifications = () => { const addQuizzes = () => { if (isInstructor.value || isModerator.value) { - sidebarLinks.value.push({ + sidebarLinks.value.splice(4, 0, { label: 'Quizzes', icon: 'CircleHelp', to: 'Quizzes', @@ -329,7 +330,7 @@ const addQuizzes = () => { const addAssignments = () => { if (isInstructor.value || isModerator.value) { - sidebarLinks.value.push({ + sidebarLinks.value.splice(5, 0, { label: 'Assignments', icon: 'Pencil', to: 'Assignments', diff --git a/frontend/src/components/AssessmentPlugin.vue b/frontend/src/components/AssessmentPlugin.vue index 21f8895e..eae9763e 100644 --- a/frontend/src/components/AssessmentPlugin.vue +++ b/frontend/src/components/AssessmentPlugin.vue @@ -2,17 +2,24 @@ - diff --git a/frontend/src/components/BatchDashboard.vue b/frontend/src/components/BatchDashboard.vue index 196cbf88..d7639490 100644 --- a/frontend/src/components/BatchDashboard.vue +++ b/frontend/src/components/BatchDashboard.vue @@ -6,13 +6,12 @@ :courses="batch.data.courses" /> - + diff --git a/frontend/src/components/Controls/Code.vue b/frontend/src/components/Controls/Code.vue new file mode 100644 index 00000000..3440f90e --- /dev/null +++ b/frontend/src/components/Controls/Code.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/src/components/Controls/CodeEditor.vue b/frontend/src/components/Controls/CodeEditor.vue index 32ae86ed..5215e511 100644 --- a/frontend/src/components/Controls/CodeEditor.vue +++ b/frontend/src/components/Controls/CodeEditor.vue @@ -5,7 +5,7 @@ height: height, }" > - + {{ label }}