Compare commits

..

675 Commits

Author SHA1 Message Date
joylessorchid 9041101505 Обновить README.md 2026-03-09 02:10:09 +03:00
Nicolai d3fda0be37 StudentProfile new design 2026-03-08 23:31:54 +03:00
Nicolai 41de21201e LaderBoard design update 2026-03-08 12:48:34 +03:00
Alexandrina-Kuzeleva 005f85c34f UPD 7
- add links in Mobile
- switch off InstalPromt
2026-01-28 11:06:55 +03:00
Alexandrina-Kuzeleva 6bb6125e81 Update ParentProfile.vue 2025-12-05 16:45:49 +03:00
Alexandrina-Kuzeleva 4da2b844e8 Update ParentProfile.vue 2025-12-05 16:44:39 +03:00
Alexandrina-Kuzeleva eba1923b7c Update ParentProfile.vue 2025-12-05 16:41:05 +03:00
Alexandrina-Kuzeleva f28823dbe9 TEST UPD
-add parentprofile
2025-12-05 16:33:32 +03:00
Alexandrina-Kuzeleva 5d122bca7d upd 2025-12-05 16:22:23 +03:00
Alexandrina-Kuzeleva ef4321586c TEST UPD 2025-12-05 16:13:38 +03:00
Alexandrina-Kuzeleva 336511dcd5 TEST UPD
- add traslation
- bug of doesntexist
2025-12-05 13:17:01 +03:00
Alexandrina-Kuzeleva d73b6f9026 TEST UPD
- /api/method/frappe.client.get DoesNotExistError
2025-12-05 12:51:37 +03:00
Alexandrina-Kuzeleva e959c0172d TEST UPD
-schoolchildprofile bug of learn_subjects
-front
2025-12-05 11:59:43 +03:00
Alexandrina-Kuzeleva a15767c14f TEST UPD
-some traslation
-trest schoolchildrenprofile
-frontworks
2025-12-04 18:57:34 +03:00
Alexandrina-Kuzeleva 20b1743223 TEST UPD
- course creator front
- tg icon
2025-12-04 16:06:39 +03:00
Alexandrina-Kuzeleva a89930fae6 TEST UPD
-translation
-front works
2025-12-04 15:29:50 +03:00
Alexandrina-Kuzeleva 60e81a921e TEST UPD
- translate leader board
2025-12-04 15:13:11 +03:00
Alexandrina-Kuzeleva 36f75beea9 TEST UPD
-test bio
2025-12-04 15:10:29 +03:00
Alexandrina-Kuzeleva 01a9eab73d TEST UPD
- front works
2025-12-04 14:17:57 +03:00
Alexandrina-Kuzeleva a4eff5ae38 TEST UPD 2025-12-03 22:52:16 +03:00
Alexandrina-Kuzeleva 46b5495167 TEST UPD
- tranlation My Points
- front works
2025-12-03 22:44:25 +03:00
Alexandrina-Kuzeleva 7c9ef2a702 TEST UPD
-front works for student profile
2025-12-03 22:10:43 +03:00
Alexandrina-Kuzeleva ee9aed6bbc Update Test.vue 2025-11-28 11:00:45 +03:00
Alexandrina-Kuzeleva eb4cf6e2db Update Test.vue 2025-11-28 10:57:54 +03:00
Alexandrina-Kuzeleva c6ad6b495c Update api.py 2025-11-28 10:50:48 +03:00
Alexandrina-Kuzeleva 5499a86854 Update Test.vue 2025-11-28 10:46:51 +03:00
Alexandrina-Kuzeleva 627ccd8214 Update Test.vue 2025-11-28 10:42:35 +03:00
Alexandrina-Kuzeleva e760d59d9f Update Test.vue
-front works
- fix bonus points
2025-11-28 10:39:04 +03:00
Alexandrina-Kuzeleva e76858121f TEST UPD
- front works
2025-11-28 10:27:03 +03:00
Alexandrina-Kuzeleva 34f1d02803 TEST UPD
- add roles permision in api method
2025-11-28 10:11:30 +03:00
Alexandrina-Kuzeleva 64610050ca TEST UPD
- current user card
2025-11-27 16:50:33 +03:00
Alexandrina-Kuzeleva f5bd52a94d Update Test.vue 2025-11-26 18:17:17 +03:00
Alexandrina-Kuzeleva ce603cac1e Update Test.vue 2025-11-26 18:09:45 +03:00
Alexandrina-Kuzeleva 3108235521 TEST UPD 2025-11-26 17:51:42 +03:00
Alexandrina-Kuzeleva 280aaecf76 Update Test.vue 2025-11-26 17:41:18 +03:00
Alexandrina-Kuzeleva ba0bb1eabc Update Test.vue 2025-11-26 17:36:32 +03:00
Alexandrina-Kuzeleva 73d0755249 Update Test.vue 2025-11-26 17:27:33 +03:00
Alexandrina-Kuzeleva 4d93dcb9b4 TEST 2025-11-26 17:21:35 +03:00
Alexandrina-Kuzeleva 1fc9b8e279 TEST 2025-11-26 17:15:09 +03:00
Alexandrina-Kuzeleva c6d05111cc Update LeaderBoard.vue 2025-11-25 15:34:35 +03:00
Alexandrina-Kuzeleva 8fa3d8ba4a Update LeaderBoard.vue 2025-11-25 15:30:22 +03:00
Alexandrina-Kuzeleva c5317beb3f TEST UPD 2025-11-25 15:28:23 +03:00
Alexandrina-Kuzeleva 7d82e36790 Update Test.vue 2025-11-25 15:18:58 +03:00
Alexandrina-Kuzeleva f39867b0e2 Update Test.vue 2025-11-25 15:12:07 +03:00
Alexandrina-Kuzeleva 54cef503ad TEST UPD
- add test page
2025-11-25 15:08:07 +03:00
Alexandrina-Kuzeleva ce51371e62 TEST UPD
- roles
2025-11-25 13:14:13 +03:00
Alexandrina-Kuzeleva 7aabbbd497 Update LeaderBoard.vue 2025-11-25 13:07:26 +03:00
Alexandrina-Kuzeleva 02b89ea137 TEST UPD
- debug
- user role
2025-11-25 13:00:10 +03:00
Alexandrina-Kuzeleva 119a48f3a3 Update LeaderBoard.vue 2025-11-25 12:37:50 +03:00
Alexandrina-Kuzeleva 3146a0354c Update LeaderBoard.vue 2025-11-25 12:34:06 +03:00
Alexandrina-Kuzeleva 8e895a9890 TEST UPD
- debug
2025-11-25 11:55:43 +03:00
Alexandrina-Kuzeleva ac436cbf79 TEST UPD
- roles
2025-11-25 11:23:15 +03:00
Alexandrina-Kuzeleva 4363aa7734 TEST UPD
- дурацкие названия файлов ненавижу
2025-11-25 11:10:29 +03:00
Alexandrina-Kuzeleva a65cb073b5 TEST UPD
- try to add imgs
2025-11-25 10:58:57 +03:00
Alexandrina-Kuzeleva a3b9e4f7b2 TEST UPD
- add front
2025-11-25 10:51:18 +03:00
Alexandrina-Kuzeleva ebde8a0171 TEST UPD
- try to do anthore logic of script
2025-11-25 10:26:39 +03:00
Alexandrina-Kuzeleva 684299ac3b Update LeaderBoard.vue 2025-11-21 16:47:34 +03:00
Alexandrina-Kuzeleva c449aef7ae Update LeaderBoard.vue 2025-11-21 16:41:05 +03:00
Alexandrina-Kuzeleva 879a27ed0a TEST UPD
- debug
2025-11-21 16:37:03 +03:00
Alexandrina-Kuzeleva 107e7a4e31 TEST UPD
- test leader board
2025-11-21 16:28:33 +03:00
Alexandrina-Kuzeleva fa0325106a Update MyPoints.vue 2025-11-21 15:57:41 +03:00
Alexandrina-Kuzeleva bbfce9363f TEST UPD
- font works
2025-11-21 15:47:58 +03:00
Alexandrina-Kuzeleva 10a6280b78 TEST UPD
- change weekly sum
2025-11-21 15:37:08 +03:00
Alexandrina-Kuzeleva 08e8724b4c UPD 6.1
- delete func
2025-11-21 15:29:07 +03:00
Alexandrina-Kuzeleva 555c7e4e2d TEST UPD
- add today weekly points
2025-11-21 15:18:41 +03:00
Alexandrina-Kuzeleva 3673026a33 TEST UPD
- add load more
2025-11-21 14:50:00 +03:00
Alexandrina-Kuzeleva cd565ec160 Update MyPoints.vue 2025-11-20 16:44:05 +03:00
Alexandrina-Kuzeleva bdcbae03ef TEST UPD
- front
2025-11-20 16:40:42 +03:00
Alexandrina-Kuzeleva 296234a093 TEST UPD
- remove upd
2025-11-20 16:36:39 +03:00
Alexandrina-Kuzeleva 10c0955c6c TEST UPD
- add load more
- add front
2025-11-20 16:28:21 +03:00
Alexandrina-Kuzeleva 8ba2bfda63 TEXT UPD
- front
2025-11-20 16:17:10 +03:00
Alexandrina-Kuzeleva cb06cc53c2 TEST UPD
- front works
2025-11-20 15:53:36 +03:00
Alexandrina-Kuzeleva 826828ba30 TEST UPD
- text-white remove
2025-11-20 15:34:24 +03:00
Alexandrina-Kuzeleva 22de38c72b TEST UPD
- try to fix bug of sum points
2025-11-20 15:23:59 +03:00
Alexandrina-Kuzeleva 0037c01beb TEST UPD
- problem of date
2025-11-20 13:35:11 +03:00
Alexandrina-Kuzeleva fb17c666a9 TEST UPD
- add my points
2025-11-20 13:29:09 +03:00
Alexandrina-Kuzeleva 2c32fac1f2 TEST UPD
- try to get resource
2025-11-20 13:17:23 +03:00
Alexandrina-Kuzeleva 3f0b00decd TEST UPD
- another logic of download user and data
2025-11-20 13:06:55 +03:00
Alexandrina-Kuzeleva 160c7863f0 TEST UPD
- My points page
2025-11-20 12:56:28 +03:00
Alexandrina-Kuzeleva c4d185f2d6 UPD 6
- add locale file, ru
- AI in Quizes, now it is off
- bug of translation in Courses
- fronted of tags in Course Detail in Mobile ver
- another locale bugs
2025-11-20 12:44:53 +03:00
Alexandrina-Kuzeleva 6b13b1231a UPD 5
- add Profiles for Student, Schoolchildren, Parent, Course Creator
2025-11-12 17:18:03 +03:00
Alexandrina-Kuzeleva 661137d500 UPD 4.1
- error
2025-11-11 17:38:06 +03:00
Alexandrina-Kuzeleva 962dcc1ce9 UPD 4
- add MobileLayout changes
2025-11-11 17:27:09 +03:00
Alexandrina-Kuzeleva 655df62d6c UPD 3.1
- add imports in ProfileAbout
2025-11-10 17:34:30 +03:00
Alexandrina-Kuzeleva d827a10c84 UPD 3
- checked and fixed problems: timer in lesson, sing-up, page length in courses
- add Points and Courses in ProfileAbout
NEW:
- add icon for RuTube
2025-11-10 17:28:26 +03:00
Alexandrina-Kuzeleva 25c640fabb UPD 2
- add RuTube servise
2025-11-10 17:00:10 +03:00
Alexandrina-Kuzeleva 0cb8d21290 UPD 1
- add all AppSidebar links that used
- add singup form
2025-11-10 12:27:28 +03:00
Jannat Patel 7a47591967 Merge pull request #1819 from rehanrehman389/misc-fix
fix: UI improvements
2025-11-06 16:43:28 +05:30
Jannat Patel 6931ca27c3 Merge pull request #1824 from pateljannat/issues-145
fix: roles, permission and access on profile page
2025-11-06 12:51:45 +05:30
Jannat Patel d00d2de1cc fix: export livecodeURL from settings store 2025-11-06 12:28:27 +05:30
Jannat Patel b1be568991 fix: removed uncalled function 2025-11-06 12:23:18 +05:30
Jannat Patel 28be3891d2 fix: roles, permission and access on profile page 2025-11-06 12:21:12 +05:30
Jannat Patel 27d2297e2b Merge pull request #1823 from pateljannat/issues-144
fix: misc improvements
2025-11-05 12:50:21 +05:30
Jannat Patel 7212ddd5c5 fix: evaluators and modetators can now see schedule of other evaluators 2025-11-05 12:34:40 +05:30
Jannat Patel f4e9ac5bf1 fix: IPhone PWA install prompt 2025-11-05 11:59:45 +05:30
Jannat Patel 8fec484d66 Merge pull request #1818 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-11-04 13:14:49 +05:30
Jannat Patel bcf781c37b chore: Serbian (Latin) translations 2025-11-03 00:37:46 +05:30
Jannat Patel d8a8e689d0 chore: Bosnian translations 2025-11-03 00:37:45 +05:30
Jannat Patel a844b95de3 chore: Burmese translations 2025-11-03 00:37:44 +05:30
Jannat Patel ece885f973 chore: Thai translations 2025-11-03 00:37:42 +05:30
Jannat Patel 66dd30604b chore: Tamil translations 2025-11-03 00:37:41 +05:30
Jannat Patel d0f0f4905c chore: Indonesian translations 2025-11-03 00:37:40 +05:30
Jannat Patel c9cb6702b6 chore: Portuguese, Brazilian translations 2025-11-03 00:37:39 +05:30
Jannat Patel 1ddb980242 chore: Vietnamese translations 2025-11-03 00:37:37 +05:30
Jannat Patel 94b626a4d2 chore: Chinese Simplified translations 2025-11-03 00:37:36 +05:30
Jannat Patel d2a011462d chore: Turkish translations 2025-11-03 00:37:35 +05:30
Jannat Patel 4c34926af0 chore: Serbian (Cyrillic) translations 2025-11-03 00:37:34 +05:30
Jannat Patel ce35cd1009 chore: Russian translations 2025-11-03 00:37:32 +05:30
Jannat Patel 56d072bd06 chore: Portuguese translations 2025-11-03 00:37:31 +05:30
Jannat Patel 5d336ef669 chore: Polish translations 2025-11-03 00:37:30 +05:30
Jannat Patel b47c59eac1 chore: Dutch translations 2025-11-03 00:37:29 +05:30
Jannat Patel 87285db361 chore: Italian translations 2025-11-03 00:37:27 +05:30
Jannat Patel 84312e498c chore: Hungarian translations 2025-11-03 00:37:26 +05:30
Jannat Patel bd763d9462 chore: German translations 2025-11-03 00:37:25 +05:30
Jannat Patel a00e66f786 chore: Czech translations 2025-11-03 00:37:24 +05:30
Jannat Patel 78c7b52088 chore: Arabic translations 2025-11-03 00:37:23 +05:30
Jannat Patel c3a5bee993 chore: Spanish translations 2025-11-03 00:37:21 +05:30
Jannat Patel c2b5b7c3e2 chore: French translations 2025-11-03 00:37:20 +05:30
Jannat Patel 3992f00353 chore: Persian translations 2025-11-03 00:37:19 +05:30
Jannat Patel 97d853e0d3 chore: Danish translations 2025-11-03 00:37:18 +05:30
Jannat Patel f786cec75f chore: Norwegian Bokmal translations 2025-11-03 00:37:16 +05:30
Jannat Patel 07cd08b55e chore: Croatian translations 2025-11-03 00:37:15 +05:30
Jannat Patel ca42faf14a chore: Swedish translations 2025-11-03 00:37:14 +05:30
Rehan Ansari 87f5b68279 fix: UI improvements 2025-11-02 13:43:25 +05:30
Jannat Patel 6b31edb687 chore: Esperanto translations 2025-10-31 23:03:40 +05:30
Jannat Patel 6a64048bb6 Merge pull request #1817 from frappe/pot_develop_2025-10-31
chore: update POT file
2025-10-31 22:03:56 +05:30
frappe-pr-bot 6cf069ee6a chore: update POT file 2025-10-31 16:04:36 +00:00
Jannat Patel 3b74bba6ab Merge pull request #1813 from rehanrehman389/missing-type-fix
fix: add missing type prop
2025-10-30 11:13:31 +05:30
Rehan Ansari 8689788523 fix: add missing type prop 2025-10-29 21:41:48 +05:30
Frappe PR Bot 1193776d06 chore(release): Bumped to Version 2.39.2 2025-10-29 12:29:14 +00:00
Jannat Patel 022514a0a7 Merge pull request #1810 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-28 10:56:10 +05:30
Jannat Patel dc7f8a59ed chore: Esperanto translations 2025-10-27 22:34:42 +05:30
Jannat Patel 4e5a76a6c1 chore: Serbian (Latin) translations 2025-10-27 22:34:41 +05:30
Jannat Patel 64c4a25ee8 chore: Bosnian translations 2025-10-27 22:34:39 +05:30
Jannat Patel 47bbdbaa26 chore: Burmese translations 2025-10-27 22:34:38 +05:30
Jannat Patel f8e0c0e19a chore: Thai translations 2025-10-27 22:34:37 +05:30
Jannat Patel 94cdd19224 chore: Tamil translations 2025-10-27 22:34:35 +05:30
Jannat Patel d86d046eb0 chore: Indonesian translations 2025-10-27 22:34:34 +05:30
Jannat Patel 25ec6b5a3f chore: Portuguese, Brazilian translations 2025-10-27 22:34:32 +05:30
Jannat Patel 967453a683 chore: Vietnamese translations 2025-10-27 22:34:30 +05:30
Jannat Patel 4c17305c05 chore: Chinese Simplified translations 2025-10-27 22:34:29 +05:30
Jannat Patel 6092131303 chore: Turkish translations 2025-10-27 22:34:28 +05:30
Jannat Patel 35749834d0 chore: Serbian (Cyrillic) translations 2025-10-27 22:34:26 +05:30
Jannat Patel fe56c7b887 chore: Russian translations 2025-10-27 22:34:25 +05:30
Jannat Patel 2b58a744d2 chore: Portuguese translations 2025-10-27 22:34:24 +05:30
Jannat Patel dfb94d05e4 chore: Polish translations 2025-10-27 22:34:22 +05:30
Jannat Patel 987c1790d8 chore: Dutch translations 2025-10-27 22:34:21 +05:30
Jannat Patel 0d416b17ce chore: Italian translations 2025-10-27 22:34:19 +05:30
Jannat Patel 473e165c89 chore: Hungarian translations 2025-10-27 22:34:18 +05:30
Jannat Patel 3d52d15004 chore: German translations 2025-10-27 22:34:17 +05:30
Jannat Patel 27278e128c chore: Czech translations 2025-10-27 22:34:16 +05:30
Jannat Patel 13cee3c9b3 chore: Arabic translations 2025-10-27 22:34:14 +05:30
Jannat Patel fd95e42e9b chore: Spanish translations 2025-10-27 22:34:13 +05:30
Jannat Patel 65cd2f5d01 chore: French translations 2025-10-27 22:34:11 +05:30
Jannat Patel 70759d1888 chore: Persian translations 2025-10-27 22:34:10 +05:30
Jannat Patel 705d6e2f00 chore: Danish translations 2025-10-27 22:34:09 +05:30
Jannat Patel 088591a335 chore: Norwegian Bokmal translations 2025-10-27 22:34:07 +05:30
Jannat Patel 3f037e0d17 chore: Croatian translations 2025-10-27 22:34:06 +05:30
Jannat Patel e6884b6c93 chore: Swedish translations 2025-10-27 22:34:04 +05:30
Jannat Patel 9943268ca0 Merge pull request #1809 from pateljannat/issues-143
fix: resolved the issue that appeared when adding a chapter
2025-10-27 11:50:25 +05:30
Jannat Patel 620e4d20c2 Merge pull request #1805 from rehanrehman389/dark-mode-fix
fix: multiple fixes for dark mode visibility
2025-10-27 11:39:26 +05:30
Jannat Patel fd03033ac6 Merge pull request #1804 from frappe/pot_develop_2025-10-24
chore: update POT file
2025-10-27 11:37:35 +05:30
Jannat Patel 939099b8c8 Merge pull request #1808 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-27 11:37:19 +05:30
Jannat Patel 75001b494d fix: escape HTML in job form fields 2025-10-27 11:36:46 +05:30
Jannat Patel 8749e21744 fix: only users with moderator and instructor role should have access to quiz form 2025-10-27 11:06:09 +05:30
Jannat Patel 982ac98e27 fix: resolved the issue that appeared when adding a chapter 2025-10-27 10:32:24 +05:30
Jannat Patel f31bf17a41 chore: Polish translations 2025-10-26 21:43:16 +05:30
Rehan Ansari 3425d9118d fix: NotPermitted text visibility in dark mode 2025-10-26 00:19:46 +05:30
Rehan Ansari 6be49ecdf3 fix: multiple fixes for dark mode visibility 2025-10-25 15:38:08 +05:30
frappe-pr-bot ffd6f9578b chore: update POT file 2025-10-24 16:04:25 +00:00
Jannat Patel 41293130ad Merge pull request #1803 from rehanrehman389/brand-setting
fix: handle missing file_url
2025-10-24 10:22:33 +05:30
Rehan Ansari 6cccd28b92 fix: handle missing file_url 2025-10-23 22:50:48 +05:30
Jannat Patel 384f10a722 Merge pull request #1801 from rehanrehman389/streak-fix
fix: improve visibility in dark mode
2025-10-23 18:37:36 +05:30
Jannat Patel a603e299f1 Merge pull request #1800 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-23 18:36:46 +05:30
Rehan Ansari 05822f82da fix: streak number visibility in dark mode 2025-10-22 23:24:02 +05:30
Rehan Ansari 0508e718cb fix: job modal text in dark mode 2025-10-22 22:56:54 +05:30
Rehan Ansari 574913e9e4 fix: improve visibility in billing page 2025-10-22 22:35:01 +05:30
Rehan Ansari 068adb62a7 fix: improve visibility in dark mode 2025-10-22 21:55:03 +05:30
Jannat Patel 73fa1f9cfe chore: Serbian (Latin) translations 2025-10-22 21:17:52 +05:30
Jannat Patel f518882926 chore: Serbian (Cyrillic) translations 2025-10-22 21:17:42 +05:30
Jannat Patel ed566f9eea Merge pull request #1794 from rehanrehman389/setting-scroll-fix
fix: scroll issue in settings
2025-10-22 10:56:40 +05:30
Jannat Patel 8ca32e439a Merge pull request #1795 from rehanrehman389/notes-dark-mode
fix: update font color in dark mode for My Notes
2025-10-22 10:47:35 +05:30
Jannat Patel 35b3b11a3c Merge pull request #1798 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-22 10:46:45 +05:30
Jannat Patel 57d4a53081 chore: Bosnian translations 2025-10-21 21:17:36 +05:30
Jannat Patel 6da05961f2 chore: Polish translations 2025-10-21 21:17:35 +05:30
Jannat Patel 7db3b8c5b8 chore: Danish translations 2025-10-21 21:17:32 +05:30
Jannat Patel 50bafb6fa6 chore: Croatian translations 2025-10-21 21:17:31 +05:30
Jannat Patel 2b3a9072d1 Merge pull request #1793 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-21 21:11:41 +05:30
Rehan Ansari 1c08e57086 fix: update font color in dark mode for My Notes 2025-10-21 13:57:52 +05:30
Rehan Ansari 4290ed2f04 fix: scroll issue in settings 2025-10-20 21:42:41 +05:30
Jannat Patel 342512f3e1 chore: Esperanto translations 2025-10-20 20:49:25 +05:30
Jannat Patel 942c04cb68 chore: Serbian (Latin) translations 2025-10-20 20:49:23 +05:30
Jannat Patel 64bf4ab3f7 chore: Bosnian translations 2025-10-20 20:49:22 +05:30
Jannat Patel 052e69737e chore: Burmese translations 2025-10-20 20:49:21 +05:30
Jannat Patel 02adc4517c chore: Thai translations 2025-10-20 20:49:19 +05:30
Jannat Patel e36fdd6823 chore: Tamil translations 2025-10-20 20:49:18 +05:30
Jannat Patel d10a7ed57f chore: Indonesian translations 2025-10-20 20:49:16 +05:30
Jannat Patel 79adf44dfe chore: Portuguese, Brazilian translations 2025-10-20 20:49:15 +05:30
Jannat Patel 4bc3113f34 chore: Vietnamese translations 2025-10-20 20:49:14 +05:30
Jannat Patel 0826704282 chore: Chinese Simplified translations 2025-10-20 20:49:12 +05:30
Jannat Patel 52aa5e6954 chore: Turkish translations 2025-10-20 20:49:11 +05:30
Jannat Patel fde85607d9 chore: Serbian (Cyrillic) translations 2025-10-20 20:49:09 +05:30
Jannat Patel cc087af012 chore: Russian translations 2025-10-20 20:49:08 +05:30
Jannat Patel 2c7da1e32e chore: Portuguese translations 2025-10-20 20:49:06 +05:30
Jannat Patel 49fe8952ae chore: Polish translations 2025-10-20 20:49:05 +05:30
Jannat Patel b298cd0509 chore: Dutch translations 2025-10-20 20:49:04 +05:30
Jannat Patel a81fc11e73 chore: Italian translations 2025-10-20 20:49:02 +05:30
Jannat Patel 199fb6229d chore: Hungarian translations 2025-10-20 20:49:01 +05:30
Jannat Patel ec6ecee455 chore: German translations 2025-10-20 20:48:59 +05:30
Jannat Patel fa72172b77 chore: Czech translations 2025-10-20 20:48:58 +05:30
Jannat Patel 6789700def chore: Arabic translations 2025-10-20 20:48:56 +05:30
Jannat Patel 752744b3a4 chore: Spanish translations 2025-10-20 20:48:55 +05:30
Jannat Patel c24fa85bf4 chore: French translations 2025-10-20 20:48:54 +05:30
Jannat Patel 4e0b59f6a9 chore: Persian translations 2025-10-20 20:48:52 +05:30
Jannat Patel bd20214552 chore: Danish translations 2025-10-20 20:48:51 +05:30
Jannat Patel 4af0ea9e47 chore: Norwegian Bokmal translations 2025-10-20 20:48:49 +05:30
Jannat Patel 8651679634 chore: Croatian translations 2025-10-20 20:48:48 +05:30
Jannat Patel 99dcac6d12 chore: Swedish translations 2025-10-20 20:48:46 +05:30
Frappe PR Bot 853bf01c9e chore(release): Bumped to Version 2.39.1 2025-10-20 06:28:36 +00:00
Jannat Patel 39c5ad7267 Merge pull request #1788 from frappe/pot_develop_2025-10-17
chore: update POT file
2025-10-20 11:55:24 +05:30
Jannat Patel 8daa2948fa Merge pull request #1787 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-20 11:55:10 +05:30
Jannat Patel f2ba25429e chore: Persian translations 2025-10-19 20:29:28 +05:30
Jannat Patel 1fffb4dc67 chore: Danish translations 2025-10-19 20:29:27 +05:30
frappe-pr-bot 45ce2439fd chore: update POT file 2025-10-17 16:04:25 +00:00
Jannat Patel cb2e77e8f6 chore: Norwegian Bokmal translations 2025-10-17 20:15:15 +05:30
Jannat Patel 800c0b0336 chore: Croatian translations 2025-10-17 20:15:13 +05:30
Jannat Patel 14c23496d5 chore: Swedish translations 2025-10-17 20:15:12 +05:30
Jannat Patel 7756a6d593 fix: increased the rate limit 2025-10-17 15:59:32 +05:30
Jannat Patel ae7791a204 Merge branch 'develop' of https://github.com/frappe/lms into develop 2025-10-15 14:45:14 +05:30
Jannat Patel 44232c44fc fix: activation level doctype name 2025-10-15 14:44:53 +05:30
Jannat Patel 142fc99761 Merge pull request #1783 from pateljannat/issues-140
fix: misc issues
2025-10-15 12:08:39 +05:30
Jannat Patel 5e6dc55c76 fix: added rate limit to all apis that can be accessed by guest 2025-10-15 11:55:58 +05:30
Jannat Patel bb2447e821 fix: misc issues 2025-10-15 11:52:23 +05:30
Frappe PR Bot a88d9cd78e chore(release): Bumped to Version 2.39.0 2025-10-15 05:13:17 +00:00
Jannat Patel dab82db693 Merge pull request #1779 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-15 10:38:52 +05:30
Jannat Patel a1183df72c Merge pull request #1780 from pateljannat/issues-139
fix: moderators should be able to access unpublished courses
2025-10-14 20:46:14 +05:30
Jannat Patel 5cfa4f173a fix: moderators should be able to access unpublished courses 2025-10-14 19:27:45 +05:30
Jannat Patel 451ef49d98 chore: Serbian (Latin) translations 2025-10-14 19:20:20 +05:30
Jannat Patel 36a8ebdc1b chore: Serbian (Cyrillic) translations 2025-10-14 19:20:07 +05:30
Jannat Patel 27577edb16 Merge pull request #1776 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-14 10:35:24 +05:30
Jannat Patel bd69ab314b chore: Burmese translations 2025-10-13 19:07:28 +05:30
Jannat Patel 6abe4ac04a chore: Tamil translations 2025-10-13 19:07:26 +05:30
Jannat Patel 1eeb190653 chore: Esperanto translations 2025-10-13 19:07:24 +05:30
Jannat Patel 23f22d9d9a chore: Serbian (Latin) translations 2025-10-13 19:07:22 +05:30
Jannat Patel b913f33b3f chore: Norwegian Bokmal translations 2025-10-13 19:07:21 +05:30
Jannat Patel 52da1feb91 chore: Bosnian translations 2025-10-13 19:07:18 +05:30
Jannat Patel 0af9f1bfbf chore: Croatian translations 2025-10-13 19:07:16 +05:30
Jannat Patel 3b0e1c3ce7 chore: Thai translations 2025-10-13 19:07:14 +05:30
Jannat Patel 37ea270c56 chore: Persian translations 2025-10-13 19:07:12 +05:30
Jannat Patel 304550dd94 chore: Indonesian translations 2025-10-13 19:07:11 +05:30
Jannat Patel 69cee24ffe chore: Portuguese, Brazilian translations 2025-10-13 19:07:09 +05:30
Jannat Patel 8334c06a9b chore: Vietnamese translations 2025-10-13 19:07:07 +05:30
Jannat Patel 3e739f2877 chore: Chinese Simplified translations 2025-10-13 19:07:05 +05:30
Jannat Patel 5e33ff4a34 chore: Turkish translations 2025-10-13 19:07:03 +05:30
Jannat Patel 2b234e5d64 chore: Swedish translations 2025-10-13 19:07:01 +05:30
Jannat Patel c8b328a1c9 chore: Serbian (Cyrillic) translations 2025-10-13 19:07:00 +05:30
Jannat Patel 5e0ac05f90 chore: Russian translations 2025-10-13 19:06:58 +05:30
Jannat Patel e440097272 chore: Portuguese translations 2025-10-13 19:06:57 +05:30
Jannat Patel 263c858a66 chore: Polish translations 2025-10-13 19:06:55 +05:30
Jannat Patel 82371fb2a8 chore: Dutch translations 2025-10-13 19:06:53 +05:30
Jannat Patel b6336a4096 chore: Italian translations 2025-10-13 19:06:51 +05:30
Jannat Patel 7acfbbaae7 chore: Hungarian translations 2025-10-13 19:06:50 +05:30
Jannat Patel 5a76b4eb2d chore: German translations 2025-10-13 19:06:48 +05:30
Jannat Patel 8aac88b696 chore: Danish translations 2025-10-13 19:06:47 +05:30
Jannat Patel d6e71068be chore: Czech translations 2025-10-13 19:06:45 +05:30
Jannat Patel 6b44951ef0 chore: Arabic translations 2025-10-13 19:06:43 +05:30
Jannat Patel 5289ebb923 chore: Spanish translations 2025-10-13 19:06:42 +05:30
Jannat Patel 263fcda053 chore: French translations 2025-10-13 19:06:40 +05:30
Jannat Patel 2e0d26575e Merge pull request #1775 from pateljannat/issues-138
fix: empty state for profile certificates section
2025-10-13 15:24:22 +05:30
Jannat Patel b9d6670bee fix: timezone for direct evaluation courses 2025-10-13 15:08:29 +05:30
Jannat Patel f20d39a3e7 fix: empty state for profile certificates section 2025-10-13 14:56:08 +05:30
Jannat Patel 09d948b3a0 Merge pull request #1774 from pateljannat/contact-us-mail-to
feat: contact us email modal
2025-10-13 13:22:26 +05:30
Jannat Patel 96941c83f3 fix: improved sidebar for settings 2025-10-13 13:14:13 +05:30
Jannat Patel b8ca0e381a feat: contact us email modal 2025-10-13 11:34:46 +05:30
Jannat Patel 4a2c5d77aa Merge pull request #1770 from frappe/pot_develop_2025-10-10
chore: update POT file
2025-10-13 11:11:39 +05:30
Jannat Patel cf2d29d82e Merge pull request #1771 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-13 11:11:27 +05:30
Jannat Patel fb2ab63550 chore: Polish translations 2025-10-12 18:34:31 +05:30
Jannat Patel 90efc152a8 chore: Burmese translations 2025-10-11 17:41:57 +05:30
frappe-pr-bot de6ba49409 chore: update POT file 2025-10-10 16:04:57 +00:00
Frappe PR Bot 9d4196f15a chore(release): Bumped to Version 2.38.0 2025-10-10 10:59:06 +00:00
Jannat Patel eed7fb970d Merge pull request #1769 from pateljannat/issues-137
fix: private file uploads in assignment text editor
2025-10-10 16:27:00 +05:30
Jannat Patel fe67f1ab61 Merge pull request #1767 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-10 15:55:51 +05:30
Jannat Patel 78640561f5 fix: private file uploads in assignment text editor 2025-10-10 15:52:35 +05:30
Jannat Patel f72631a262 fix: reverted the alternative host change 2025-10-10 12:31:49 +05:30
Jannat Patel 670e5d0202 Merge pull request #1768 from pateljannat/issues-136
fix: misc zoom issues
2025-10-10 11:48:47 +05:30
Jannat Patel ea59d1158a fix: misc zoom issues 2025-10-10 11:40:19 +05:30
Jannat Patel ba23cf9789 fix: misc zoom issues 2025-10-10 11:39:40 +05:30
Jannat Patel de585b90ea chore: Persian translations 2025-10-09 16:56:19 +05:30
Jannat Patel cf40f4e525 Merge pull request #1766 from pateljannat/issues-135
fix: live class issues
2025-10-09 15:50:01 +05:30
Jannat Patel b273e34ac8 fix: live class issues 2025-10-09 15:35:00 +05:30
Jannat Patel 1a00d708e1 Merge pull request #1762 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-08 17:10:46 +05:30
Jannat Patel 583584d8c5 Merge pull request #1763 from pateljannat/issues-134
fix: misc issues
2025-10-07 19:56:47 +05:30
Jannat Patel 09c087dee7 fix: misc issues 2025-10-07 19:24:47 +05:30
Jannat Patel f5cff50674 chore: Burmese translations 2025-10-07 17:03:51 +05:30
Jannat Patel 81c48d5182 chore: Persian translations 2025-10-07 17:03:43 +05:30
Jannat Patel c44414cadb chore: Portuguese, Brazilian translations 2025-10-07 17:03:41 +05:30
Jannat Patel 85db4be514 chore: Arabic translations 2025-10-07 17:03:24 +05:30
Jannat Patel 6526eefaf5 chore: French translations 2025-10-07 17:03:21 +05:30
Jannat Patel bd1fc5d705 Merge pull request #1760 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-07 10:46:27 +05:30
Jannat Patel ca547023b0 chore: Tamil translations 2025-10-06 16:21:31 +05:30
Jannat Patel caf57d355b chore: Esperanto translations 2025-10-06 16:21:30 +05:30
Jannat Patel 7e6da62480 chore: Serbian (Latin) translations 2025-10-06 16:21:28 +05:30
Jannat Patel e14e560415 chore: Norwegian Bokmal translations 2025-10-06 16:21:26 +05:30
Jannat Patel 0a8ac87cee chore: Bosnian translations 2025-10-06 16:21:25 +05:30
Jannat Patel 42441aafd6 chore: Croatian translations 2025-10-06 16:21:23 +05:30
Jannat Patel 9c3ff958e3 chore: Thai translations 2025-10-06 16:21:21 +05:30
Jannat Patel efac7af750 chore: Persian translations 2025-10-06 16:21:20 +05:30
Jannat Patel 314935f68e chore: Indonesian translations 2025-10-06 16:21:18 +05:30
Jannat Patel 1efa857d95 chore: Portuguese, Brazilian translations 2025-10-06 16:21:16 +05:30
Jannat Patel a7409b498e chore: Vietnamese translations 2025-10-06 16:21:13 +05:30
Jannat Patel a9cb0a8c26 chore: Chinese Simplified translations 2025-10-06 16:21:12 +05:30
Jannat Patel 9333affaf1 chore: Turkish translations 2025-10-06 16:21:10 +05:30
Jannat Patel 38a32be503 chore: Swedish translations 2025-10-06 16:21:08 +05:30
Jannat Patel fe5f7daf78 chore: Serbian (Cyrillic) translations 2025-10-06 16:20:58 +05:30
Jannat Patel 3c07c3e1cf chore: Russian translations 2025-10-06 16:20:57 +05:30
Jannat Patel cb87c75ac0 chore: Portuguese translations 2025-10-06 16:20:55 +05:30
Jannat Patel 62ead16817 chore: Polish translations 2025-10-06 16:20:53 +05:30
Jannat Patel 6352e4deb1 chore: Dutch translations 2025-10-06 16:20:52 +05:30
Jannat Patel 0c4b569be6 chore: Italian translations 2025-10-06 16:20:51 +05:30
Jannat Patel fe4d7cfb75 chore: Hungarian translations 2025-10-06 16:20:49 +05:30
Jannat Patel d3e791b017 chore: German translations 2025-10-06 16:20:48 +05:30
Jannat Patel 0849183d26 chore: Danish translations 2025-10-06 16:20:46 +05:30
Jannat Patel 3410af8899 chore: Czech translations 2025-10-06 16:20:45 +05:30
Jannat Patel 81a1e3a4c3 chore: Arabic translations 2025-10-06 16:20:44 +05:30
Jannat Patel c8e18dc445 chore: Spanish translations 2025-10-06 16:20:42 +05:30
Jannat Patel ad21bd6f53 chore: French translations 2025-10-06 16:20:41 +05:30
Jannat Patel 781457fce3 Merge pull request #1759 from pateljannat/contact-us
feat: contact us
2025-10-06 11:11:19 +05:30
Jannat Patel 6662b713f1 Merge pull request #1757 from frappe/pot_develop_2025-10-03
chore: update POT file
2025-10-06 10:57:28 +05:30
Jannat Patel 34c0d16411 refactor: changed Certified Members to Certifications 2025-10-06 10:33:50 +05:30
Jannat Patel f7003ecbbe feat: contact us 2025-10-06 10:15:53 +05:30
frappe-pr-bot 134090df5d chore: update POT file 2025-10-03 16:04:25 +00:00
Jannat Patel efb4feab2e Merge pull request #1755 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-03 10:44:01 +05:30
Jannat Patel 900e959b0b chore: Tamil translations 2025-10-01 14:02:00 +05:30
Jannat Patel 946ffeb3ca chore: Portuguese, Brazilian translations 2025-10-01 14:01:52 +05:30
Jannat Patel 56d32b0674 chore: Vietnamese translations 2025-10-01 14:01:50 +05:30
Jannat Patel 03fc5c084a chore: Chinese Simplified translations 2025-10-01 14:01:48 +05:30
Jannat Patel 2a32355dd8 chore: Polish translations 2025-10-01 14:01:42 +05:30
Jannat Patel d80d0e9d9b chore: Hungarian translations 2025-10-01 14:01:38 +05:30
Jannat Patel be0388ee6e chore: Arabic translations 2025-10-01 14:01:33 +05:30
Jannat Patel 414c41162a chore: French translations 2025-10-01 14:01:31 +05:30
Frappe PR Bot 39106de96c chore(release): Bumped to Version 2.37.0 2025-10-01 07:23:35 +00:00
Jannat Patel 8adb76abfb Merge pull request #1753 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-10-01 12:52:01 +05:30
Jannat Patel 4889b04283 chore: Persian translations 2025-09-30 14:05:54 +05:30
Jannat Patel e9973a242b Merge pull request #1748 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-09-30 10:29:49 +05:30
Jannat Patel d324ad0ac5 Merge pull request #1752 from pateljannat/issues-132
fix: misc issues
2025-09-30 10:29:33 +05:30
Jannat Patel ab6cc2698a fix: assignment should be uploaded as private file 2025-09-29 18:27:50 +05:30
Jannat Patel da076f71a1 fix: when course is unpublished and not coming either, user should not be able to access the course details 2025-09-29 18:09:03 +05:30
Jannat Patel 63c1fe8e75 fix: border when course card has an image 2025-09-29 15:26:14 +05:30
Jannat Patel 31f0833629 chore: removed unused files 2025-09-29 15:13:39 +05:30
Jannat Patel d4dc094049 chore: Serbian (Latin) translations 2025-09-29 13:56:06 +05:30
Jannat Patel 21a84e8032 chore: Serbian (Cyrillic) translations 2025-09-29 13:55:53 +05:30
Jannat Patel 819a1baae0 Merge pull request #1745 from UmakanthKaspa/fix/dark-theme-text-visibility
fix: make text visible in dark theme
2025-09-29 12:17:56 +05:30
Jannat Patel b0ee67faff Merge pull request #1743 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-09-29 12:17:14 +05:30
Jannat Patel 0684bd105a chore: Norwegian Bokmal translations 2025-09-28 13:59:13 +05:30
UmakanthKaspa 5c948862a0 fix: remove extra space in CSS class 2025-09-27 14:15:39 +00:00
UmakanthKaspa 23291b38de fix: add dark theme text colors to Create Program dialog 2025-09-27 13:59:07 +00:00
UmakanthKaspa 9357bd55a6 fix: make text visible in dark theme
- Add text-ink-gray-9 to headings
- Text now shows in both light and dark themes
2025-09-27 13:04:07 +00:00
Jannat Patel e51d668418 chore: Esperanto translations 2025-09-27 13:46:00 +05:30
Jannat Patel a48df70631 chore: Serbian (Latin) translations 2025-09-27 13:45:59 +05:30
Jannat Patel 9853e8311b chore: Norwegian Bokmal translations 2025-09-27 13:45:57 +05:30
Jannat Patel cd8041b048 chore: Bosnian translations 2025-09-27 13:45:56 +05:30
Jannat Patel 00bf5b7ad6 chore: Croatian translations 2025-09-27 13:45:54 +05:30
Jannat Patel 40bcc983c7 chore: Thai translations 2025-09-27 13:45:53 +05:30
Jannat Patel dfb26f31db chore: Persian translations 2025-09-27 13:45:51 +05:30
Jannat Patel a57a0bebef chore: Indonesian translations 2025-09-27 13:45:50 +05:30
Jannat Patel 4853621b1b chore: Portuguese, Brazilian translations 2025-09-27 13:45:49 +05:30
Jannat Patel e3209c6fb3 chore: Vietnamese translations 2025-09-27 13:45:48 +05:30
Jannat Patel 13d25cce1f chore: Chinese Simplified translations 2025-09-27 13:45:47 +05:30
Jannat Patel cb16b0ca64 chore: Turkish translations 2025-09-27 13:45:45 +05:30
Jannat Patel b3ce3159e7 chore: Swedish translations 2025-09-27 13:45:44 +05:30
Jannat Patel 76ffc70892 chore: Serbian (Cyrillic) translations 2025-09-27 13:45:42 +05:30
Jannat Patel 2734587981 chore: Russian translations 2025-09-27 13:45:41 +05:30
Jannat Patel 223e93d654 chore: Portuguese translations 2025-09-27 13:45:39 +05:30
Jannat Patel 26351726a8 chore: Polish translations 2025-09-27 13:45:38 +05:30
Jannat Patel efc84db580 chore: Dutch translations 2025-09-27 13:45:37 +05:30
Jannat Patel 3bf58bb6f0 chore: Italian translations 2025-09-27 13:45:35 +05:30
Jannat Patel a7962d9404 chore: Hungarian translations 2025-09-27 13:45:34 +05:30
Jannat Patel 5ce9bb306d chore: German translations 2025-09-27 13:45:33 +05:30
Jannat Patel 5b5bb38f4f chore: Danish translations 2025-09-27 13:45:32 +05:30
Jannat Patel 419ad311a0 chore: Czech translations 2025-09-27 13:45:30 +05:30
Jannat Patel 0c3af09566 chore: Arabic translations 2025-09-27 13:45:29 +05:30
Jannat Patel c9063625ec chore: Spanish translations 2025-09-27 13:45:28 +05:30
Jannat Patel 205858a41d chore: French translations 2025-09-27 13:45:26 +05:30
Jannat Patel 87edad17c3 Merge pull request #1742 from frappe/pot_develop_2025-09-26
chore: update POT file
2025-09-26 22:45:31 +05:30
frappe-pr-bot 1c54e80951 chore: update POT file 2025-09-26 16:04:27 +00:00
Jannat Patel 6c19cdc729 Merge pull request #1741 from pateljannat/fui-upgrade
chore: upgraded frappe ui and made relevant changes
2025-09-25 18:30:15 +05:30
Jannat Patel 84a703bb50 test: updated tests as per latest frappe ui version 2025-09-25 18:19:03 +05:30
Jannat Patel 27ed95b044 Merge pull request #1739 from KerollesFathy/work-mode-on-job-portal
feat(jobs): Add Work Mode on Job Portal
2025-09-25 17:42:57 +05:30
Jannat Patel 0358dfe790 fix: upgraded evaluator schedule as per latest frappe ui 2025-09-25 17:42:12 +05:30
KerollesFathy 68cee65f22 refactor: make all filters on the same line 2025-09-25 11:59:57 +00:00
Jannat Patel 24b2125b97 chore: upgraded frappe ui and made relevant changes 2025-09-25 16:46:29 +05:30
Jannat Patel eceed12992 Merge pull request #1740 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-09-25 13:02:02 +05:30
Jannat Patel f9028433a0 chore: Serbian (Latin) translations 2025-09-25 12:44:36 +05:30
Jannat Patel 1936e7b212 chore: Bosnian translations 2025-09-25 12:44:33 +05:30
Jannat Patel c04a972be3 chore: Croatian translations 2025-09-25 12:44:32 +05:30
Jannat Patel 9c26b011e4 chore: Swedish translations 2025-09-25 12:44:23 +05:30
Jannat Patel 821f125789 chore: Serbian (Cyrillic) translations 2025-09-25 12:44:22 +05:30
Jannat Patel e0f376880a chore: Dutch translations 2025-09-25 12:44:18 +05:30
Jannat Patel 6da77bb3c7 chore: Hungarian translations 2025-09-25 12:44:15 +05:30
Jannat Patel 2fd660a93f chore: German translations 2025-09-25 12:44:14 +05:30
Jannat Patel f3eefc748a chore: Danish translations 2025-09-25 12:44:12 +05:30
Jannat Patel 4852698d74 chore: Czech translations 2025-09-25 12:44:11 +05:30
Jannat Patel 36b24dc826 chore: Arabic translations 2025-09-25 12:44:09 +05:30
Jannat Patel d60bc1d4b6 chore: Spanish translations 2025-09-25 12:44:08 +05:30
Jannat Patel 06a02c0877 chore: French translations 2025-09-25 12:44:06 +05:30
KerollesFathy 1a53a9f30b feat: Add work mode selection to job form 2025-09-24 14:24:08 +00:00
KerollesFathy 7ee81d4693 feat: Add work mode badge to job detail page 2025-09-24 14:23:45 +00:00
KerollesFathy 0a32d03fda feat: Display work mode badge on job card 2025-09-24 14:22:59 +00:00
KerollesFathy 6c43dfea18 feat: Add work mode filter and selection to job opportunities 2025-09-24 14:22:07 +00:00
KerollesFathy 7d1e226743 feat: Add work mode on job opportunity 2025-09-24 14:21:03 +00:00
Frappe PR Bot 8ea903b81a chore(release): Bumped to Version 2.36.0 2025-09-24 07:17:24 +00:00
Jannat Patel 24b08599b3 Merge pull request #1737 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-09-24 12:46:37 +05:30
Jannat Patel a0f1c1f227 chore: Esperanto translations 2025-09-24 12:46:22 +05:30
Jannat Patel efbb014588 chore: Serbian (Latin) translations 2025-09-24 12:46:20 +05:30
Jannat Patel f881a0e1d5 chore: Norwegian Bokmal translations 2025-09-24 12:46:19 +05:30
Jannat Patel acdb81e8a3 chore: Bosnian translations 2025-09-24 12:46:17 +05:30
Jannat Patel 6559a87323 chore: Croatian translations 2025-09-24 12:46:15 +05:30
Jannat Patel 8301bab768 chore: Thai translations 2025-09-24 12:46:14 +05:30
Jannat Patel eb0b2010f9 chore: Persian translations 2025-09-24 12:46:12 +05:30
Jannat Patel 8158ea164d chore: Indonesian translations 2025-09-24 12:46:10 +05:30
Jannat Patel beb3134af9 chore: Portuguese, Brazilian translations 2025-09-24 12:46:08 +05:30
Jannat Patel 5fdc6a21a5 chore: Vietnamese translations 2025-09-24 12:46:07 +05:30
Jannat Patel e7516c57bc chore: Chinese Simplified translations 2025-09-24 12:46:05 +05:30
Jannat Patel 2169d81b73 chore: Turkish translations 2025-09-24 12:46:04 +05:30
Jannat Patel 1d6bb9f9f6 chore: Swedish translations 2025-09-24 12:46:02 +05:30
Jannat Patel 812bd07d03 chore: Serbian (Cyrillic) translations 2025-09-24 12:46:01 +05:30
Jannat Patel 19bb02f905 chore: Russian translations 2025-09-24 12:45:59 +05:30
Jannat Patel 58d750f726 chore: Portuguese translations 2025-09-24 12:45:58 +05:30
Jannat Patel 7f3bb58ec1 chore: Polish translations 2025-09-24 12:45:56 +05:30
Jannat Patel 08ceaf204f chore: Dutch translations 2025-09-24 12:45:54 +05:30
Jannat Patel eced1221a8 chore: Italian translations 2025-09-24 12:45:52 +05:30
Jannat Patel 3a5bbde0cc chore: Hungarian translations 2025-09-24 12:45:51 +05:30
Jannat Patel eb1a790485 chore: German translations 2025-09-24 12:45:49 +05:30
Jannat Patel 21e9c85bf7 chore: Danish translations 2025-09-24 12:45:47 +05:30
Jannat Patel 632f783d57 chore: Czech translations 2025-09-24 12:45:46 +05:30
Jannat Patel 3ba3908108 chore: Arabic translations 2025-09-24 12:45:44 +05:30
Jannat Patel 903a4e91b0 chore: Spanish translations 2025-09-24 12:45:43 +05:30
Jannat Patel 6496b129ce chore: French translations 2025-09-24 12:45:41 +05:30
Jannat Patel 9fff7a2ea8 Merge pull request #1736 from pateljannat/pwa-customizations
feat: PWA Customizations
2025-09-24 12:26:54 +05:30
Jannat Patel 6d94617e59 feat: PWA Customizations 2025-09-24 12:11:12 +05:30
Jannat Patel b95d07babb Merge pull request #1732 from pateljannat/activation
chore: site data for analytics
2025-09-23 14:54:47 +05:30
Jannat Patel 9748d075fa chore: site data for analytics 2025-09-23 13:00:58 +05:30
Jannat Patel aaeeb84ed3 Merge pull request #1726 from pateljannat/payment-settings-refactor
refactor: payment settings
2025-09-23 11:37:03 +05:30
Jannat Patel c3702ee6d5 feat: transaction list 2025-09-23 11:26:46 +05:30
Jannat Patel f239987043 feat: new payment gateway creation from settings 2025-09-22 14:19:27 +05:30
Jannat Patel 6aa67c3fae Merge pull request #1727 from frappe/pot_develop_2025-09-19
chore: update POT file
2025-09-22 11:16:45 +05:30
frappe-pr-bot 1fc5d75cc0 chore: update POT file 2025-09-19 16:04:26 +00:00
Frappe PR Bot 2e0cd1964c chore(release): Bumped to Version 2.35.0 2025-09-18 13:01:47 +00:00
Jannat Patel bcfd3bb636 refactor: payment settings 2025-09-18 18:29:08 +05:30
Jannat Patel 9b893b56f4 Merge branch 'develop' of https://github.com/frappe/lms into develop 2025-09-17 16:00:52 +05:30
Jannat Patel 733254e2c0 fix: URL in iframe for programming exercises 2025-09-17 15:58:02 +05:30
Jannat Patel 1e351e60a5 Merge pull request #1725 from pateljannat/language-control
feat: switch language from profile
2025-09-17 15:55:37 +05:30
Jannat Patel 90c53e4b46 feat: switch language from profile 2025-09-17 15:46:28 +05:30
Jannat Patel 69f54a00c5 Merge pull request #1263 from FahidLatheef/feat/scorm-progress
feat: SCORM Course Resume Functionality
2025-09-17 12:50:22 +05:30
Jannat Patel 372c4d33f0 Merge pull request #1722 from pateljannat/issues-131
fix: misc issues
2025-09-17 12:48:42 +05:30
Jannat Patel 5ba77aae3d fix: moved lesson navigation buttons to navbar to keep them always visible 2025-09-17 12:15:51 +05:30
Jannat Patel 28f4de1b7f fix: check if question exists in local storage before adding to avoid duplicate 2025-09-17 11:45:09 +05:30
Jannat Patel ed162e2546 fix: don't allow unnecessary attributes in profile bio 2025-09-16 11:34:09 +05:30
Jannat Patel e16cecd149 Merge pull request #1718 from frappe/pot_develop_2025-09-12
chore: update POT file
2025-09-15 11:24:43 +05:30
frappe-pr-bot 3a26a5b4d8 chore: update POT file 2025-09-12 16:04:25 +00:00
Jannat Patel 3848af08fe Merge pull request #1711 from frappe/pot_develop_2025-09-05
chore: update POT file
2025-09-08 10:06:02 +05:30
Fahid Latheef Alungal ae79e52486 fix: add a 300 ms debounce on saveProgress from 'cmi.suspend_data' SCORM API to prevent duplicate entries 2025-09-07 19:04:16 +05:30
Fahid Latheef Alungal 1968f5064d refactor: dropped typehints from whitelisted method to avoid unexpected errors 2025-09-07 17:55:54 +05:30
Fahid Latheef Alungal 9d21bcecbe fix: typehint for SCORMDetails 2025-09-07 17:07:07 +05:30
Fahid Latheef Alungal 7a84933b2f Merge remote-tracking branch 'origin/develop' into feat/scorm-progress 2025-09-07 02:00:32 +05:30
frappe-pr-bot 9a10240fcc chore: update POT file 2025-09-05 16:04:16 +00:00
Jannat Patel 99516421ac Merge pull request #1710 from vishwajeet-13/fix/installprompt-responsive-in-mobile
fix(ui): make install prompt message mobile responsive
2025-09-04 13:55:19 +05:30
vishwajeet 5eecffb75e fix(ui): make install prompt message mobile responsive 2025-09-04 13:35:14 +05:30
Jannat Patel 9d2627849a fix: scorm patch query 2025-09-03 15:44:10 +05:30
Frappe PR Bot 22b8222c3d chore(release): Bumped to Version 2.34.1 2025-09-03 05:39:26 +00:00
Jannat Patel b3d1b14abd Merge pull request #1708 from pateljannat/issues-130
fix: misc issues
2025-09-03 10:41:52 +05:30
Jannat Patel f1cd0d3dd4 fix: misc issues 2025-09-02 19:57:32 +05:30
Jannat Patel 3d7815d65f Merge pull request #1369 from FahidLatheef/fix/continue-learning
fix: fixed issue with Lesson Render for SCORM Chapters
2025-09-02 09:48:25 +05:30
Fahid Latheef Alungal a3e7d1f981 refactor: derive is_scorm_package from Lesson instead of Chapter 2025-09-02 02:29:19 +05:30
Fahid Latheef Alungal 2e79190977 fix: added Patch to update wrong indexes for SCORM Lesson References 2025-09-02 01:36:18 +05:30
Fahid Latheef Alungal ee7debeef7 fix: added missing index for first lesson for SCORM 2025-09-02 00:28:26 +05:30
Fahid Latheef Alungal 61f547733c fix: Continue Learning button not working for SCORM Chapters 2025-09-01 23:51:46 +05:30
Jannat Patel 5129e6d6ac Merge pull request #1707 from frappe/pot_develop_2025-08-29
chore: update POT file
2025-09-01 10:00:25 +05:30
Hussain Nagaria 0a9c14f8b1 Merge pull request #1706 from vishwajeet-13/fix/mobile-view-for-live-class 2025-08-31 19:55:06 +05:30
vishwajeet a1960489e1 chore: ran pre-commit 2025-08-30 11:34:31 +05:30
frappe-pr-bot 66fe1a83c6 chore: update POT file 2025-08-29 16:04:45 +00:00
vishwajeet a88d384ac7 fix: mobile responsive live class card 2025-08-29 19:04:33 +05:30
Jannat Patel 2dc6b68974 Merge branch 'develop' of https://github.com/frappe/lms into develop 2025-08-29 17:44:03 +05:30
Jannat Patel 0988ecc515 fix: misc issues on home page 2025-08-29 17:43:44 +05:30
Frappe PR Bot 5ff100da27 chore(release): Bumped to Version 2.34.0 2025-08-29 11:16:37 +00:00
Jannat Patel f218846f4a Merge pull request #1700 from vishwajeet-13/fix/ui-text-correction
fix(ui): correct mislabeled subtitle in statistics
2025-08-28 12:28:14 +05:30
Jannat Patel fc0aba60b9 Merge pull request #1704 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-28 12:26:29 +05:30
Jannat Patel 82e6a62c54 chore: Norwegian Bokmal translations 2025-08-28 09:48:09 +05:30
Jannat Patel fce8950cc5 chore: Danish translations 2025-08-28 09:48:08 +05:30
Jannat Patel 54247e85e0 Merge pull request #1696 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-27 20:39:35 +05:30
vishwajeet cc3f9cd8a4 fix(ui): correct mislabeled subtitle in statistics 2025-08-27 17:43:09 +05:30
Jannat Patel 6dd79dc5d2 chore: Norwegian Bokmal translations 2025-08-27 09:36:52 +05:30
Jannat Patel 6eb53770c3 chore: Danish translations 2025-08-27 09:36:51 +05:30
Jannat Patel 10faeca121 chore: Serbian (Latin) translations 2025-08-27 09:36:49 +05:30
Jannat Patel 38252d9ffc chore: Thai translations 2025-08-27 09:36:46 +05:30
Jannat Patel 86f62aeb99 chore: Portuguese, Brazilian translations 2025-08-27 09:36:43 +05:30
Jannat Patel e3a6bb8781 chore: Turkish translations 2025-08-27 09:36:40 +05:30
Jannat Patel 8ed94ed12f chore: Serbian (Cyrillic) translations 2025-08-27 09:36:37 +05:30
Jannat Patel 95bcfc2ee0 chore: Russian translations 2025-08-27 09:36:36 +05:30
Jannat Patel 313e4811a1 chore: Polish translations 2025-08-27 09:36:33 +05:30
Jannat Patel 5bb9dfeaf8 chore: Spanish translations 2025-08-27 09:36:27 +05:30
Jannat Patel aabb316c7d Merge pull request #1694 from pateljannat/home-page
feat: home page
2025-08-26 12:30:01 +05:30
Jannat Patel ae40e6f41b fix: batch forms labels to add more clarity 2025-08-26 12:17:26 +05:30
Jannat Patel e644d5d20d fix: lesson progress check issue 2025-08-26 11:35:28 +05:30
Jannat Patel ae9abd08ff chore: French translations 2025-08-26 08:58:40 +05:30
Jannat Patel 965128c802 chore: Esperanto translations 2025-08-26 08:58:38 +05:30
Jannat Patel 26324a63df chore: Serbian (Latin) translations 2025-08-26 08:58:37 +05:30
Jannat Patel b9d9754818 chore: Bosnian translations 2025-08-26 08:58:36 +05:30
Jannat Patel 0fb516a86c chore: Croatian translations 2025-08-26 08:58:34 +05:30
Jannat Patel 4b4c7d8927 chore: Thai translations 2025-08-26 08:58:33 +05:30
Jannat Patel e3cae93bd3 chore: Persian translations 2025-08-26 08:58:31 +05:30
Jannat Patel 324f87dc19 chore: Indonesian translations 2025-08-26 08:58:30 +05:30
Jannat Patel 347b5c9411 chore: Portuguese, Brazilian translations 2025-08-26 08:58:29 +05:30
Jannat Patel 7371f8a29d chore: Vietnamese translations 2025-08-26 08:58:27 +05:30
Jannat Patel fb381a30cf chore: Chinese Simplified translations 2025-08-26 08:58:26 +05:30
Jannat Patel 474e3dae65 chore: Turkish translations 2025-08-26 08:58:24 +05:30
Jannat Patel 738ac3f1c9 chore: Swedish translations 2025-08-26 08:58:23 +05:30
Jannat Patel 2fbe69afc5 chore: Serbian (Cyrillic) translations 2025-08-26 08:58:21 +05:30
Jannat Patel 693448cb42 chore: Russian translations 2025-08-26 08:58:20 +05:30
Jannat Patel 17a416d905 chore: Portuguese translations 2025-08-26 08:58:18 +05:30
Jannat Patel 09a6ede925 chore: Polish translations 2025-08-26 08:58:17 +05:30
Jannat Patel 5fc04ae318 chore: Dutch translations 2025-08-26 08:58:15 +05:30
Jannat Patel 900ea0fb5c chore: Italian translations 2025-08-26 08:58:14 +05:30
Jannat Patel 85a6a1d884 chore: Hungarian translations 2025-08-26 08:58:12 +05:30
Jannat Patel 0572009456 chore: German translations 2025-08-26 08:58:11 +05:30
Jannat Patel 9bcd98cb69 chore: Czech translations 2025-08-26 08:58:10 +05:30
Jannat Patel aefce2a842 chore: Arabic translations 2025-08-26 08:58:08 +05:30
Jannat Patel ee4fe99b08 chore: Spanish translations 2025-08-26 08:58:07 +05:30
Jannat Patel 2cb85d47b8 chore: removed unused files 2025-08-25 15:37:00 +05:30
Jannat Patel 0713b6b419 fix: streak logic 2025-08-25 15:33:05 +05:30
Jannat Patel 8f116fddab Merge pull request #1695 from frappe/pot_develop_2025-08-22
chore: update POT file
2025-08-25 10:08:06 +05:30
Jannat Patel 58838cd806 Merge branch 'develop' into pot_develop_2025-08-22 2025-08-25 10:07:59 +05:30
Jannat Patel 7d5918b320 fix: program list heading 2025-08-25 09:57:00 +05:30
Jannat Patel 7eb4ca0fb4 fix: live class end time 2025-08-25 09:22:59 +05:30
Jannat Patel d575bfa0fb feat: streak details 2025-08-25 09:19:03 +05:30
Jannat Patel b60ea3f153 chore: Indonesian translations 2025-08-25 08:59:48 +05:30
Jannat Patel 6e7bc6cfb4 chore: Italian translations 2025-08-25 08:59:39 +05:30
frappe-pr-bot 9b85a0044c chore: update POT file 2025-08-22 16:04:56 +00:00
Jannat Patel b8708382b1 feat: Admin Home 2025-08-22 16:47:25 +05:30
Jannat Patel e0601c7b38 feat: home page 2025-08-21 21:16:21 +05:30
Jannat Patel 0725714144 Merge pull request #1692 from pateljannat/issues-129
fix: misc issues
2025-08-20 18:31:43 +05:30
Jannat Patel ab501e1c6a fix: filter when getting programming exercise title when adding to lesson 2025-08-20 17:18:47 +05:30
Jannat Patel 7cd401327b fix: misc issues 2025-08-20 17:13:23 +05:30
Jannat Patel e886088dff Merge pull request #1686 from pateljannat/program-refactor
refactor: learning path
2025-08-20 14:09:41 +05:30
Jannat Patel ebe7cc32af fix: misc issues 2025-08-20 13:30:31 +05:30
Jannat Patel 5e607c3b8e refactor: sidebar visibility of programs 2025-08-20 13:11:22 +05:30
Jannat Patel 5ec809e3dd feat: program progress summary 2025-08-20 12:06:52 +05:30
Jannat Patel 9d3b6e0556 feat: program self enrollment 2025-08-19 17:33:20 +05:30
Jannat Patel acd003814a refactor: program list for students 2025-08-18 15:51:07 +05:30
Jannat Patel c4991d09f3 Merge pull request #1688 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-18 11:26:35 +05:30
Jannat Patel ca9b2dada3 chore: Serbian (Latin) translations 2025-08-18 08:04:07 +05:30
Jannat Patel 6a3004d75d chore: Serbian (Cyrillic) translations 2025-08-18 08:03:56 +05:30
Jannat Patel 9bd624f31a chore: Swedish translations 2025-08-17 07:45:41 +05:30
Jannat Patel acb264da20 chore: French translations 2025-08-16 07:25:45 +05:30
Jannat Patel c23b0fbfe7 chore: Esperanto translations 2025-08-16 07:25:44 +05:30
Jannat Patel 0e78b5e289 chore: Serbian (Latin) translations 2025-08-16 07:25:42 +05:30
Jannat Patel 91ab895a1d chore: Bosnian translations 2025-08-16 07:25:41 +05:30
Jannat Patel 5c37238114 chore: Croatian translations 2025-08-16 07:25:39 +05:30
Jannat Patel 3cf40ba504 chore: Thai translations 2025-08-16 07:25:38 +05:30
Jannat Patel 9229961f72 chore: Persian translations 2025-08-16 07:25:36 +05:30
Jannat Patel 0b35b11909 chore: Indonesian translations 2025-08-16 07:25:35 +05:30
Jannat Patel 77a27d8716 chore: Portuguese, Brazilian translations 2025-08-16 07:25:34 +05:30
Jannat Patel be32939029 chore: Vietnamese translations 2025-08-16 07:25:32 +05:30
Jannat Patel 4f046425aa chore: Chinese Simplified translations 2025-08-16 07:25:31 +05:30
Jannat Patel 89bf6b1fd9 chore: Turkish translations 2025-08-16 07:25:30 +05:30
Jannat Patel d81414cca6 chore: Swedish translations 2025-08-16 07:25:28 +05:30
Jannat Patel d8eb96a37a chore: Serbian (Cyrillic) translations 2025-08-16 07:25:27 +05:30
Jannat Patel d1b3b0ba34 chore: Russian translations 2025-08-16 07:25:25 +05:30
Jannat Patel dca207347f chore: Portuguese translations 2025-08-16 07:25:24 +05:30
Jannat Patel 5a7ebf8027 chore: Polish translations 2025-08-16 07:25:23 +05:30
Jannat Patel f6d877715c chore: Dutch translations 2025-08-16 07:25:22 +05:30
Jannat Patel 754eb32db4 chore: Italian translations 2025-08-16 07:25:20 +05:30
Jannat Patel 67c04c7f59 chore: Hungarian translations 2025-08-16 07:25:19 +05:30
Jannat Patel daebb26ffa chore: German translations 2025-08-16 07:25:18 +05:30
Jannat Patel 3e2993d048 chore: Czech translations 2025-08-16 07:25:16 +05:30
Jannat Patel 2745cf4015 chore: Arabic translations 2025-08-16 07:25:15 +05:30
Jannat Patel 4e99e2960c chore: Spanish translations 2025-08-16 07:25:13 +05:30
Jannat Patel 649028307d Merge pull request #1687 from frappe/pot_develop_2025-08-15
chore: update POT file
2025-08-15 21:57:49 +05:30
frappe-pr-bot 24787c32dd chore: update POT file 2025-08-15 16:04:47 +00:00
Jannat Patel 625ddac65a refactor: learning path 2025-08-14 20:26:46 +05:30
Jannat Patel 78ff2e6d07 Merge pull request #1684 from pateljannat/issues-128
fix: misc ui issues
2025-08-12 15:14:35 +05:30
Jannat Patel 837426d3c5 Merge pull request #1683 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-12 15:03:47 +05:30
Jannat Patel 65477a5b2b fix: misc ui issues 2025-08-12 15:03:19 +05:30
Jannat Patel a9fa8be5a2 chore: French translations 2025-08-12 06:48:19 +05:30
Jannat Patel 0bb26150d6 chore: Esperanto translations 2025-08-12 06:48:17 +05:30
Jannat Patel fd8f7ade51 chore: Serbian (Latin) translations 2025-08-12 06:48:16 +05:30
Jannat Patel 3310bc2fed chore: Bosnian translations 2025-08-12 06:48:14 +05:30
Jannat Patel 0cbb640054 chore: Croatian translations 2025-08-12 06:48:13 +05:30
Jannat Patel b4aa88ac33 chore: Thai translations 2025-08-12 06:48:11 +05:30
Jannat Patel f2906e57f0 chore: Persian translations 2025-08-12 06:48:10 +05:30
Jannat Patel 8d381b167e chore: Indonesian translations 2025-08-12 06:48:08 +05:30
Jannat Patel d78e316349 chore: Portuguese, Brazilian translations 2025-08-12 06:48:07 +05:30
Jannat Patel 82d00c6a4a chore: Vietnamese translations 2025-08-12 06:48:05 +05:30
Jannat Patel ba4fd7f1ef chore: Chinese Simplified translations 2025-08-12 06:48:04 +05:30
Jannat Patel 591d265167 chore: Turkish translations 2025-08-12 06:48:02 +05:30
Jannat Patel af9d973a91 chore: Swedish translations 2025-08-12 06:48:01 +05:30
Jannat Patel 15096a3118 chore: Serbian (Cyrillic) translations 2025-08-12 06:47:59 +05:30
Jannat Patel 7c78a9697c chore: Russian translations 2025-08-12 06:47:58 +05:30
Jannat Patel 3f5051f697 chore: Portuguese translations 2025-08-12 06:47:56 +05:30
Jannat Patel 044a66f2e8 chore: Polish translations 2025-08-12 06:47:55 +05:30
Jannat Patel 6fb8775bb7 chore: Dutch translations 2025-08-12 06:47:53 +05:30
Jannat Patel f16d0300ba chore: Italian translations 2025-08-12 06:47:52 +05:30
Jannat Patel 84e12f1eea chore: Hungarian translations 2025-08-12 06:47:50 +05:30
Jannat Patel ba40b7ff15 chore: German translations 2025-08-12 06:47:49 +05:30
Jannat Patel e733326056 chore: Czech translations 2025-08-12 06:47:47 +05:30
Jannat Patel b3d987e933 chore: Arabic translations 2025-08-12 06:47:46 +05:30
Jannat Patel e919874e9e chore: Spanish translations 2025-08-12 06:47:44 +05:30
Jannat Patel a2f51f151a Merge pull request #1681 from pateljannat/pre-commit
chore: formatted files with ruff
2025-08-11 21:30:17 +05:30
Jannat Patel ea288abb27 test: fixed batch overlay test 2025-08-11 20:06:43 +05:30
Jannat Patel b274900ae0 chore: removed profile page renderers 2025-08-11 19:34:22 +05:30
Jannat Patel 10d29d3a3f chore: formatted js with eslint 2025-08-11 17:56:51 +05:30
Jannat Patel a92e04343a chore: formatted files with ruff 2025-08-11 17:48:37 +05:30
Jannat Patel be31c18bfe Merge pull request #1680 from pateljannat/pwa
feat: PWA
2025-08-11 17:26:42 +05:30
Jannat Patel 9d0a5fd8f3 chore: updated pre-commit config 2025-08-11 17:06:35 +05:30
Jannat Patel 5ca577bc0a fix: responsive layout for pages 2025-08-11 17:02:46 +05:30
Jannat Patel 4d25d185c3 feat: PWA 2025-08-11 15:43:34 +05:30
Jannat Patel ea289e02da Merge pull request #1679 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-11 11:22:56 +05:30
Jannat Patel a3f16eb7e8 Merge pull request #1678 from frappe/pot_develop_2025-08-08
chore: update POT file
2025-08-11 11:22:41 +05:30
Jannat Patel ddd29abe99 chore: Italian translations 2025-08-11 06:48:11 +05:30
Jannat Patel decd56f2d2 chore: Italian translations 2025-08-10 06:47:20 +05:30
frappe-pr-bot 387401cb44 chore: update POT file 2025-08-08 16:04:31 +00:00
Jannat Patel 57c1a6b540 Merge pull request #1676 from pateljannat/issues-127
fix: misc issues
2025-08-08 12:08:04 +05:30
Jannat Patel 8dba0e8242 Merge pull request #1677 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-08 12:06:59 +05:30
Jannat Patel ee715f6387 chore: fixed linters 2025-08-08 10:47:21 +05:30
Jannat Patel b770b30334 chore: Italian translations 2025-08-08 06:54:22 +05:30
Jannat Patel d61abac126 fix: validate is uploaded svg is malicious 2025-08-07 17:33:32 +05:30
Jannat Patel ccf28b8012 refactor: bring course title down from the gradient in course cards 2025-08-07 17:23:14 +05:30
Jannat Patel 3762cb06bb Merge pull request #1671 from pateljannat/notes
feat: notes and highlights in lesson
2025-08-07 17:11:46 +05:30
Jannat Patel 15400f2a3e test: open community tab before testing discussions 2025-08-07 17:01:55 +05:30
Jannat Patel 20d1b1fe83 Merge pull request #1675 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-07 16:05:03 +05:30
Jannat Patel 73844f8813 fix: minor changes with visibility and change 2025-08-07 16:04:36 +05:30
Jannat Patel 2187553625 chore: Serbian (Latin) translations 2025-08-07 06:36:14 +05:30
Jannat Patel 984b2a5dea chore: Serbian (Cyrillic) translations 2025-08-07 06:36:13 +05:30
Jannat Patel 9098d9454f Merge pull request #1674 from pateljannat/issues-126
fix: video statistics scroll and time format
2025-08-06 11:28:15 +05:30
Jannat Patel 027dd93fb5 fix: reload notes when moving to another lesson 2025-08-06 11:13:16 +05:30
Jannat Patel a005adc89a Merge pull request #1672 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-06 10:32:09 +05:30
Jannat Patel 866ef04fbf chore: Esperanto translations 2025-08-06 05:34:46 +05:30
Jannat Patel 00b6f97e3a chore: Serbian (Latin) translations 2025-08-06 05:34:45 +05:30
Jannat Patel a1d21b1a2a chore: Bosnian translations 2025-08-06 05:34:43 +05:30
Jannat Patel 7358ea43d8 chore: Croatian translations 2025-08-06 05:34:42 +05:30
Jannat Patel 88c69311eb chore: Thai translations 2025-08-06 05:34:40 +05:30
Jannat Patel c1e45e5d0d chore: Persian translations 2025-08-06 05:34:39 +05:30
Jannat Patel fe78de2417 chore: Indonesian translations 2025-08-06 05:34:37 +05:30
Jannat Patel 4c1fc201e6 chore: Portuguese, Brazilian translations 2025-08-06 05:34:36 +05:30
Jannat Patel 3f5d270915 chore: Vietnamese translations 2025-08-06 05:34:34 +05:30
Jannat Patel a452fbeb07 chore: Chinese Simplified translations 2025-08-06 05:34:33 +05:30
Jannat Patel a6f02c245f chore: Turkish translations 2025-08-06 05:34:31 +05:30
Jannat Patel cb4f9129d6 chore: Swedish translations 2025-08-06 05:34:30 +05:30
Jannat Patel 9c5d64c211 chore: Serbian (Cyrillic) translations 2025-08-06 05:34:28 +05:30
Jannat Patel 41dc0ecc60 chore: Russian translations 2025-08-06 05:34:26 +05:30
Jannat Patel 6b9409b889 chore: Portuguese translations 2025-08-06 05:34:25 +05:30
Jannat Patel ea66eeed6c chore: Polish translations 2025-08-06 05:34:24 +05:30
Jannat Patel a419d28ef1 chore: Dutch translations 2025-08-06 05:34:22 +05:30
Jannat Patel 481dfc24fd chore: Italian translations 2025-08-06 05:34:20 +05:30
Jannat Patel ed686a7d52 chore: Hungarian translations 2025-08-06 05:34:19 +05:30
Jannat Patel b4c5a07800 chore: German translations 2025-08-06 05:34:17 +05:30
Jannat Patel 6ae16f7fef chore: Czech translations 2025-08-06 05:34:16 +05:30
Jannat Patel 4aae2ed3b8 chore: Arabic translations 2025-08-06 05:34:14 +05:30
Jannat Patel 81d4137b20 chore: Spanish translations 2025-08-06 05:34:13 +05:30
Jannat Patel 77ecb02a17 feat: notes in lesson 2025-08-05 20:00:09 +05:30
Jannat Patel 4a375f92ed Merge pull request #1668 from frappe/pot_develop_2025-08-01
chore: update POT file
2025-08-04 20:05:22 +05:30
Jannat Patel 7caf91460a Merge pull request #1669 from frappe/l10n_develop2
chore: sync translations from crowdin
2025-08-04 20:05:12 +05:30
Jannat Patel 0e015c8b97 chore: Indonesian translations 2025-08-03 04:35:30 +05:30
frappe-pr-bot 7b69ddb14d chore: update POT file 2025-08-01 16:04:44 +00:00
Jannat Patel 2271eb270e Merge pull request #1667 from harshpwctech/develop
refactor: Announcement mail being sent to students in BCC
2025-08-01 17:32:41 +05:30
CA Harsh Agrawal 7e5b2e4e79 refactor: Announcement mail being sent to students in BCC 2025-08-01 17:02:48 +05:30
Jannat Patel 124b9d9ea5 fix: video statistics scroll 2025-07-30 17:31:33 +05:30
Jannat Patel 36076068ec fix: text padding on card gradient 2025-07-30 12:30:12 +05:30
Fahid Latheef A 39cc83c1b8 Merge branch 'frappe:develop' into feat/scorm-progress 2025-03-02 14:17:26 +05:30
Fahid Latheef A 5dd1bb949c Merge branch 'develop' into feat/scorm-progress 2025-02-17 23:07:10 +05:30
Fahid Latheef Alungal b76486e4dc fix: changed filters in calculation of completed LMS Course Progress count 2025-02-16 21:08:58 +05:30
FahidLatheef 2ce2df6390 style: change height of SCORM iframe so that user don't have to scroll 2025-01-22 15:25:39 +05:30
FahidLatheef 19e5136d64 feat: added logic to handle failure cases on SCORM courses failure (Whether to allow retake of final quiz or Reset the whole Course) 2025-01-22 15:25:39 +05:30
FahidLatheef 281e155480 feat: set cmi.launch_data from scorm_content for resumability
- Replaced already_completed with progress_already_exists variable
- Added scorm_details parameter to save_progress whitelisted function
- Added Separate logic for SCORM chapter progress in save_progress
2025-01-22 15:25:39 +05:30
FahidLatheef 70b2a11cb7 feat: added is_scorm_chapter and scorm_details fields used for tracking SCORM Data Model
feat: added is_scorm_chapter and scorm_content to track cmi.suspend_data for resuming scorm chapter
2025-01-22 15:25:39 +05:30
326 changed files with 73651 additions and 31703 deletions
+124
View File
@@ -0,0 +1,124 @@
{
"env": {
"browser": true,
"node": true,
"es2022": true
},
"parserOptions": {
"sourceType": "module"
},
"extends": "eslint:recommended",
"rules": {
"indent": "off",
"brace-style": "off",
"no-mixed-spaces-and-tabs": "off",
"no-useless-escape": "off",
"space-unary-ops": ["error", { "words": true }],
"linebreak-style": "off",
"quotes": ["off"],
"semi": "off",
"camelcase": "off",
"no-unused-vars": "off",
"no-console": ["warn"],
"no-extra-boolean-cast": ["off"],
"no-control-regex": ["off"],
},
"root": true,
"globals": {
"frappe": true,
"Vue": true,
"SetVueGlobals": true,
"__": true,
"repl": true,
"Class": true,
"locals": true,
"cint": true,
"cstr": true,
"cur_frm": true,
"cur_dialog": true,
"cur_page": true,
"cur_list": true,
"cur_tree": true,
"msg_dialog": true,
"is_null": true,
"in_list": true,
"has_common": true,
"posthog": true,
"has_words": true,
"validate_email": true,
"open_web_template_values_editor": true,
"validate_name": true,
"validate_phone": true,
"validate_url": true,
"get_number_format": true,
"format_number": true,
"format_currency": true,
"comment_when": true,
"open_url_post": true,
"toTitle": true,
"lstrip": true,
"rstrip": true,
"strip": true,
"strip_html": true,
"replace_all": true,
"flt": true,
"precision": true,
"CREATE": true,
"AMEND": true,
"CANCEL": true,
"copy_dict": true,
"get_number_format_info": true,
"strip_number_groups": true,
"print_table": true,
"Layout": true,
"web_form_settings": true,
"$c": true,
"$a": true,
"$i": true,
"$bg": true,
"$y": true,
"$c_obj": true,
"refresh_many": true,
"refresh_field": true,
"toggle_field": true,
"get_field_obj": true,
"get_query_params": true,
"unhide_field": true,
"hide_field": true,
"set_field_options": true,
"getCookie": true,
"getCookies": true,
"get_url_arg": true,
"md5": true,
"$": true,
"jQuery": true,
"moment": true,
"hljs": true,
"Awesomplete": true,
"Sortable": true,
"Showdown": true,
"Taggle": true,
"Gantt": true,
"Slick": true,
"Webcam": true,
"PhotoSwipe": true,
"PhotoSwipeUI_Default": true,
"io": true,
"JsBarcode": true,
"L": true,
"Chart": true,
"DataTable": true,
"Cypress": true,
"cy": true,
"it": true,
"describe": true,
"expect": true,
"context": true,
"before": true,
"beforeEach": true,
"after": true,
"qz": true,
"localforage": true,
"extend_cscript": true
}
}
+26 -17
View File
@@ -1,10 +1,10 @@
exclude: 'node_modules|.git'
default_stages: [commit]
default_stages: [pre-commit]
fail_fast: false
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v5.0.0
hooks:
- id: trailing-whitespace
files: "lms.*"
@@ -16,17 +16,16 @@ repos:
- id: check-toml
- id: debug-statements
- repo: https://github.com/asottile/pyupgrade
rev: v2.34.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.1
hooks:
- id: pyupgrade
args: ['--py310-plus']
- repo: https://github.com/adityahase/black
rev: 9cb0a69f4d0030cdf687eddf314468b39ed54119
hooks:
- id: black
additional_dependencies: ['click==8.0.4']
- id: ruff
name: "Run ruff import sorter"
args: ["--select=I", "--fix"]
- id: ruff
name: "Run ruff linter"
- id: ruff-format
name: "Run ruff formatter"
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.7.1
@@ -44,12 +43,22 @@ repos:
lms/public/js/lib/.*
)$
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
- repo: https://github.com/pre-commit/mirrors-eslint
rev: v8.44.0
hooks:
- id: flake8
additional_dependencies: ['flake8-bugbear',]
args: ['--config', '.github/helper/flake8.conf']
- id: eslint
types_or: [javascript]
args: ['--quiet']
exclude: |
(?x)^(
lms/public/dist/.*|
cypress/.*|
.*node_modules.*|
.*boilerplate.*|
lms/www/website_script.js|
lms/templates/includes/.*|
lms/public/js/lib/.*
)$
ci:
autoupdate_schedule: weekly
-178
View File
@@ -1,178 +0,0 @@
<div align="center" markdown="1">
<img src=".github/lms-logo.png" alt="Frappe Learning logo" width="80" height="80"/>
<h1>Frappe Learning</h1>
**Easy to use, open source, Learning Management System**
![Tests](https://img.shields.io/endpoint?url=https://dashboard.cypress.io/badge/simple/vandxn/main&style=flat&logo=cypress)
</div>
<div align="center">
<img src=".github/hero.png?v=5" alt="Hero Image" width="72%" />
</div>
<br />
<div align="center">
<a href="https://frappe.io/learning">Website</a>
-
<a href="https://docs.frappe.io/learning">Documentation</a>
</div>
## Frappe Learning
Frappe Learning is an easy-to-use learning system that helps you bring structure to your content.
### Motivation
In 2021, we were looking for a Learning Management System to launch [Mon.School](https://mon.school) for FOSS United. We checked out Moodle, but it didnt feel right. The forms were unnecessarily lengthy and the UI was confusing. It shouldn't be this hard to create a course right? So I started making a learning system for Mon.School which soon became a product in itself. The aim is to have a simple platform that anyone can use to launch a course of their own and make knowledge sharing easier.
### Key Features
- **Structured Learning**: Design a course with a 3-level hierarchy, where your courses have chapters and you can group your lessons within these chapters. This ensures that the context of the lesson is set by the chapter.
- **Live Classes**: Group learners into batches based on courses and duration. You can then create Zoom live class for these batches right from the app. Learners get to see the list of live classes they have to take as a part of this batch.
- **Quizzes and Assignments**: Create quizzes where questions can have single-choice, multiple-choice options, or can be open ended. Instructors can also add assignments which learners can submit as PDF's or Documents.
- **Getting Certified**: Once a learner has completed the course or batch, you can grant them a certificate. The app provides an inbuilt certificate template. You can use this or else create a template of your own and use that instead.
<details>
<summary>View Screenshots</summary>
![Batch](.github/batch.png)
<div align="center">
<sub>
Create batches to group your learners
</sub>
</div>
<br>
![Quiz](.github/quiz.png)
<div align="center">
<sub>
Evaluate their knowledge by quizzes
</sub>
</div>
<br>
![Cerficicate](.github/certificate.png)
<div align="center">
<sub>
Autenticate their work with certification
</sub>
</div>
</details>
### Under the Hood
- [**Frappe Framework**](https://github.com/frappe/frappe): A full-stack web application framework.
- [**Frappe UI**](https://github.com/frappe/frappe-ui): A Vue-based UI library, to provide a modern user interface.
## Production Setup
### Managed Hosting
You can try [Frappe Cloud](https://frappecloud.com), a simple, user-friendly and sophisticated [open-source](https://github.com/frappe/press) platform to host Frappe applications with peace of mind.
It takes care of installation, setup, upgrades, monitoring, maintenance and support of your Frappe deployments. It is a fully featured developer platform with an ability to manage and control multiple Frappe deployments.
<div>
<a href="https://frappecloud.com/lms/signup" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/try-on-fc-white.png">
<img src="https://frappe.io/files/try-on-fc-black.png" alt="Try on Frappe Cloud" height="28" />
</picture>
</a>
</div>
### Self Hosting
Follow these steps to set up Frappe Learning in production:
**Step 1**: Download the easy install script
```bash
wget https://frappe.io/easy-install.py
```
**Step 2**: Run the deployment command
```bash
python3 ./easy-install.py deploy \
--project=learning_prod_setup \
--email=your_email.example.com \
--image=ghcr.io/frappe/lms \
--version=stable \
--app=lms \
--sitename subdomain.domain.tld
```
Replace the following parameters with your values:
- `your_email.example.com`: Your email address
- `subdomain.domain.tld`: Your domain name where Learning will be hosted
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
You need Docker, docker-compose and git setup on your machine. Refer [Docker documentation](https://docs.docker.com/). After that, follow below steps:
**Step 1**: Setup folder and download the required files
mkdir frappe-learning
cd frappe-learning
# Download the docker-compose file
wget -O docker-compose.yml https://raw.githubusercontent.com/frappe/lms/develop/docker/docker-compose.yml
# Download the setup script
wget -O init.sh https://raw.githubusercontent.com/frappe/lms/develop/docker/init.sh
**Step 2**: Run the container and daemonize it
docker compose up -d
**Step 3**: The site [http://lms.localhost:8000/lms](http://lms.localhost:8000/lms) should now be available. The default credentials are:
- Username: Administrator
- Password: admin
### Local
To setup the repository locally follow the steps mentioned below:
1. Install bench and setup a `frappe-bench` directory by following the [Installation Steps](https://frappeframework.com/docs/user/en/installation)
1. Start the server by running `bench start`
1. In a separate terminal window, create a new site by running `bench new-site learning.test`
1. Map your site to localhost with the command `bench --site learning.test add-to-hosts`
1. Get the Learning app. Run `bench get-app https://github.com/frappe/lms`
1. Run `bench --site learning.test install-app lms`.
1. Now open the URL `http://learning.test:8000/lms` in your browser, you should see the app running
## Learn and connect
- [Telegram Public Group](https://t.me/frappelms)
- [Discuss Forum](https://discuss.frappe.io/c/lms/70)
- [Documentation](https://docs.frappe.io/learning)
- [YouTube](https://www.youtube.com/channel/UCn3bV5kx77HsVwtnlCeEi_A)
<br>
<br>
<div align="center" style="padding-top: 0.75rem;">
<a href="https://frappe.io" target="_blank">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://frappe.io/files/Frappe-white.png">
<img src="https://frappe.io/files/Frappe-black.png" alt="Frappe Technologies" height="28"/>
</picture>
</a>
</div>
+12 -24
View File
@@ -10,11 +10,11 @@ describe("Batch Creation", () => {
cy.get("span").contains("Settings").click();
// Add a new member
cy.get('[id^="headlessui-dialog-panel-v-"]')
cy.get("[data-dismissable-layer]")
.find("span")
.contains(/^Members$/)
.click();
cy.get('[id^="headlessui-dialog-panel-v-"]')
cy.get("[data-dismissable-layer]")
.find("button")
.contains("New")
.click();
@@ -28,12 +28,12 @@ describe("Batch Creation", () => {
cy.get("button").contains("Add").click();
// Add evaluator
cy.get('[id^="headlessui-dialog-panel-v-"]')
cy.get("[data-dismissable-layer]")
.find("span")
.contains(/^Evaluators$/)
.click();
cy.get('[id^="headlessui-dialog-panel-v-"]')
cy.get("[data-dismissable-layer]")
.find("button")
.contains("New")
.click();
@@ -138,43 +138,31 @@ describe("Batch Creation", () => {
.contains("Test Batch Short Description to test the UI")
.should("be.visible");
cy.get("a").contains("Evaluator").should("be.visible");
cy.get("span")
cy.get("span:visible")
.contains("01 Oct 2030 - 31 Oct 2030")
.should("be.visible");
cy.get("span").contains("10:00 AM - 11:00 AM").should("be.visible");
cy.get("span").contains("IST").should("be.visible");
cy.get("div")
.contains("10")
.should("be.visible")
.get("span")
.contains("Seats Left")
cy.get("span:visible")
.contains("10:00 AM - 11:00 AM")
.should("be.visible");
cy.get("span:visible").contains("IST").should("be.visible");
cy.contains("div:visible", "10 Seats Left").should("be.visible");
cy.get("p")
.contains(
"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").contains("Manage Batch").click();
cy.get("button:visible").contains("Manage Batch").click();
/* Add student to batch */
cy.get("button").contains("Add").click();
cy.get('div[id^="headlessui-dialog-panel-v-"]')
.first()
.find("button")
.eq(1)
.click();
cy.get('div[role="dialog"]').first().find("button").eq(1).click();
cy.get("input[id^='headlessui-combobox-input-v-']").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("div")
.contains("9")
.should("be.visible")
.get("span")
.contains("Seats Left")
.should("be.visible");
cy.contains("div:visible", "9 Seats Left").should("be.visible");
});
});
+4 -3
View File
@@ -76,7 +76,7 @@ describe("Course Creation", () => {
cy.button("Add Chapter").click();
cy.wait(1000);
cy.get("[id^=headlessui-dialog-panel-")
cy.get("[data-dismissable-layer]")
.should("be.visible")
.within(() => {
cy.get("label").contains("Title").type("Test Chapter");
@@ -98,7 +98,7 @@ describe("Course Creation", () => {
// View Course
cy.wait(1000);
cy.visit("/lms");
cy.visit("/lms/courses");
cy.closeOnboardingModal();
cy.url().should("include", "/lms/courses");
@@ -140,9 +140,10 @@ describe("Course Creation", () => {
);
// Add Discussion
cy.get("span").contains("Community").click();
cy.button("New Question").click();
cy.wait(500);
cy.get("[id^=headlessui-dialog-panel-").within(() => {
cy.get("[data-dismissable-layer]").within(() => {
cy.get("label").contains("Title").type("Test Discussion");
cy.get("div[contenteditable=true]").invoke(
"text",
+1
View File
@@ -2,4 +2,5 @@ node_modules
.DS_Store
dist
dist-ssr
dev-dist
*.local
+10
View File
@@ -0,0 +1,10 @@
/* eslint-disable */
/* prettier-ignore */
// @ts-nocheck
// noinspection JSUnusedGlobalSymbols
// Generated by unplugin-auto-import
// biome-ignore lint: disable
export {}
declare global {
}
+10 -1
View File
@@ -10,6 +10,7 @@ declare module 'vue' {
export interface GlobalComponents {
Annoucements: typeof import('./src/components/Annoucements.vue')['default']
AnnouncementModal: typeof import('./src/components/Modals/AnnouncementModal.vue')['default']
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
Apps: typeof import('./src/components/Apps.vue')['default']
AppSidebar: typeof import('./src/components/AppSidebar.vue')['default']
AssessmentModal: typeof import('./src/components/Modals/AssessmentModal.vue')['default']
@@ -41,6 +42,7 @@ 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']
ContactUsEmail: typeof import('./src/components/ContactUsEmail.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']
@@ -66,9 +68,12 @@ declare module 'vue' {
FrappeCloudIcon: typeof import('./src/components/Icons/FrappeCloudIcon.vue')['default']
IconPicker: typeof import('./src/components/Controls/IconPicker.vue')['default']
IndicatorIcon: typeof import('./src/components/Icons/IndicatorIcon.vue')['default']
InlineLessonMenu: typeof import('./src/components/Notes/InlineLessonMenu.vue')['default']
InstallPrompt: typeof import('./src/components/InstallPrompt.vue')['default']
InviteIcon: typeof import('./src/components/Icons/InviteIcon.vue')['default']
JobApplicationModal: typeof import('./src/components/Modals/JobApplicationModal.vue')['default']
JobCard: typeof import('./src/components/JobCard.vue')['default']
LayoutHeader: typeof import('./src/components/LayoutHeader.vue')['default']
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']
@@ -81,9 +86,11 @@ declare module 'vue' {
MultiSelect: typeof import('./src/components/Controls/MultiSelect.vue')['default']
NoPermission: typeof import('./src/components/NoPermission.vue')['default']
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']
PageModal: typeof import('./src/components/Modals/PageModal.vue')['default']
PaymentSettings: typeof import('./src/components/Settings/PaymentSettings.vue')['default']
PaymentGatewayDetails: typeof import('./src/components/Settings/PaymentGatewayDetails.vue')['default']
PaymentGateways: typeof import('./src/components/Settings/PaymentGateways.vue')['default']
Play: typeof import('./src/components/Icons/Play.vue')['default']
ProgressBar: typeof import('./src/components/ProgressBar.vue')['default']
Question: typeof import('./src/components/Modals/Question.vue')['default']
@@ -102,6 +109,8 @@ declare module 'vue' {
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']
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']
+198 -1
View File
@@ -3,6 +3,203 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" href="{{ favicon }}" />
<link rel="manifest" href="/api/method/lms.lms.api.get_pwa_manifest" />
<link rel="apple-touch-icon" href="public/manifest/apple-icon-180.png" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0F0F0F" media="(prefers-color-scheme: dark)" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<meta name="msapplication-navbutton-color" content="#ffffff" />
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2048-2732.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2732-2048.jpg"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1668-2388.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2388-1668.jpg"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1536-2048.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2048-1536.jpg"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1640-2360.jpg"
media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2360-1640.jpg"
media="(device-width: 820px) and (device-height: 1180px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1668-2224.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2224-1668.jpg"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1620-2160.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2160-1620.jpg"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1488-2266.jpg"
media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2266-1488.jpg"
media="(device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1320-2868.jpg"
media="(device-width: 440px) and (device-height: 956px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2868-1320.jpg"
media="(device-width: 440px) and (device-height: 956px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1206-2622.jpg"
media="(device-width: 402px) and (device-height: 874px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2622-1206.jpg"
media="(device-width: 402px) and (device-height: 874px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1290-2796.jpg"
media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2796-1290.jpg"
media="(device-width: 430px) and (device-height: 932px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1179-2556.jpg"
media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2556-1179.jpg"
media="(device-width: 393px) and (device-height: 852px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1170-2532.jpg"
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2532-1170.jpg"
media="(device-width: 390px) and (device-height: 844px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1284-2778.jpg"
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2778-1284.jpg"
media="(device-width: 428px) and (device-height: 926px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1125-2436.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2436-1125.jpg"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1242-2688.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2688-1242.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-828-1792.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1792-828.jpg"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1242-2208.jpg"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-2208-1242.jpg"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-750-1334.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1334-750.jpg"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-640-1136.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
/>
<link
rel="apple-touch-startup-image"
href="public/manifest/apple-splash-1136-640.jpg"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{ title }}</title>
<meta name="title" content="{{ meta.title }}" />
@@ -16,7 +213,7 @@
<meta name="twitter:image" content="{{ meta.image }}" />
<meta name="twitter:description" content="{{ meta.description }}" />
</head>
<body>
<body class="sm:overscroll-y-none no-scrollbar">
<div id="app">
<div id="seo-content">
<h1>{{ meta.title }}</h1>
+4 -2
View File
@@ -30,8 +30,9 @@
"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.172",
"frappe-ui": "^0.1.201",
"highlight.js": "^11.11.1",
"lucide-vue-next": "^0.383.0",
"markdown-it": "^14.0.0",
@@ -53,6 +54,7 @@
"@vitejs/plugin-vue": "^5.0.3",
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"vite": "^5.0.11"
"vite": "^5.0.11",
"vite-plugin-pwa": "^1.0.2"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 512.001 512.001" xml:space="preserve">
<g>
<path style="fill:#A67C52;" d="M84.096,436.178l-49.312,37.686c-7.54,5.762-8.981,16.547-3.219,24.087
c3.383,4.425,8.494,6.751,13.666,6.751c3.638,0,7.306-1.151,10.421-3.532l49.312-37.686c7.54-5.762,8.981-16.547,3.219-24.087
C102.421,431.858,91.637,430.416,84.096,436.178z"/>
<path style="fill:#A67C52;" d="M441.194,473.864l-49.312-37.686c-7.541-5.762-18.325-4.32-24.087,3.219
c-5.762,7.541-4.321,18.325,3.219,24.087l49.312,37.686c3.115,2.38,6.782,3.532,10.421,3.532c5.171,0,10.284-2.326,13.665-6.751
C450.175,490.411,448.734,479.627,441.194,473.864z"/>
</g>
<path style="fill:#DBAD75;" d="M237.989,36.024c-131.227,0-237.989,106.761-237.989,237.989s106.761,237.989,237.989,237.989
S475.978,405.24,475.978,274.012S369.216,36.024,237.989,36.024z"/>
<path style="fill:#EABD81;" d="M237.989,36.024c-131.227,0-237.989,106.761-237.989,237.989s106.761,237.989,237.989,237.989V36.024
z"/>
<path style="fill:#BC2A46;" d="M237.989,80.411c-106.752,0-193.601,86.849-193.601,193.601s86.849,193.601,193.601,193.601
s193.601-86.849,193.601-193.601S344.742,80.411,237.989,80.411z"/>
<path style="fill:#D62D46;" d="M237.989,80.411c-106.752,0-193.601,86.849-193.601,193.601s86.849,193.601,193.601,193.601V80.411z"
/>
<path style="fill:#DBAD75;" d="M237.989,142.771c-72.367,0-131.241,58.874-131.241,131.241s58.874,131.241,131.241,131.241
S369.23,346.379,369.23,274.012S310.355,142.771,237.989,142.771z"/>
<path style="fill:#EABD81;" d="M237.989,142.771c-72.367,0-131.241,58.874-131.241,131.241s58.874,131.241,131.241,131.241V142.771z
"/>
<path style="fill:#BC2A46;" d="M237.989,209.763c-35.427,0-64.248,28.821-64.248,64.248s28.821,64.248,64.248,64.248
s64.248-28.821,64.248-64.248S273.416,209.763,237.989,209.763z"/>
<path style="fill:#D62D46;" d="M237.989,209.763c-35.427,0-64.248,28.821-64.248,64.248s28.821,64.248,64.248,64.248V209.763z"/>
<path style="fill:#CFCDD6;" d="M237.989,291.196c-4.398,0-8.796-1.677-12.15-5.034c-6.711-6.711-6.711-17.59,0-24.301
L448.687,39.014c6.71-6.711,17.59-6.711,24.301,0s6.711,17.59,0,24.301L250.14,286.162
C246.784,289.519,242.386,291.196,237.989,291.196z"/>
<path style="fill:#DEE1E7;" d="M237.989,291.196c-4.398,0-8.796-1.677-12.15-5.034c-6.711-6.711-6.711-17.59,0-24.301
l106.576-106.576l24.301,24.301L250.14,286.162C246.784,289.519,242.386,291.196,237.989,291.196z"/>
<path style="fill:#39B7B6;" d="M457.533,105.266h-33.615c-9.49,0-17.184-7.694-17.184-17.184V54.467
c0-9.49,7.694-17.184,17.184-17.184s17.184,7.694,17.184,17.184v16.432h16.431c9.49,0,17.184,7.694,17.184,17.184
S467.023,105.266,457.533,105.266z"/>
<path style="fill:#FBB03B;" d="M476.175,86.623h-33.614c-9.49,0-17.184-7.694-17.184-17.184V35.825
c0-9.49,7.694-17.184,17.184-17.184s17.184,7.694,17.184,17.184v16.431h16.431c9.49,0,17.184,7.694,17.184,17.184
S485.665,86.623,476.175,86.623z"/>
<path style="fill:#39B7B6;" d="M494.817,67.982h-33.614c-9.49,0-17.184-7.694-17.184-17.184V17.184
c0-9.49,7.694-17.184,17.184-17.184s17.184,7.694,17.184,17.184v16.431h16.431c9.49,0,17.184,7.694,17.184,17.184
S504.308,67.982,494.817,67.982z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--noto" preserveAspectRatio="xMidYMid meet"><path d="M68.05 7.23l13.46 30.7a7.047 7.047 0 0 0 5.82 4.19l32.79 2.94c3.71.54 5.19 5.09 2.5 7.71l-24.7 20.75c-2 1.68-2.91 4.32-2.36 6.87l7.18 33.61c.63 3.69-3.24 6.51-6.56 4.76L67.56 102a7.033 7.033 0 0 0-7.12 0l-28.62 16.75c-3.31 1.74-7.19-1.07-6.56-4.76l7.18-33.61c.54-2.55-.36-5.19-2.36-6.87L5.37 52.78c-2.68-2.61-1.2-7.17 2.5-7.71l32.79-2.94a7.047 7.047 0 0 0 5.82-4.19l13.46-30.7c1.67-3.36 6.45-3.36 8.11-.01z" fill="#fdd835"></path><path d="M67.07 39.77l-2.28-22.62c-.09-1.26-.35-3.42 1.67-3.42c1.6 0 2.47 3.33 2.47 3.33l6.84 18.16c2.58 6.91 1.52 9.28-.97 10.68c-2.86 1.6-7.08.35-7.73-6.13z" fill="#ffff8d"></path><path d="M95.28 71.51L114.9 56.2c.97-.81 2.72-2.1 1.32-3.57c-1.11-1.16-4.11.51-4.11.51l-17.17 6.71c-5.12 1.77-8.52 4.39-8.82 7.69c-.39 4.4 3.56 7.79 9.16 3.97z" fill="#f4b400"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+9 -10
View File
@@ -1,10 +1,9 @@
<template>
<FrappeUIProvider>
<Layout>
<div class="text-base">
<router-view />
</div>
<Layout class="isolate text-base">
<router-view />
</Layout>
<!--<InstallPrompt v-if="isMobile" />-->
<Dialogs />
</FrappeUIProvider>
</template>
@@ -13,14 +12,15 @@ import { FrappeUIProvider } from 'frappe-ui'
import { Dialogs } from '@/utils/dialogs'
import { computed, onUnmounted, ref, watch } from 'vue'
import { useScreenSize } from './utils/composables'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
import { usersStore } from '@/stores/user'
import { useRouter } from 'vue-router'
import { posthogSettings } from '@/telemetry'
import DesktopLayout from './components/DesktopLayout.vue'
import MobileLayout from './components/MobileLayout.vue'
import NoSidebarLayout from './components/NoSidebarLayout.vue'
import InstallPrompt from './components/InstallPrompt.vue'
const screenSize = useScreenSize()
const { isMobile } = useScreenSize()
const router = useRouter()
const noSidebar = ref(false)
const { userResource } = usersStore()
@@ -38,10 +38,9 @@ const Layout = computed(() => {
if (noSidebar.value) {
return NoSidebarLayout
}
if (screenSize.width < 640) {
if (isMobile.value) {
return MobileLayout
}
return DesktopLayout
})
+166 -39
View File
@@ -9,12 +9,12 @@
>
<UserDropdown :isCollapsed="sidebarStore.isSidebarCollapsed" />
<div class="flex flex-col" v-if="sidebarSettings.data">
<SidebarLink
v-for="link in sidebarLinks"
:link="link"
:isCollapsed="sidebarStore.isSidebarCollapsed"
class="mx-2 my-0.5"
/>
<div v-for="link in sidebarLinks" class="mx-2 my-0.5">
<SidebarLink
:link="link"
:isCollapsed="sidebarStore.isSidebarCollapsed"
/>
</div>
</div>
<div
v-if="sidebarSettings.data?.web_pages?.length || isModerator"
@@ -54,15 +54,18 @@
class="flex flex-col transition-all duration-300 ease-in-out"
:class="!sidebarStore.isWebpagesCollapsed ? 'block' : 'hidden'"
>
<SidebarLink
<div
v-for="link in sidebarSettings.data.web_pages"
:link="link"
:isCollapsed="sidebarStore.isSidebarCollapsed"
class="mx-2 my-0.5"
:showControls="isModerator ? true : false"
@openModal="openPageModal"
@deletePage="deletePage"
/>
>
<SidebarLink
:link="link"
:isCollapsed="sidebarStore.isSidebarCollapsed"
:showControls="isModerator ? true : false"
@openModal="openPageModal"
@deletePage="deletePage"
/>
</div>
</div>
</div>
</div>
@@ -196,7 +199,7 @@ import { usersStore } from '@/stores/user'
import { sessionStore } from '@/stores/session'
import { useSidebar } from '@/stores/sidebar'
import { useSettings } from '@/stores/settings'
import { Button, createResource, Tooltip } from 'frappe-ui'
import { Button, call, createResource, Tooltip } from 'frappe-ui'
import PageModal from '@/components/Modals/PageModal.vue'
import { capture } from '@/telemetry'
import LMSLogo from '@/components/Icons/LMSLogo.vue'
@@ -214,6 +217,7 @@ import {
Users,
BookText,
Zap,
Check,
} from 'lucide-vue-next'
import {
TrialBanner,
@@ -360,37 +364,49 @@ const addProgrammingExercises = () => {
}
}
const addPrograms = () => {
let activeFor = ['Programs', 'ProgramForm']
let index = 1
let canAddProgram = false
const addPrograms = async () => {
let canAddProgram = await checkIfCanAddProgram()
if (!canAddProgram) return
let activeFor = ['Programs', 'ProgramDetail']
let index = 2
if (
!isInstructor.value &&
!isModerator.value &&
settingsStore.learningPaths.data
) {
sidebarLinks.value = sidebarLinks.value.filter(
(link) => link.label !== 'Courses'
)
activeFor.push('CourseDetail')
activeFor.push('Lesson')
index = 0
canAddProgram = true
} else if (isInstructor.value || isModerator.value) {
canAddProgram = true
}
sidebarLinks.value.splice(index, 0, {
label: 'Programs',
icon: 'Route',
to: 'Programs',
activeFor: activeFor,
})
}
if (canAddProgram) {
sidebarLinks.value.splice(index, 0, {
label: 'Programs',
icon: 'Route',
to: 'Programs',
activeFor: activeFor,
const addContactUsDetails = () => {
if (settingsStore.contactUsEmail?.data || settingsStore.contactUsURL?.data) {
sidebarLinks.value.push({
label: 'Contact Us',
icon: settingsStore.contactUsURL?.data ? 'Headset' : 'Mail',
to: settingsStore.contactUsURL?.data
? settingsStore.contactUsURL.data
: settingsStore.contactUsEmail?.data,
})
}
}
const checkIfCanAddProgram = async () => {
if (isModerator.value || isInstructor.value) {
return true
}
const programs = await call('lms.lms.utils.get_programs')
return programs.enrolled.length > 0 || programs.published.length > 0
}
const addHome = () => {
sidebarLinks.value.unshift({
label: 'Home',
icon: 'Home',
to: 'Home',
activeFor: ['Home'],
})
}
const openPageModal = (link) => {
showPageModal.value = true
pageToEdit.value = link
@@ -593,6 +609,11 @@ const articles = ref([
{ name: 'create-a-live-class', title: __('Create a live class') },
],
},
{
title: __('Learning Paths'),
opened: false,
subArticles: [{ name: 'add-a-program', title: __('Add a program') }],
},
{
title: __('Assessments'),
opened: false,
@@ -638,15 +659,121 @@ const setUpOnboarding = () => {
}
}
const addMyPoints = () => {
const roles = userResource.data?.roles || []
if (roles.includes('LMS Student') || roles.includes('LMS Schoolchild')) {
sidebarLinks.value.push({
label: __('My points'),
icon: 'Award',
to: 'MyPoints',
activeFor: [],
})
}
}
const addLeaderBoard = () => {
if (user) {
sidebarLinks.value.push({
label: __('Leader Board'),
icon: 'Trophy',
to: 'LeaderBoard',
activeFor: [],
})
}
}
const addChatGPT = () => {
const roles = userResource.data?.roles || []
let URL = ''
let nameLabel = ''
if (roles.includes('LMS Schoolchild') || roles.includes('LMS Student') || roles.includes('Course Creator')) {
if (roles.includes('LMS Schoolchild')) {
URL = 'chatgpt-schoolchild'
nameLabel = __('ChatGPT for Schoolers')
} else if (roles.includes('LMS Student')) {
URL = 'chatgpt-schoolchild'
nameLabel = __('ChatGPT for Students')
} else if (roles.includes('Course Creator')) {
URL = 'ai-teachers'
nameLabel = __('ChatGPT for Teachers')
}
sidebarLinks.value.push({
label: nameLabel,
icon: 'Cpu',
to: URL,
external: true,
activeFor: [],
})
}
}
const addMyChild = () => {
const roles = userResource.data?.roles || []
if (roles.includes('Parent')) {
sidebarLinks.value.push({
label: __('My Child'),
icon: 'User',
to: 'my-child',
activeFor: [],
external: true,
})
}
}
//test of new page
const addProfile = () => {
const roles = userResource.data?.roles || []
if (roles.includes('LMS Student')) {
sidebarLinks.value.push({
label: __('Student Profile'),
icon: 'Home',
to: 'StudentProfile',
activeFor: [],
})
} else if (roles.includes('LMS Schoolchild')) {
sidebarLinks.value.push({
label: __('Schoolchildren Profile'),
icon: 'Home',
to: 'SchoolchildrenProfile',
activeFor: [],
})
} else if (roles.includes('Course Creator')) {
sidebarLinks.value.push({
label: __('Course Creator Profile'),
icon: 'Home',
to: 'CourseCreatorProfile',
activeFor: [],
})
} else {
sidebarLinks.value.push({
label: __('Parent Profile'),
icon: 'Home',
to: 'ParentProfile',
activeFor: [],
})
}
}
watch(userResource, () => {
addContactUsDetails()
if (userResource.data) {
isModerator.value = userResource.data.is_moderator
isInstructor.value = userResource.data.is_instructor
addHome()
addPrograms()
addProgrammingExercises()
addQuizzes()
addAssignments()
setUpOnboarding()
addMyPoints()
addLeaderBoard()
addChatGPT()
addMyChild()
addProfile()
}
})
+6
View File
@@ -70,6 +70,9 @@
<FileUploader
v-if="!submissionFile"
:fileTypes="getType()"
:uploadArgs="{
private: true,
}"
:validateFile="validateFile"
@success="(file) => saveSubmission(file)"
>
@@ -127,6 +130,9 @@
@change="(val) => (answer = val)"
:editable="true"
:fixedMenu="true"
:uploadArgs="{
private: true,
}"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[7rem]"
/>
</div>
+12 -4
View File
@@ -56,7 +56,7 @@
</div>
<div v-if="!readOnlyMode">
<router-link
v-if="isModerator || isStudent"
v-if="canAccessBatch"
:to="{
name: 'Batch',
params: {
@@ -66,11 +66,11 @@
>
<Button variant="solid" class="w-full mt-4">
<template #prefix>
<Settings v-if="isModerator" class="size-4 stroke-1.5" />
<LogIn v-else class="size-4 stroke-1.5" />
<LogIn v-if="isStudent" class="size-4 stroke-1.5" />
<Settings v-else class="size-4 stroke-1.5" />
</template>
<span>
{{ isModerator ? __('Manage Batch') : __('Visit Batch') }}
{{ isStudent ? __('Visit Batch') : __('Manage Batch') }}
</span>
</Button>
</router-link>
@@ -204,4 +204,12 @@ const isStudent = computed(() => {
const isModerator = computed(() => {
return user.data?.is_moderator
})
const isEvaluator = computed(() => {
return user.data?.is_evaluator
})
const canAccessBatch = computed(() => {
return isModerator.value || isStudent.value || isEvaluator.value
})
</script>
+1 -1
View File
@@ -5,7 +5,7 @@
{{ __('Statistics') }}
</div>
</div>
<div class="grid grid-cols-4 gap-5 mb-8">
<div class="grid grid-cols-2 md:grid-cols-4 gap-5 mb-8">
<NumberChart
class="border rounded-md"
:config="{ title: __('Students'), value: students.data?.length || 0 }"
@@ -0,0 +1,67 @@
<template>
<Dialog
v-model="show"
:options="{
title: __('Contact Us'),
size: 'md',
}"
>
<template #body-content>
<div class="flex flex-col gap-4">
<FormControl
v-model="subject"
:label="__('Subject')"
type="text"
:required="true"
/>
<div>
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Message') }}
<span class="text-ink-red-3">*</span>
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (message = val)"
editorClass="prose-sm py-2 px-2 min-h-[200px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
/>
</div>
</div>
</template>
<template #actions="{ close }">
<div class="pb-5 float-right">
<Button variant="solid" @click="sendMail(close)">
{{ __('Send') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { Button, call, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { ref } from 'vue'
import { useSettings } from '@/stores/settings'
const show = defineModel<boolean>({ required: true, default: false })
const subject = ref('')
const message = ref('')
const settingsStore = useSettings()
const sendMail = (close: Function) => {
call('frappe.core.doctype.communication.email.make', {
recipients: settingsStore.contactUsEmail?.data,
subject: subject.value,
content: message.value,
send_email: true,
})
.then(() => {
toast.success(__('Email sent successfully'))
close()
subject.value = ''
message.value = ''
})
.catch(() => {
toast.error(__('Failed to send email'))
close()
})
}
</script>
@@ -37,7 +37,7 @@
</slot>
</template>
<template #body="{ isOpen }">
<div v-show="isOpen">
<div v-show="isOpen" class="">
<div
class="mt-1 rounded-lg bg-surface-white py-1 text-base border-2"
>
+37 -30
View File
@@ -1,11 +1,11 @@
<template>
<div
v-if="course.title"
class="flex flex-col h-full rounded-md border-2 overflow-auto text-ink-gray-9"
class="flex flex-col h-full rounded-md overflow-auto text-ink-gray-9"
style="min-height: 350px"
>
<div
class="w-[100%] h-[168px] bg-cover bg-center bg-no-repeat"
class="w-[100%] h-[168px] bg-cover bg-center bg-no-repeat border-t border-x rounded-t-md"
:style="
course.image
? { backgroundImage: `url('${encodeURI(course.image)}')` }
@@ -15,10 +15,10 @@
}
"
>
<div class="flex items-center flex-wrap relative top-4 px-2 w-fit">
<!-- <div class="flex items-center flex-wrap relative top-4 px-2 w-fit">
<div
v-if="course.featured"
class="flex items-center space-x-1 text-xs text-ink-amber-3 bg-surface-white px-2 py-0.5 rounded-md mr-1 mb-1"
class="flex items-center space-x-1 text-xs text-ink-amber-3 bg-surface-white border border-outline-amber-1 px-2 py-0.5 rounded-md mr-1 mb-1"
>
<Star class="size-3 stroke-2" />
<span>
@@ -32,16 +32,22 @@
>
{{ tag }}
</div>
</div>
</div> -->
<div
v-if="!course.image"
class="flex items-center justify-center text-white flex-1 font-extrabold text-2xl my-auto"
:class="course.tags ? 'h-[80%]' : 'h-full'"
class="flex items-center justify-center text-white flex-1 font-extrabold my-auto px-5 text-center leading-6 h-full"
:class="
course.title.length > 32
? 'text-lg'
: course.title.length > 20
? 'text-xl'
: 'text-2xl'
"
>
{{ course.title }}
</div>
</div>
<div class="flex flex-col flex-auto p-4">
<div class="flex flex-col flex-auto p-4 border-x-2 border-b-2 rounded-b-md">
<div class="flex items-center justify-between mb-2">
<div v-if="course.lessons">
<Tooltip :text="__('Lessons')">
@@ -56,7 +62,7 @@
<Tooltip :text="__('Enrolled Students')">
<span class="flex items-center">
<Users class="h-4 w-4 stroke-1.5 mr-1" />
{{ course.enrollments }}
{{ formatAmount(course.enrollments) }}
</span>
</Tooltip>
</div>
@@ -70,18 +76,16 @@
</Tooltip>
</div>
<!-- <div v-if="course.status != 'Approved'">
<Badge
variant="subtle"
:theme="course.status === 'Under Review' ? 'orange' : 'blue'"
size="sm"
>
{{ course.status }}
</Badge>
</div> -->
<Tooltip v-if="course.featured" :text="__('Featured')">
<Award class="size-4 stroke-2 text-ink-amber-3" />
</Tooltip>
</div>
<div v-if="course.image" class="text-xl font-semibold leading-6">
<div
v-if="course.image"
class="font-semibold leading-6"
:class="course.title.length > 32 ? 'text-lg' : 'text-xl'"
>
{{ course.title }}
</div>
@@ -112,27 +116,30 @@
<CourseInstructors :instructors="course.instructors" />
</div>
<div v-if="course.paid_course" class="font-semibold">
{{ course.price }}
</div>
<div class="flex items-center space-x-2">
<div v-if="course.paid_course" class="font-semibold">
{{ course.price }}
</div>
<Tooltip
v-if="course.paid_certificate || course.enable_certification"
:text="__('Get Certified')"
>
<GraduationCap class="size-5 stroke-1.5" />
</Tooltip>
<Tooltip
v-if="course.paid_certificate || course.enable_certification"
:text="__('Get Certified')"
>
<GraduationCap class="size-5 stroke-1.5 text-ink-gray-7" />
</Tooltip>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue'
import { Award, BookOpen, GraduationCap, Star, Users } from 'lucide-vue-next'
import { sessionStore } from '@/stores/session'
import { Tooltip } from 'frappe-ui'
import { theme } from '@/utils/theme'
import { formatAmount } from '@/utils'
import CourseInstructors from '@/components/CourseInstructors.vue'
import UserAvatar from '@/components/UserAvatar.vue'
import ProgressBar from '@/components/ProgressBar.vue'
const { user } = sessionStore()
@@ -169,6 +169,7 @@
</div>
</div>
<CourseProgressSummary
v-if="user.data?.is_moderator || is_instructor()"
v-model="showProgressModal"
:courseName="course.data.name"
:enrollments="course.data.enrollments"
@@ -1,11 +1,12 @@
<template>
<div class="">
<div class="text-ink-gray-7">
<span v-if="instructors?.length == 1">
<router-link
:to="{
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].full_name }}
</router-link>
@@ -16,6 +17,7 @@
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].first_name }}
</router-link>
@@ -25,6 +27,7 @@
name: 'Profile',
params: { username: instructors[1].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[1].first_name }}
</router-link>
@@ -35,6 +38,7 @@
name: 'Profile',
params: { username: instructors[0].username },
}"
class="text-ink-gray-7 hover:text-ink-gray-9"
>
{{ instructors[0].first_name }}
</router-link>
+11
View File
@@ -208,6 +208,10 @@ const props = defineProps({
type: Boolean,
default: false,
},
lessonProgress: {
type: Number,
default: 0,
},
})
const outline = createResource({
@@ -229,6 +233,13 @@ watch(
}
)
watch(
() => props.lessonProgress,
() => {
outline.reload()
}
)
const deleteLesson = createResource({
url: 'lms.lms.api.delete_lesson',
makeParams(values) {
+6 -12
View File
@@ -1,16 +1,10 @@
<template>
<div class="relative flex h-full flex-col">
<div class="h-full flex-1">
<div class="flex h-screen text-base bg-surface-white">
<div
class="relative block min-h-0 flex-shrink-0 overflow-hidden hover:overflow-auto"
>
<AppSidebar />
</div>
<div class="w-full overflow-auto" id="scrollContainer">
<slot />
</div>
</div>
<div class="flex h-screen w-screen">
<div class="h-full border-r bg-surface-menu-bar">
<AppSidebar />
</div>
<div class="flex-1 flex flex-col h-full overflow-auto bg-surface-white">
<slot />
</div>
</div>
</template>
@@ -32,13 +32,13 @@
"
:options="[
{
label: 'Edit',
label: __('Edit'),
onClick() {
reply.editable = true
},
},
{
label: 'Delete',
label: __('Delete'),
onClick() {
deleteReply(reply)
},
+6 -3
View File
@@ -5,6 +5,9 @@
class="float-right"
@click="openTopicModal()"
>
<template #prefix>
<Plus class="size-4" />
</template>
{{ __('New {0}').format(singularize(title)) }}
</Button>
<div class="text-xl font-semibold text-ink-gray-9">
@@ -49,7 +52,7 @@
class="flex flex-col items-center justify-center border-2 border-dashed mt-5 py-8 rounded-md"
>
<MessageSquareText class="w-7 h-7 text-ink-gray-4 stroke-1.5 mr-2" />
<div class="">
<div class="mt-2">
<div v-if="emptyStateTitle" class="font-medium mb-2">
{{ __(emptyStateTitle) }}
</div>
@@ -73,7 +76,7 @@ import { singularize, timeAgo } from '@/utils'
import { ref, onMounted, inject, onUnmounted } from 'vue'
import DiscussionReplies from '@/components/DiscussionReplies.vue'
import DiscussionModal from '@/components/Modals/DiscussionModal.vue'
import { MessageSquareText } from 'lucide-vue-next'
import { MessageSquareText, Plus } from 'lucide-vue-next'
import { getScrollContainer } from '@/utils/scrollContainer'
const showTopics = ref(true)
@@ -102,7 +105,7 @@ const props = defineProps({
},
emptyStateText: {
type: String,
default: 'Start a discussion',
default: 'Start a Discussion',
},
singleThread: {
type: Boolean,
+1 -1
View File
@@ -5,7 +5,7 @@
{{ __('No {0}').format(type?.toLowerCase()) }}
</div>
<div
class="leading-5 text-base w-2/5 text-base text-center text-ink-gray-7"
class="leading-5 text-base w-full md:w-2/5 text-base text-center text-ink-gray-7"
>
{{
__(
+97
View File
@@ -0,0 +1,97 @@
<template>
<Dialog v-model="showDialog">
<template #body-title>
<h2 class="text-lg font-bold">{{ __('Install Frappe Learning') }}</h2>
</template>
<template #body-content>
<p>
{{
__(
'Get the app on your device for easy access & a better experience!'
)
}}
</p>
</template>
<template #actions>
<Button variant="solid" class="w-full py-5" @click="install">
<template #prefix><FeatherIcon name="download" class="w-4" /></template>
{{ __('Install') }}
</Button>
</template>
</Dialog>
<Popover :show="iosInstallMessage" placement="top-start">
<template #body>
<div
class="fixed top-[20rem] translate-x-1/3 z-20 flex flex-col gap-3 rounded bg-surface-white py-5 drop-shadow-xl"
>
<div
class="mb-1 flex flex-row items-center justify-between px-3 text-center"
>
<span class="text-base font-bold text-gray-900">
{{ __('Install Frappe Learning') }}
</span>
<span class="inline-flex items-baseline">
<FeatherIcon
name="x"
class="ml-auto h-4 w-4 text-gray-700"
@click="iosInstallMessage = false"
/>
</span>
</div>
<div class="px-3 text-xs text-gray-800">
<span class="flex flex-col gap-2">
<span class="leading-5">
{{
__(
'Get the app on your iPhone for easy access & a better experience'
)
}}
</span>
<span class="inline-flex items-start whitespace-nowrap">
<span>{{ __('Tap') }}&nbsp;</span>
<FeatherIcon name="share" class="h-4 w-4 text-blue-600" />
<span>&nbsp;{{ __("and then 'Add to Home Screen'") }}</span>
</span>
</span>
</div>
</div>
</template>
</Popover>
</template>
<script setup>
import { ref } from 'vue'
import { Button, Dialog, FeatherIcon, Popover } from 'frappe-ui'
const deferredPrompt = ref(null)
const showDialog = ref(false)
const iosInstallMessage = ref(false)
const isIos = () => {
const userAgent = window.navigator.userAgent.toLowerCase()
return /iphone|ipad|ipod/.test(userAgent)
}
const isInStandaloneMode = () =>
'standalone' in window.navigator && window.navigator.standalone
if (isIos() && !isInStandaloneMode()) iosInstallMessage.value = true
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault()
deferredPrompt.value = e
if (isIos() && !isInStandaloneMode()) iosInstallMessage.value = true
else showDialog.value = true
})
window.addEventListener('appinstalled', () => {
showDialog.value = false
deferredPrompt.value = null
})
const install = () => {
deferredPrompt.value.prompt()
showDialog.value = false
}
</script>
+4 -1
View File
@@ -3,7 +3,7 @@
class="flex flex-col border rounded-md p-3 h-full hover:border-outline-gray-3"
>
<div class="flex space-x-4 mb-4">
<div class="flex flex-col space-y-2 flex-1">
<div class="flex flex-col space-y-2 flex-1 break-all">
<div class="text-lg font-semibold text-ink-gray-9">
{{ job.company_name }}
</div>
@@ -33,6 +33,9 @@
<Badge>
{{ job.type }}
</Badge>
<Badge v-if="job.work_mode">
{{ job.work_mode }}
</Badge>
<Badge>
{{ dayjs(job.creation).fromNow() }}
</Badge>
+35
View File
@@ -9,6 +9,16 @@
allowfullscreen
></iframe>
</div>
<div v-if="rutube">
<iframe
class="rutube-video"
:src="getRutubeVideoSource(rutube.split('/').pop())"
width="100%"
:height="screenSize.width < 640 ? 200 : 400"
frameborder="0"
allowfullscreen
></iframe>
</div>
<div v-for="block in content?.split('\n\n')">
<div v-if="block.includes('{{ YouTubeVideo')">
<iframe
@@ -20,6 +30,16 @@
allowfullscreen
></iframe>
</div>
<div v-else-if="block.includes('{{ RutubeVideo')">
<iframe
class="rutube-video"
:src="getRutubeVideoSource(block)"
width="100%"
:height="screenSize.width < 640 ? 200 : 400"
frameborder="0"
allowfullscreen
></iframe>
</div>
<div v-else-if="block.includes('{{ Quiz')">
<Quiz :quiz="getId(block)" />
</div>
@@ -97,6 +117,13 @@ const getYouTubeVideoSource = (block) => {
return `https://www.youtube.com/embed/${block}`
}
const getRutubeVideoSource = (block) => {
if (block.includes('{{')) {
block = getId(block)
}
return `https://rutube.ru/play/embed/${block}`
}
const getPDFSource = (block) => {
return `${getId(block)}#toolbar=0`
}
@@ -105,3 +132,11 @@ const getId = (block) => {
return block.match(/\(["']([^"']+?)["']\)/)[1]
}
</script>
<style scoped>
.youtube-video,
.rutube-video {
display: block;
margin: 0 auto;
}
</style>
+2 -2
View File
@@ -52,9 +52,9 @@ const contentMap = {
'To upload Image, Video, Audio or PDF from your system, click on the add icon and select upload from the menu. Then choose the file you want to add to the lesson and it gets added to your lesson.',
},
youtube: {
title: 'How to add a YouTube Video?',
title: 'How to add a YouTube Video/RuTube?',
description:
'Copy the URL of the video from YouTube and paste it in the editor.',
'Copy the URL of the video from YouTube/RuTube and paste it in the editor.',
},
remove: {
title: 'How to remove an embed?',
+11 -4
View File
@@ -22,7 +22,10 @@
</span>
</Button>
</div>
<div v-if="liveClasses.data?.length" class="grid grid-cols-3 gap-5 mt-5">
<div
v-if="liveClasses.data?.length"
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5 mt-5"
>
<div
v-for="cls in liveClasses.data"
class="flex flex-col border rounded-md h-full text-ink-gray-7 hover:border-outline-gray-3 p-3"
@@ -51,8 +54,8 @@
<div class="flex items-center space-x-2">
<Clock class="w-4 h-4 stroke-1.5" />
<span>
{{ formatTime(cls.time) }} -
{{ dayjs(getClassEnd(cls)).format('HH:mm') }}
{{ dayjs(getClassStart(cls)).format('hh:mm A') }} -
{{ dayjs(getClassEnd(cls)).format('hh:mm A') }}
</span>
</div>
<div
@@ -178,8 +181,12 @@ const canAccessClass = (cls) => {
return true
}
const getClassStart = (cls) => {
return new Date(`${cls.date}T${cls.time}`)
}
const getClassEnd = (cls) => {
const classStart = new Date(`${cls.date}T${cls.time}`)
const classStart = getClassStart(cls)
return new Date(classStart.getTime() + cls.duration * 60000)
}
+179 -37
View File
@@ -56,6 +56,7 @@
<script setup>
import { getSidebarLinks } from '@/utils'
import { useRouter } from 'vue-router'
import { call } from 'frappe-ui'
import { watch, ref, onMounted } from 'vue'
import { sessionStore } from '@/stores/session'
import { useSettings } from '@/stores/settings'
@@ -71,17 +72,16 @@ const sidebarLinks = ref(getSidebarLinks())
const otherLinks = ref([])
const showMenu = ref(false)
const menu = ref(null)
const isModerator = ref(false)
const isInstructor = ref(false)
onMounted(() => {
sidebarSettings.reload(
{},
{
onSuccess(data) {
filterLinksToShow(data)
addOtherLinks()
},
}
)
// Вызываем addSideBar только если userResource уже загружен
if (userResource.data) {
addSideBar()
}
addOtherLinks()
filterLinksToShow(data)
})
const handleOutsideClick = (e) => {
@@ -110,55 +110,196 @@ const filterLinksToShow = (data) => {
})
}
const addSideBar = () => {
sidebarLinks.value = [] // Очищаем, чтобы избежать дублирования
// Проверяем роли пользователя
const roles = userResource.data?.roles || []
sidebarLinks.value.push({
label: __('Courses'),
icon: 'BookOpen',
to: 'Courses',
activeFor: [
'Courses',
'CourseDetail',
'Lesson',
'CourseForm',
'LessonForm',
],
})
sidebarLinks.value.push({
label: __('Leader Board'),
icon: 'Trophy',
to: 'LeaderBoard',
activeFor: [],
})
if (roles.includes('LMS Student') || roles.includes('LMS Schoolchild')) {
otherLinks.value.push({
label: __('My points'),
icon: 'Award',
to: 'MyPoints',
activeFor: [],
})
}
if (roles.includes('Parent')) {
otherLinks.value.push({
label: __('My Child'),
icon: 'User',
to: 'my-child',
external: true,
activeFor: [],
})
}
let chatGPTURL = ''
let chatGPTLabel = ''
if (roles.includes('LMS Schoolchild')) {
chatGPTURL = 'chatgpt-schoolchild'
chatGPTLabel = __('ChatGPT for Schoolers')
} else if (roles.includes('LMS Student')) {
chatGPTURL = 'chatgpt-schoolchild'
chatGPTLabel = __('ChatGPT for Students')
} else if (roles.includes('Course Creator')) {
chatGPTURL = 'ai-teachers'
chatGPTLabel = __('ChatGPT for Teachers')
}
if (chatGPTURL) {
sidebarLinks.value.push({
label: chatGPTLabel,
icon: 'Cpu',
to: chatGPTURL,
external: true,
activeFor: [],
})
}
}
const addOtherLinks = () => {
otherLinks.value = []
if (user) {
const roles = userResource.data?.roles || []
if (!userResource.data?.is_instructor && !userResource.data?.is_moderator) {
otherLinks.value.push({
label: __('Programs'),
icon: 'Route',
to: 'Programs',
activeFor: ['Programs', 'ProgramForm', 'CourseDetail', 'Lesson'],
})
} else if (userResource.data?.is_instructor || userResource.data?.is_moderator) {
otherLinks.value.push({
label: __('Programs'),
icon: 'Route',
to: 'Programs',
activeFor: ['Programs', 'ProgramForm'],
})
}
if (userResource.data?.is_moderator || userResource.data?.is_instructor) {
otherLinks.value.push({
label: __('Quizzes'),
icon: 'CircleHelp',
to: 'Quizzes',
activeFor: [
'Quizzes',
'QuizForm',
'QuizSubmissionList',
'QuizSubmission',
],
})
otherLinks.value.push({
label: __('Assignments'),
icon: 'Pencil',
to: 'Assignments',
activeFor: [
'Assignments',
'AssignmentForm',
'AssignmentSubmissionList',
'AssignmentSubmission',
],
}),
otherLinks.value.push({
label: 'Programming Exercises',
icon: 'Code',
to: 'ProgrammingExercises',
})
}
if (roles.includes('LMS Student') || roles.includes('LMS Schoolchild')) {
otherLinks.value.push({
label: __('My points'),
icon: 'Award',
to: 'my_points',
external: true,
activeFor: [],
})
}
let chatGPTURL = ''
let chatGPTLabel = ''
if (roles.includes('LMS Schoolchild')) {
chatGPTURL = 'chatgpt-schoolchild'
chatGPTLabel = __('ChatGPT for Schoolers')
} else if (roles.includes('LMS Student')) {
chatGPTURL = 'chatgpt-schoolchild'
chatGPTLabel = __('ChatGPT for Students')
} else if (roles.includes('Course Creator')) {
chatGPTURL = 'ai-teachers'
chatGPTLabel = __('ChatGPT for Teachers')
}
if (chatGPTURL) {
otherLinks.value.push({
label: chatGPTLabel,
icon: 'Cpu',
to: chatGPTURL,
external: true,
activeFor: [],
})
}
otherLinks.value.push({
label: 'Notifications',
icon: 'Bell',
to: 'Notifications',
label: __('Leader Board'),
icon: 'Trophy',
to: 'leaderboardsample',
external: true,
activeFor: [],
})
otherLinks.value.push({
label: 'Profile',
label: __('Profile'),
icon: 'UserRound',
to: 'Profile',
params: { username: userResource.data?.username },
})
otherLinks.value.push({
label: 'Log out',
label: __('Log out'),
icon: 'LogOut',
})
} else {
otherLinks.value.push({
label: 'Log in',
label: __('Log in'),
icon: 'LogIn',
})
}
}
watch(userResource, () => {
if (
userResource.data &&
(userResource.data.is_moderator || userResource.data.is_instructor)
) {
addQuizzes()
addAssignments()
if (userResource.data) {
addSideBar() // Обновляем sidebarLinks при изменении userResource
addOtherLinks() // Обновляем otherLinks
}
})
const addQuizzes = () => {
otherLinks.value.push({
label: 'Quizzes',
icon: 'CircleHelp',
to: 'Quizzes',
})
}
const addAssignments = () => {
otherLinks.value.push({
label: 'Assignments',
icon: 'Pencil',
to: 'Assignments',
})
}
let isActive = (tab) => {
return tab.activeFor?.includes(router.currentRoute.value.name)
}
@@ -176,6 +317,7 @@ const handleClick = (tab) => {
username: userResource.data?.username,
},
})
else if (tab.external) window.location.href = `/${tab.to}`
else router.push({ name: tab.to })
}
@@ -25,6 +25,7 @@
<div class="">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Reply To') }}
<span class="text-ink-red-3">*</span>
</div>
<Input type="text" v-model="announcement.replyTo" />
</div>
@@ -70,8 +71,8 @@ const announcementResource = createResource({
url: 'frappe.core.doctype.communication.email.make',
makeParams(values) {
return {
recipients: props.students.join(', '),
cc: announcement.replyTo,
recipients: announcement.replyTo,
bcc: props.students.join(', '),
subject: announcement.subject,
content: announcement.announcement,
doctype: 'LMS Batch',
@@ -95,6 +96,9 @@ const makeAnnouncement = (close) => {
if (!announcement.announcement) {
return __('Announcement is required')
}
if (!announcement.replyTo) {
return __('Reply To is required')
}
},
onSuccess() {
close()
@@ -113,6 +113,14 @@ watch(
{ flush: 'post' }
)
watch(show, (newVal) => {
if (newVal && props.assignmentID === 'new') {
assignment.title = ''
assignment.type = ''
assignment.question = ''
}
})
const saveAssignment = () => {
if (props.assignmentID == 'new') {
assignments.value.insert.submit(
@@ -11,7 +11,7 @@
<Avatar :image="student.user_image" size="3xl" />
<div class="space-y-1">
<div class="flex items-center space-x-2">
<div class="text-xl font-semibold">
<div class="text-xl font-semibold text-ink-gray-9">
{{ student.full_name }}
</div>
<Badge
@@ -36,7 +36,9 @@
v-if="Object.keys(student.assessments).length"
class="space-y-2 text-sm"
>
<div class="flex items-center border-b pb-1 font-medium">
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Assessment') }}
</span>
@@ -86,7 +88,9 @@
v-if="Object.keys(student.courses).length"
class="space-y-2 text-sm"
>
<div class="flex items-center border-b pb-1 font-medium">
<div
class="flex items-center border-b pb-1 font-medium text-ink-gray-9"
>
<span class="flex-1">
{{ __('Courses') }}
</span>
@@ -50,7 +50,7 @@
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
</div>
<div class="flex flex-col">
<span>
<span class="text-ink-gray-9">
{{ chapter.scorm_package.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
@@ -7,12 +7,11 @@
}"
>
<template #body-content>
<div class="flex justify-between space-x-10 text-base mt-10">
<div
class="flex flex-col-reverse md:flex-row justify-between md:space-x-10 text-base mt-10"
>
<div class="w-full">
<div class="flex items-center justify-between space-x-5 mb-4">
<!-- <div class="text-xl font-semibold text-ink-gray-6">
{{ __('{0} Members').format(memberCount) }}
</div> -->
<FormControl
v-model="searchFilter"
:placeholder="__('Search by Member')"
@@ -90,7 +89,9 @@
</div>
</div>
<div class="mb-4 self-start w-full space-y-5">
<div class="flex items-center space-x-4">
<div
class="flex flex-col md:flex-row items-center space-y-2 md:space-y-0 md:space-x-4"
>
<NumberChart
class="border rounded-md w-full"
:config="{
@@ -147,10 +148,11 @@ import {
import { computed, ref, watch } from 'vue'
import { theme } from '@/utils/theme'
const show = defineModel<boolean | undefined>()
const show = defineModel<boolean>({ default: false })
const searchFilter = ref<string | null>(null)
type Filters = {
course: string | undefined
member_name?: string[]
}
@@ -221,7 +223,6 @@ const progressColumns = computed(() => {
{
label: __('Progress'),
key: 'progress',
width: '30%',
align: 'right',
icon: 'trending-up',
},
+108 -68
View File
@@ -1,11 +1,11 @@
<template>
<Dialog
:options="{
title: 'Edit your profile',
size: 'xl',
title: __('Edit your profile'),
size: '3xl',
actions: [
{
label: 'Save',
label: __('Save'),
variant: 'solid',
onClick: (close) => saveProfile(close),
},
@@ -13,74 +13,81 @@
}"
>
<template #body-content>
<div>
<FileUploader
v-if="!profile.image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template v-slot="{ file, progress, uploading, openFileSelector }">
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading
? `Uploading ${progress}%`
: 'Upload a profile image'
}}
</Button>
<div class="grid grid-cols-2 gap-5">
<div class="space-y-4">
<!-- <Uploader
v-model="profile.image.file_url"
label="Profile Image"
description="Your profile image to help others recognize you."
/> -->
<div>
<div class="text-xs text-ink-gray-5 mb-1">
{{ __('Profile Image') }}
</div>
<FileUploader
v-if="!profile.image"
:fileTypes="['image/*']"
:validateFile="validateFile"
@success="(file) => saveImage(file)"
>
<template
v-slot="{ file, progress, uploading, openFileSelector }"
>
<div class="mb-4">
<Button @click="openFileSelector" :loading="uploading">
{{
uploading
? `Uploading ${progress}%`
: 'Upload a profile image'
}}
</Button>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="flex items-center">
<img
:src="profile.image.file_url"
class="object-cover h-[50px] w-[50px] rounded-full border-4 border-white object-cover"
/>
<div class="text-base flex flex-col ml-2">
<span>
{{ profile.image.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
{{ getFileSize(profile.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
/>
</div>
</div>
</template>
</FileUploader>
<div v-else class="mb-4">
<div class="text-xs text-ink-gray-5 mb-1">
{{ __('Profile Image') }}
</div>
<div class="flex items-center">
<div class="border rounded-md p-2 mr-2">
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
<FormControl v-model="profile.first_name" :label="__('First Name')" />
<FormControl v-model="profile.last_name" :label="__('Last Name')" />
<FormControl v-model="profile.headline" :label="__('Headline')" />
<Link
:label="__('Language')"
v-model="profile.language"
doctype="Language"
/>
</div>
<div>
<div class="mb-4">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<div class="text-base flex flex-col">
<span>
{{ profile.image.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
{{ getFileSize(profile.image.file_size) }}
</span>
</div>
<X
@click="removeImage()"
class="bg-surface-gray-3 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
editorClass="prose-sm py-2 px-2 min-h-[200px] border-outline-gray-2 hover:border-outline-gray-3 rounded-b-md bg-surface-gray-3"
/>
</div>
</div>
<FormControl
v-model="profile.first_name"
:label="__('First Name')"
class="mb-4"
/>
<FormControl
v-model="profile.last_name"
:label="__('Last Name')"
class="mb-4"
/>
<FormControl
v-model="profile.headline"
:label="__('Headline')"
class="mb-4"
/>
<div class="mb-4">
<div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Bio') }}
</div>
<TextEditor
:fixedMenu="true"
@change="(val) => (profile.bio = val)"
:content="profile.bio"
editorClass="prose-sm py-2 px-2 min-h-[200px] border-outline-gray-2 hover:border-outline-gray-3 rounded-md bg-surface-gray-3"
/>
</div>
</div>
</template>
</Dialog>
@@ -95,11 +102,14 @@ import {
TextEditor,
toast,
} from 'frappe-ui'
import { reactive, watch } from 'vue'
import { FileText, X } from 'lucide-vue-next'
import { getFileSize } from '@/utils'
import { ref, reactive, watch } from 'vue'
import { X } from 'lucide-vue-next'
import { getFileSize, decodeEntities } from '@/utils'
import Link from '@/components/Controls/Link.vue'
import DOMPurify from 'dompurify'
const reloadProfile = defineModel('reloadProfile')
const hasLanguageChanged = ref(false)
const props = defineProps({
profile: {
@@ -147,12 +157,32 @@ const updateProfile = createResource({
})
const saveProfile = (close) => {
profile.bio = DOMPurify.sanitize(decodeEntities(profile.bio), {
ALLOWED_TAGS: [
'b',
'i',
'em',
'strong',
'a',
'p',
'br',
'ul',
'ol',
'li',
'img',
],
ALLOWED_ATTR: ['href', 'target', 'src'],
})
updateProfile.submit(
{},
{
onSuccess() {
close()
reloadProfile.value.reload()
if (hasLanguageChanged.value) {
hasLanguageChanged.value = false
window.location.reload()
}
},
onError(err) {
toast.error(err.messages?.[0] || err)
@@ -183,9 +213,19 @@ watch(
profile.first_name = newVal.first_name
profile.last_name = newVal.last_name
profile.headline = newVal.headline
profile.language = newVal.language
profile.bio = newVal.bio
if (newVal.user_image) imageResource.submit({ image: newVal.user_image })
}
}
)
watch(
() => profile.language,
(newVal, oldVal) => {
if (newVal !== oldVal) {
hasLanguageChanged.value = true
}
}
)
</script>
+37 -6
View File
@@ -66,7 +66,11 @@
</template>
{{ __('View Certificate') }}
</Button>
<Button v-else @click="openCallLink(event.venue)" class="w-full">
<Button
v-else-if="userIsEvaluator()"
@click="openCallLink(event.venue)"
class="w-full"
>
<template #prefix>
<Video class="h-4 w-4 stroke-1.5" />
</template>
@@ -83,21 +87,31 @@
class="flex flex-col space-y-4 p-5"
>
<div class="flex items-center justify-between">
<Rating v-model="evaluation.rating" :label="__('Rating')" />
<Rating
v-model="evaluation.rating"
:label="__('Rating')"
:disabled="!userIsEvaluator()"
/>
<FormControl
type="select"
:options="statusOptions"
v-model="evaluation.status"
:label="__('Status')"
class="w-1/2"
:disabled="!userIsEvaluator()"
/>
</div>
<Textarea
v-model="evaluation.summary"
:label="__('Summary')"
:rows="7"
:disabled="!userIsEvaluator()"
/>
<Button variant="solid" @click="saveEvaluation()">
<Button
v-if="userIsEvaluator()"
variant="solid"
@click="saveEvaluation()"
>
{{ __('Save') }}
</Button>
</div>
@@ -106,11 +120,13 @@
type="checkbox"
v-model="certificate.published"
:label="__('Published')"
:disabled="!userIsEvaluator()"
/>
<Link
v-model="certificate.template"
:label="__('Template')"
doctype="Print Format"
:disabled="!userIsEvaluator()"
:filters="{
doc_type: 'LMS Certificate',
}"
@@ -118,14 +134,20 @@
<FormControl
type="date"
v-model="certificate.issue_date"
:disabled="!userIsEvaluator()"
:label="__('Issue Date')"
/>
<FormControl
type="date"
v-model="certificate.expiry_date"
:disabled="!userIsEvaluator()"
:label="__('Expiry Date')"
/>
<Button variant="solid" @click="saveCertificate()">
<Button
v-if="userIsEvaluator()"
variant="solid"
@click="saveCertificate()"
>
{{ __('Save') }}
</Button>
</div>
@@ -163,6 +185,7 @@ import Rating from '@/components/Controls/Rating.vue'
import Link from '@/components/Controls/Link.vue'
const show = defineModel()
const user = inject('$user')
const dayjs = inject('$dayjs')
const tabIndex = ref(0)
const showCertification = ref(false)
@@ -175,9 +198,18 @@ const props = defineProps({
})
const evaluation = reactive({})
const certificate = reactive({})
watch(user, () => {
if (userIsEvaluator()) {
defaultTemplate.reload()
}
})
const userIsEvaluator = () => {
return user.data && user.data.name == props.event.evaluator
}
const defaultTemplate = createResource({
url: 'frappe.client.get_value',
makeParams(values) {
@@ -190,7 +222,6 @@ const defaultTemplate = createResource({
},
}
},
auto: true,
onSuccess(data) {
certificate.template = data.value
},
@@ -18,7 +18,7 @@
>
<template #body-content>
<div class="flex flex-col gap-4">
<p>
<p class="text-ink-gray-9">
{{
__(
'Submit your resume to proceed with your application for this position. Upon submission, it will be shared with the job poster.'
@@ -51,7 +51,7 @@
<FileText class="h-5 w-5 stroke-1.5 text-ink-gray-7" />
</div>
<div class="flex flex-col">
<span>
<span class="text-ink-gray-9">
{{ resume.file_name }}
</span>
<span class="text-sm text-ink-gray-4 mt-1">
+2 -1
View File
@@ -126,7 +126,7 @@ import {
Button,
toast,
} from 'frappe-ui'
import { computed, watch, reactive, ref, inject } from 'vue'
import { watch, reactive, ref, inject } from 'vue'
import Link from '@/components/Controls/Link.vue'
import { useOnboarding } from 'frappe-ui/frappe'
@@ -141,6 +141,7 @@ const existingQuestion = reactive({
question: '',
marks: 1,
})
const question = reactive({
question: '',
type: 'Choices',
@@ -23,7 +23,9 @@
</div>
<div v-if="currentTab" class="mt-4">
<div class="grid grid-cols-[55%,40%] gap-5">
<div class="space-y-5 border rounded-md p-2 pt-4">
<div
class="space-y-5 border rounded-md p-2 pt-4 h-[70vh] overflow-y-auto"
>
<div class="grid grid-cols-[70%,30%] text-sm text-ink-gray-5">
<div class="px-4">
{{ __('Member') }}
@@ -59,7 +61,7 @@
</div>
</div>
<div class="text-center text-sm">
{{ convertToMinutes(row.watch_time) }}
{{ formatTimestamp(row.watch_time) }}
</div>
</div>
</router-link>
@@ -69,7 +71,7 @@
<NumberChart
class="border rounded-md"
:config="{
title: __('Average Watch Time (minutes)'),
title: __('Average Watch Time'),
value: averageWatchTime,
}"
/>
@@ -97,7 +99,7 @@ import {
TabButtons,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import { enablePlyr, convertToMinutes } from '@/utils'
import { enablePlyr, formatTimestamp } from '@/utils'
import VideoBlock from '@/components/VideoBlock.vue'
const show = defineModel<boolean | undefined>()
@@ -185,7 +187,7 @@ const averageWatchTime = computed(() => {
totalWatchTime += parseFloat(item.watch_time)
})
return convertToMinutes(totalWatchTime / currentTabData.value.length)
return formatTimestamp(totalWatchTime / currentTabData.value.length)
})
const currentTabData = computed(() => {
+3 -3
View File
@@ -1,13 +1,13 @@
<template>
<div class="border rounded-md w-1/3 mx-auto my-32">
<div class="border-b px-5 py-3 font-medium">
<div class="border-b px-5 py-3 font-medium text-ink-gray-9">
<span
class="inline-flex items-center before:bg-surface-red-5 before:w-2 before:h-2 before:rounded-md before:mr-2"
></span>
{{ __('Not Permitted') }}
</div>
<div v-if="user.data" class="px-5 py-3">
<div>
<div class="text-ink-gray-7">
{{ __('You do not have permission to access this page.') }}
</div>
<router-link
@@ -21,7 +21,7 @@
</router-link>
</div>
<div class="px-5 py-3">
<div>
<div class="text-ink-gray-7">
{{ __('Please login to access this page.') }}
</div>
<Button @click="redirectToLogin()" class="mt-4">
+2 -2
View File
@@ -1,13 +1,13 @@
<template>
<div class="text-base border rounded-md w-1/3 mx-auto my-32">
<div class="border-b px-5 py-3 font-medium">
<div class="border-b px-5 py-3 font-medium text-ink-gray-9">
<span
class="inline-flex items-center before:bg-surface-red-5 before:w-2 before:h-2 before:rounded-md before:mr-2"
></span>
{{ __(title) }}
</div>
<div class="px-5 py-3">
<div class="mb-4 leading-6">
<div class="mb-4 leading-6 text-ink-gray-7">
{{ __(text) }}
</div>
<Button variant="solid" class="w-full" @click="redirect()">
@@ -0,0 +1,241 @@
<template>
<div
class="text-sm absolute bg-white border rounded-md z-10 w-44"
:style="{
display: top > 0 ? 'block' : 'none',
top: top + 'px',
left: left + 'px',
}"
>
<div class="space-y-2 py-2">
<div class="text-xs text-ink-gray-5 font-medium px-3">
{{ __('Highlight') }}
</div>
<div class="">
<div
v-for="color in colors"
class="flex items-center space-x-2 px-3 py-2 cursor-pointer hover:bg-surface-gray-2"
@click="saveHighLight(color)"
>
<span
class="size-3 rounded-full"
:style="{
backgroundColor: theme.backgroundColor[color.toLowerCase()][400],
}"
></span>
<span>
{{ __(color) }}
</span>
</div>
</div>
</div>
<div class="border-t">
<div
@click="addToNotes()"
class="flex items-center space-x-2 hover:bg-surface-gray-2 cursor-pointer rounded-b-md py-2 px-3"
>
<NotepadText class="size-3 stroke-1.5" />
<span>
{{ __('Add to Notes') }}
</span>
</div>
<div
v-if="highlightExists()"
@click="deleteHighlight"
class="flex items-center space-x-2 hover:bg-surface-gray-2 cursor-pointer rounded-b-md py-2 px-3"
>
<Trash2 class="size-3 stroke-1.5" />
<span>
{{ __('Remove Highlight') }}
</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, inject, ref, watch } from 'vue'
import { NotepadText, Trash2 } from 'lucide-vue-next'
import { theme } from '@/utils/theme'
import type { Note, Notes } from '@/components/Notes/types'
import { blockQuotesClick, highlightText } from '@/utils'
const user = inject<any>('$user')
const show = defineModel()
const notes = defineModel<Notes>('notes')
const top = ref(0)
const left = ref(0)
const currentSelection = ref<Selection | null>(null)
const selectedText = ref('')
const emit = defineEmits<{
(e: 'updateNotes'): void
}>()
const props = defineProps<{
lesson: string
}>()
watch(show, () => {
if (!show.value) {
return resetMenuPosition()
}
currentSelection.value = window.getSelection()
if (!currentSelection.value?.toString()) {
return resetMenuPosition()
}
updateMenuPosition()
})
const updateMenuPosition = () => {
selectedText.value = currentSelection.value?.toString() || ''
const range = currentSelection.value?.getRangeAt(0)
const rect = range?.getBoundingClientRect()
if (!rect) return
const offsetY = window.scrollY
const offsetX = window.scrollX
top.value = Math.floor(rect.top + offsetY - 40)
left.value = Math.floor(rect.right + offsetX + 10)
}
const resetMenuPosition = () => {
top.value = 0
left.value = 0
}
const colors = computed(() => {
return ['Red', 'Blue', 'Green', 'Yellow', 'Purple']
})
const highlightExists = () => {
return notes.value?.data?.some(
(note: Note) => note.highlighted_text === selectedText.value
)
}
const saveHighLight = (color: string) => {
if (!selectedText.value) return
notes.value?.insert.submit(
{
lesson: props.lesson,
member: user?.data?.name,
highlighted_text: selectedText.value,
color: color,
name: '',
},
{
onSuccess(data: Note) {
highlightText(data)
resetStates()
emit('updateNotes')
},
onError(err: any) {
console.error('Error saving highlight:', err)
resetStates()
},
}
)
}
const deleteHighlight = () => {
let notesToDelete = notes.value?.data.find(
(note: Note) => note.highlighted_text === selectedText.value
)
if (!notesToDelete) return
notes.value?.delete.submit(notesToDelete.name, {
onSuccess() {
resetStates()
document.querySelectorAll('.highlighted-text').forEach((el) => {
const element = el as HTMLElement
if (element.dataset.name === notesToDelete.name) {
element.style.backgroundColor = 'transparent'
}
})
},
onError(err: any) {
console.error('Error deleting highlight:', err)
resetStates()
},
})
}
const addToNotes = () => {
if (!selectedText.value) return
let noteToUpdate = notes.value?.data.find((note: Note) => {
return !note.highlighted_text && note.note !== ''
})
if (!noteToUpdate) {
createNote()
} else {
updateNote(noteToUpdate)
}
}
const createNote = () => {
notes.value?.insert.submit(
{
lesson: props.lesson,
member: user?.data?.name,
note: `<blockquote><p>${selectedText.value}</p></blockquote><br>`,
color: 'Yellow',
name: '',
},
{
onSuccess(data: Note) {
emit('updateNotes')
setTimeout(() => {
scrollToText(selectedText.value)
blockQuotesClick()
resetStates()
}, 100)
},
onError(err: any) {
console.error('Error creating note:', err)
resetStates()
},
}
)
}
const updateNote = (noteToUpdate: Note) => {
notes.value?.setValue.submit(
{
name: noteToUpdate.name,
note: `${noteToUpdate.note}\n\n<blockquote><p>${selectedText.value}</p></blockquote><br>`,
},
{
onSuccess(data: Note) {
emit('updateNotes')
setTimeout(() => {
scrollToText(selectedText.value)
blockQuotesClick()
resetStates()
}, 100)
},
onError(err: any) {
console.error('Error updating note:', err)
resetStates()
},
}
)
}
const scrollToText = (text: string) => {
const elements = document.querySelectorAll('blockquote p')
Array.from(elements).forEach((el) => {
const element = el as HTMLElement
if (element.textContent?.toLowerCase().includes(text.toLowerCase())) {
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
}
})
}
const resetStates = () => {
selectedText.value = ''
show.value = false
resetMenuPosition()
}
</script>
+115
View File
@@ -0,0 +1,115 @@
<template>
<div class="text-lg font-semibold mb-4 text-ink-gray-9">
{{ __('My Notes') }}
</div>
<TextEditor
:content="note"
:placeholder="__('Make notes for quick revision. Press / for menu.')"
@change="(val: string) => updateNoteText(val)"
:editable="true"
editorClass="prose prose-sm min-h-[200px] max-w-none"
/>
</template>
<script setup lang="ts">
import { TextEditor } from 'frappe-ui'
import { useDebounceFn } from '@vueuse/core'
import { inject, ref, onMounted, watch } from 'vue'
import type { Note, Notes } from '@/components/Notes/types'
import { blockQuotesClick } from '@/utils/'
const note = ref<string | null>(null)
const currentNoteName = ref<string | null>(null)
const user = inject<any>('$user')
const notes = defineModel<Notes>('notes')
const emit = defineEmits<{
(e: 'updateNotes'): void
}>()
const props = defineProps<{
lesson: string
}>()
onMounted(() => {
updateCurrentNote()
})
watch(
() => notes.value?.data,
() => {
updateCurrentNote()
blockQuotesClick()
}
)
const updateCurrentNote = () => {
const currentNote = notes.value?.data?.filter((row: Note) => {
return !row.highlighted_text && row.note !== ''
})
if (currentNote?.length === 0) {
note.value = null
currentNoteName.value = null
return
} else if (currentNote && currentNote.length > 0) {
currentNoteName.value = currentNote[0].name
note.value = currentNote[0].note || null
}
}
const updateNoteText = (val: string) => {
note.value = val
debouncedSave()
}
const debouncedSave = useDebounceFn(() => {
saveNotes()
}, 2000)
const saveNotes = () => {
if (currentNoteName.value) {
updateNote()
} else {
createNote()
}
}
const createNote = () => {
notes.value?.insert.submit(
{
lesson: props.lesson,
member: user?.data?.name,
note: note.value,
color: 'Yellow',
name: '',
},
{
onSuccess(data: Note) {
currentNoteName.value = data.name || null
emit('updateNotes')
},
onError(err: any) {
console.error('Error creating note:', err)
},
}
)
}
const updateNote = () => {
if (!currentNoteName.value) return
notes.value?.setValue.submit(
{
name: currentNoteName.value,
lesson: props.lesson,
member: user?.data?.name,
note: note.value,
},
{
onSuccess(data: Note) {
emit('updateNotes')
},
onError(err: any) {
console.error('Error updating note:', err)
},
}
)
}
</script>
+32
View File
@@ -0,0 +1,32 @@
export type Note = {
highlighted_text?: string
color?: string
name: string
note?: string | null
lesson?: string
member?: string
}
export type Notes = {
data: Note[]
reload: () => void
insert: {
submit: (
data: Note,
options: { onSuccess: (data: Note) => void; onError: (err: any) => void }
) => void
}
setValue: {
submit: (
data: Note,
options: { onSuccess: (data: Note) => void; onError: (err: any) => void }
) => void
},
delete: {
submit: (
data: Note | string,
options?: { onSuccess: () => void; onError: (err: any) => void }
) => void
}
}
+16 -6
View File
@@ -55,8 +55,8 @@
<div v-if="quiz.data.duration" class="flex flex-col space-x-1 my-4">
<div class="mb-2">
<span class=""> {{ __('Time') }}: </span>
<span class="font-semibold">
<span class="text-ink-gray-9"> {{ __('Time') }}: </span>
<span class="font-semibold text-ink-gray-9">
{{ formatTimer(timer) }}
</span>
</div>
@@ -165,14 +165,14 @@
</div>
</div>
<span
class="ml-2"
class="ml-2 text-ink-gray-9"
v-html="questionDetails.data[`option_${index}`]"
>
</span>
</label>
<div
v-if="questionDetails.data[`explanation_${index}`]"
class="mt-2 text-xs"
class="mt-2 text-xs text-ink-gray-7"
v-show="showAnswers.length"
>
{{ questionDetails.data[`explanation_${index}`] }}
@@ -260,7 +260,7 @@
)
}}
</div>
<div v-else>
<div v-else class="text-ink-gray-7">
{{
__(
'You got {0}% correct answers with a score of {1} out of {2}'
@@ -574,7 +574,17 @@ const addToLocalStorage = () => {
return answer != undefined
}),
}
quizData ? quizData.push(questionData) : (quizData = [questionData])
if (quizData) {
let existingQuestion = quizData.find(
(q) => q.question_name == questionData.question_name
)
if (!existingQuestion) {
quizData.push(questionData)
}
} else {
quizData = [questionData]
}
localStorage.setItem(quiz.data.title, JSON.stringify(quizData))
}
@@ -69,9 +69,12 @@ const update = () => {
let imageFields = ['favicon', 'banner_image']
props.fields.forEach((f) => {
if (imageFields.includes(f.name)) {
fieldsToSave[f.name] = f.value ? f.value.file_url : null
fieldsToSave[f.name] =
branding.data[f.name] && branding.data[f.name].file_url
? branding.data[f.name].file_url
: null
} else {
fieldsToSave[f.name] = f.value
fieldsToSave[f.name] = branding.data[f.name]
}
})
@@ -141,9 +141,6 @@ const props = defineProps({
type: String,
default: '',
},
show: {
type: Boolean,
},
})
const evaluators = createListResource({
+1 -12
View File
@@ -117,14 +117,7 @@
</Dialog>
</template>
<script setup lang="ts">
import {
Avatar,
Badge,
Button,
createResource,
Dialog,
FormControl,
} from 'frappe-ui'
import { Avatar, Button, createResource, Dialog, FormControl } from 'frappe-ui'
import { useRouter } from 'vue-router'
import { ref, watch, reactive, inject } from 'vue'
import { RefreshCw, Plus, Search, Shield } from 'lucide-vue-next'
@@ -146,7 +139,6 @@ const start = ref(0)
const memberList = ref<Member[]>([])
const hasNextPage = ref(false)
const showForm = ref(false)
const dayjs = inject('$dayjs')
const user = inject<User | null>('$user')
const { updateOnboardingStep } = useOnboarding('learning')
@@ -164,9 +156,6 @@ const props = defineProps({
type: String,
default: '',
},
show: {
type: Boolean,
},
})
const members = createResource({
@@ -0,0 +1,233 @@
<template>
<Dialog
v-model="show"
:options="{
title:
gatewayID === 'new'
? __('New Payment Gateway')
: __('Edit Payment Gateway'),
size: '3xl',
}"
>
<template #body-content>
<SettingFields
v-if="gatewayID != 'new' && paymentGateway.data"
:fields="paymentGateway.data.fields"
:data="paymentGateway.data.data"
class="pt-5 my-0"
/>
<div v-else>
<FormControl
v-model="newGateway"
:label="__('Select Payment Gateway')"
type="select"
:options="allGatewayOptions"
:required="true"
/>
<SettingFields
v-if="newGateway"
:fields="newGatewayFields"
:data="newGatewayData"
class="pt-5 my-0"
/>
</div>
</template>
<template #actions="{ close }">
<div class="pb-5 float-right">
<Button variant="solid" @click="saveSettings(close)">
{{ __('Save') }}
</Button>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import {
Button,
call,
createListResource,
createResource,
Dialog,
FormControl,
} from 'frappe-ui'
import { computed, ref, watch } from 'vue'
import SettingFields from '@/components/Settings/SettingFields.vue'
const show = defineModel<boolean>({ required: true, default: false })
const paymentGateways = defineModel<any>('paymentGateways')
const newGateway = ref(null)
const newGatewayFields = ref([])
const newGatewayData = ref<Record<string, any>>({})
const props = defineProps<{
gatewayID: string | null
}>()
const paymentGateway = createResource({
url: 'lms.lms.api.get_payment_gateway_details',
makeParams(values: any) {
return {
payment_gateway: props.gatewayID,
}
},
transform(data: any) {
arrangeFields(data.fields)
return data
},
})
const allGateways = createListResource({
doctype: 'DocType',
filters: {
module: 'Payment Gateways',
},
fields: ['name', 'issingle'],
})
const gatewayFields = createResource({
url: 'lms.lms.api.get_new_gateway_fields',
makeParams(values: any) {
return {
doctype: values.doctype,
}
},
})
const arrangeFields = (fields: any[]) => {
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',
})
}
watch(
() => props.gatewayID,
() => {
if (props.gatewayID && props.gatewayID !== 'new') {
paymentGateway.reload()
} else if (props.gatewayID == 'new') {
allGateways.reload()
}
}
)
const getNewGateway = () => {
return allGateways.data?.find((gateway: any) =>
gateway.name.includes(newGateway.value)
)
}
watch(newGateway, () => {
let gatewayDoc = getNewGateway()
gatewayFields.reload({ doctype: gatewayDoc.name }).then(() => {
let fields = gatewayFields.data || []
arrangeFields(fields)
newGatewayFields.value = fields
prepareGatewayData()
})
})
const saveSettings = (close: () => void) => {
if (props.gatewayID === 'new') {
saveNewGateway(close)
} else {
saveExistingGateway(
paymentGateway.data.doctype,
paymentGateway.data.docname,
close
)
}
}
const saveNewGateway = (close: () => void) => {
let gatewayDoc = getNewGateway()
if (gatewayDoc.issingle) {
saveExistingGateway(gatewayDoc.name, gatewayDoc.name, close)
} else {
call('frappe.client.insert', {
doc: {
doctype: gatewayDoc.name,
...newGatewayData.value,
},
}).then((data: any) => {
paymentGateways.value.reload()
close()
})
}
}
const saveExistingGateway = (
doctype: string,
docname: string,
close: () => void
) => {
call('frappe.client.set_value', {
doctype: doctype,
name: docname,
fieldname: getGatewayFields(),
}).then(() => {
paymentGateways.value?.reload()
close()
})
}
const getGatewayFields = () => {
let data =
props.gatewayID == 'new' ? newGatewayData.value : paymentGateway.data.data
return Object.keys(data).reduce((fields: any, key: string) => {
if (data[key] && typeof data[key] === 'object') {
fields[key] = data[key].file_url
} else {
fields[key] = data[key]
}
return fields
}, {})
}
const createGatewayRecord = (gatewayDoc: any, data: any = {}) => {
call('frappe.client.insert', {
doc: {
doctype: 'Payment Gateway',
gateway: newGateway.value,
gateway_controller: gatewayDoc.issingle ? '' : gatewayDoc.name,
gateway_settings: gatewayDoc.issingle ? '' : data.name,
},
}).then(() => {
paymentGateways.value?.reload()
})
}
const allGatewayOptions = computed(() => {
let options: string[] = []
let gatewayList = allGateways.data?.map((gateway: any) => gateway.name) || []
gatewayList.forEach((gateway: any) => {
let gatewayName = gateway.split(' ')[0]
let existingGateways =
paymentGateways.value?.data?.map((pg: any) => pg.name) || []
if (
!options.includes(gatewayName) &&
!existingGateways.includes(gatewayName)
) {
options.push(gatewayName)
}
})
return options.map((gateway: string) => ({ label: gateway, value: gateway }))
})
const prepareGatewayData = () => {
newGatewayData.value = {}
if (newGatewayFields.value.length) {
newGatewayFields.value.forEach((field: any) => {
newGatewayData.value[field.fieldname] = field.default || ''
})
}
}
</script>
@@ -0,0 +1,140 @@
<template>
<div class="flex min-h-0 flex-col text-base">
<div class="flex items-center justify-between mb-5">
<div>
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
{{ __(label) }}
</div>
<div class="text-ink-gray-6 leading-5">
{{ __(description) }}
</div>
</div>
<Button @click="openForm('new')">
<template #prefix>
<Plus class="h-3 w-3 stroke-1.5" />
</template>
{{ __('New') }}
</Button>
</div>
<div v-if="paymentGateways.data?.length" class="overflow-y-scroll">
<ListView
:columns="columns"
:rows="paymentGateways.data"
row-key="name"
:options="{
showTooltip: false,
onRowClick: (row) => {
openForm(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 }">
<FeatherIcon
v-if="item.icon"
:name="item.icon"
class="h-4 w-4 stroke-1.5"
/>
</template>
</ListHeaderItem>
</ListHeader>
<ListRows>
<ListRow :row="row" v-for="row in paymentGateways.data">
<template #default="{ column, item }">
<ListRowItem :item="row[column.key]" :align="column.align">
<div v-if="column.key == 'enabled'">
<Badge v-if="row[column.key]" theme="green">
{{ __('Enabled') }}
</Badge>
<Badge v-else theme="gray">
{{ __('Disabled') }}
</Badge>
</div>
<div v-else 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="removeAccount(selections, unselectAll)"
>
<Trash2 class="h-4 w-4 stroke-1.5" />
</Button>
</div>
</template>
</ListSelectBanner>
</ListView>
</div>
</div>
<PaymentGatewayDetails
v-model="showForm"
:gatewayID="currentGateway"
v-model:paymentGateways="paymentGateways"
/>
</template>
<script setup>
import {
Badge,
Button,
createListResource,
FeatherIcon,
ListView,
ListHeader,
ListHeaderItem,
ListRows,
ListRow,
ListRowItem,
ListSelectBanner,
} from 'frappe-ui'
import { computed, ref } from 'vue'
import { Plus, Trash2 } from 'lucide-vue-next'
import PaymentGatewayDetails from '@/components/Settings/PaymentGatewayDetails.vue'
const showForm = ref(false)
const currentGateway = ref(null)
const props = defineProps({
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
})
const paymentGateways = createListResource({
doctype: 'Payment Gateway',
fields: ['name', 'gateway_settings', 'gateway_controller'],
auto: true,
orderBy: 'modified desc',
})
const openForm = (gatewayID) => {
currentGateway.value = gatewayID
showForm.value = true
}
const columns = computed(() => {
return [
{
label: __('Gateway'),
key: 'name',
icon: 'credit-card',
},
]
})
</script>
@@ -1,128 +0,0 @@
<template>
<div class="flex flex-col h-full">
<div class="flex items-center justify-between">
<div class="text-xl font-semibold mb-1 text-ink-gray-9">
{{ label }}
</div>
<!-- <Badge
v-if="isDirty"
:label="__('Not Saved')"
variant="subtle"
theme="orange"
/> -->
</div>
<div class="overflow-y-scroll">
<div class="flex flex-col divide-y">
<SettingFields :fields="fields" :data="data.doc" />
<SettingFields
v-if="paymentGateway.data"
:fields="paymentGateway.data.fields"
:data="paymentGateway.data.data"
class="pt-5 my-0"
/>
</div>
</div>
<div class="flex flex-row-reverse mt-auto">
<Button variant="solid" @click="update">
{{ __('Update') }}
</Button>
</div>
</div>
</template>
<script setup>
import SettingFields from '@/components/Settings/SettingFields.vue'
import { createResource, Badge, Button } from 'frappe-ui'
import { watch } from 'vue'
const props = defineProps({
label: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
data: {
type: Object,
required: true,
},
fields: {
type: Array,
required: true,
},
})
const paymentGateway = createResource({
url: 'lms.lms.api.get_payment_gateway_details',
makeParams(values) {
return {
payment_gateway: props.data.doc.payment_gateway,
}
},
transform(data) {
arrangeFields(data.fields)
return data
},
auto: true,
})
const arrangeFields = (fields) => {
fields = fields.sort((a, b) => {
if (a.type === 'Upload' && b.type !== 'Upload') {
return 1
} else if (a.type !== 'Upload' && b.type === 'Upload') {
return -1
}
return 0
})
fields.splice(3, 0, {
type: 'Column Break',
})
}
const saveSettings = createResource({
url: 'frappe.client.set_value',
makeParams(values) {
let fields = {}
Object.keys(paymentGateway.data.data).forEach((key) => {
if (
paymentGateway.data.data[key] &&
typeof paymentGateway.data.data[key] === 'object'
) {
fields[key] = paymentGateway.data.data[key].file_url
} else {
fields[key] = paymentGateway.data.data[key]
}
})
return {
doctype: paymentGateway.data.doctype,
name: paymentGateway.data.docname,
fieldname: fields,
}
},
auto: false,
onSuccess(data) {
paymentGateway.reload()
},
})
const update = () => {
props.fields.forEach((f) => {
if (f.type != 'Column Break') {
props.data.doc[f.name] = f.value
}
})
props.data.save.submit()
saveSettings.submit()
}
watch(
() => props.data.doc.payment_gateway,
() => {
paymentGateway.reload()
}
)
</script>

Some files were not shown because too many files have changed in this diff Show More