IU Table Organizer

A script to order the lectures table according to weekdays on the Islamic University website

  1. // ==UserScript==
  2. // @name IU Table Organizer
  3. // @description A script to order the lectures table according to weekdays on the Islamic University website
  4. // @name:en IU Table Organizer
  5. // @description:en A script to order the lectures table according to weekdays on the Islamic University website
  6. // @name:ar منظم جدول الجامعة الاسلامية
  7. // @description:ar اضافة لتعديل مظهر الجدول بالجامعة الاسلامية الى جدول مرتب تبعا لايام الاسبوع بضغطة زر
  8. // @include https://eduportal.iu.edu.sa/iu/ui/student/homeIndex.faces
  9. // @include https://eduportal.iu.edu.sa/iu/ui/student/*/*/*
  10. // @include http://eduportal.iu.edu.sa/iu/ui/student/*
  11. // @include https://eduportal.iu.edu.sa/iu/ui/student/student_schedule/index/studentScheduleIndex.faces
  12. // @version 4.3
  13. // @icon https://www.google.com/s2/favicons?domain=sso.iu.edu.sa
  14. // @namespace https://greasyfork.org/users/814159
  15. // @icon https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/table-icon.png
  16. // @license Mozilla Public License 2.0
  17. // @grant GM_addStyle
  18. // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js
  19. // ==/UserScript==
  20.  
  21. (function() {{
  22. 'use strict';
  23. // Add styles
  24. GM_addStyle(`#newTable {
  25. border-collapse: collapse;
  26. margin: 0;
  27. font-size: 0.9em;
  28. border-radius: 8px;
  29. overflow: hidden;
  30. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  31. background: white;
  32. width: fit-content;
  33. min-width: auto;
  34. table-layout: fixed;
  35. display: table;
  36. direction: rtl;
  37. }
  38.  
  39. /* Add styles for the table wrapper */
  40. .table-wrapper {
  41. width: 100%;
  42. max-width: 1100px;
  43. margin: 0 auto;
  44. overflow: hidden;
  45. padding: 0;
  46. display: flex;
  47. justify-content: center;
  48. border-radius: 8px;
  49. }
  50.  
  51. #newTable thead tr {
  52. background: linear-gradient(135deg, #1a237e 0%, #0d47a1 100%);
  53. color: #ffffff;
  54. text-align: center;
  55. font-weight: bold;
  56. height: 60px;
  57. position: relative;
  58. box-shadow: 0 3px 6px rgba(0,0,0,0.1);
  59. font-feature-settings: "kern", "liga", "clig", "calt", "arab";
  60. -webkit-font-feature-settings: "kern", "liga", "clig", "calt", "arab";
  61. font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
  62. }
  63.  
  64. #newTable th {
  65. padding: 1px;
  66. position: relative;
  67. font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
  68. border-left: 1px solid rgba(255,255,255,0.15);
  69. transition: background-color 0.3s ease;
  70. width: 220px; /* 1100px / 5 columns */
  71. }
  72.  
  73. #newTable th:last-child {
  74. border-left: none;
  75. }
  76.  
  77. #newTable th::after {
  78. content: '';
  79. position: absolute;
  80. bottom: 0;
  81. left: 0;
  82. right: 0;
  83. height: 3px;
  84. background: rgba(255,255,255,0.1);
  85. transform: scaleX(0.7);
  86. transition: transform 0.3s ease;
  87. }
  88.  
  89. #newTable th:hover::after {
  90. transform: scaleX(1);
  91. }
  92.  
  93. #newTable th .day-name {
  94. font-size: 1.3em;
  95. font-weight: 600;
  96. margin-bottom: 2px;
  97. text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
  98. text-align: center;
  99. direction: rtl;
  100. font-family: "Noto Kufi Arabic", "Segoe UI", sans-serif;
  101. font-feature-settings: "kern", "liga", "clig", "calt";
  102. -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
  103. }
  104.  
  105. #newTable td {
  106. padding: 1px;
  107. text-align: center;
  108. vertical-align: middle;
  109. height: auto;
  110. width: 220px; /* 1100px / 5 columns */
  111. font-size: 0.85em;
  112. direction: rtl;
  113. }
  114.  
  115. #newTable td:empty {
  116. padding: 0;
  117. height: 0;
  118. }
  119.  
  120. #newTable tbody tr {
  121. border-bottom: 1px solid #e0e0e0;
  122. transition: background-color 0.3s ease;
  123. }
  124.  
  125. #newTable tbody tr:hover {
  126. background-color: #f5f5f5;
  127. }
  128.  
  129. .break-cell {
  130. background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
  131. color: #424242;
  132. font-style: italic;
  133. padding: 4px;
  134. border-radius: 6px;
  135. margin: 1px;
  136. font-size: 1.1em;
  137. box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  138. text-align: center;
  139. border: 1px solid #e0e0e0;
  140. display: inline-block;
  141. box-sizing: border-box;
  142. width: fit-content;
  143. margin: 0 auto;
  144. }
  145.  
  146. .break-content {
  147. display: inline-block;
  148. white-space: nowrap;
  149. padding: 0 8px;
  150. }
  151.  
  152. .lecture-cell {
  153. border-left: 4px solid;
  154. padding: 4px;
  155. background: #fff;
  156. border-radius: 6px;
  157. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  158. margin: 1px;
  159. transition: all 0.2s ease;
  160. display: block;
  161. box-sizing: border-box;
  162. }
  163.  
  164. .lecture-cell:hover {
  165. transform: translateX(-2px);
  166. }
  167.  
  168. .lecture-cell strong {
  169. display: block;
  170. margin-bottom: 1px;
  171. font-size: 0.95em;
  172. }
  173.  
  174. .lecture-cell div {
  175. margin: 0px;
  176. line-height: 1.15;
  177. }
  178.  
  179. .lecture-cell > div {
  180. margin-bottom: 2px;
  181. }
  182.  
  183. .lecture-cell .lecture-hall {
  184. display: block;
  185. background: #e8f5e9;
  186. padding: 4px 8px;
  187. border-radius: 6px;
  188. font-size: 1.1em;
  189. color: #2e7d32;
  190. margin-top: 5px;
  191. font-weight: 500;
  192. border: 1px solid #c8e6c9;
  193. text-align: center;
  194. }
  195.  
  196. /* Update grid layout margins and gaps */
  197. .lecture-cell > div > div {
  198. display: grid;
  199. grid-template-columns: 1fr 1fr;
  200. gap: 6px;
  201. margin-top: 6px;
  202. }
  203.  
  204. .schedule-summary {
  205. background: linear-gradient(45deg, #f5f5f5, #fff);
  206. border-radius: 8px;
  207. padding: 10px;
  208. margin: 7px auto 0;
  209. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  210. border: 1px solid #e0e0e0;
  211. width: 100%;
  212. box-sizing: border-box;
  213. min-width: auto;
  214. }
  215.  
  216. .schedule-summary > div {
  217. display: flex;
  218. align-items: center;
  219. justify-content: center;
  220. gap: 20px;
  221. flex-wrap: wrap;
  222. width: 100%;
  223. box-sizing: border-box;
  224. }
  225.  
  226. .schedule-summary div > div {
  227. flex: 0 1 auto;
  228. min-width: fit-content;
  229. white-space: nowrap;
  230. }
  231.  
  232. .control-buttons {
  233. display: flex;
  234. gap: 8px;
  235. align-items: center;
  236. flex-wrap: wrap;
  237. }
  238.  
  239. .control-button {
  240. display: inline-flex;
  241. align-items: center;
  242. justify-content: center;
  243. padding: 10px 20px;
  244. border-radius: 12px;
  245. font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
  246. font-size: 0.95em;
  247. font-weight: 600;
  248. text-align: center;
  249. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  250. border: none;
  251. background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
  252. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25),
  253. 0 4px 8px rgba(0, 0, 0, 0.15),
  254. inset 0 2px 4px rgba(255, 255, 255, 0.1);
  255. text-decoration: none;
  256. margin: 4px 8px;
  257. min-width: 120px;
  258. white-space: nowrap;
  259. line-height: 1.5;
  260. cursor: pointer;
  261. color: white;
  262. position: relative;
  263. overflow: visible;
  264. }
  265.  
  266. .control-button::before {
  267. content: '';
  268. position: absolute;
  269. top: 0;
  270. left: 0;
  271. width: 100%;
  272. height: 100%;
  273. background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
  274. opacity: 0;
  275. transition: opacity 0.3s ease;
  276. }
  277.  
  278. .control-button:hover {
  279. transform: translateY(-2px) scale(1.02);
  280. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35),
  281. 0 8px 16px rgba(0, 0, 0, 0.25),
  282. inset 0 2px 4px rgba(255, 255, 255, 0.15);
  283. filter: brightness(1.1);
  284. }
  285.  
  286. .control-button:hover::before {
  287. opacity: 1;
  288. }
  289.  
  290. .control-button:active {
  291. transform: translateY(1px) scale(0.98);
  292. box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
  293. inset 0 2px 4px rgba(0, 0, 0, 0.1);
  294. }
  295.  
  296. /* Theme buttons specific styles */
  297. .theme-btn, #ramadanBtn {
  298. min-width: 100px;
  299. backdrop-filter: blur(8px);
  300. position: relative;
  301. padding-right: 3rem;
  302. }
  303.  
  304. .theme-btn::after, #ramadanBtn::after {
  305. content: '';
  306. position: absolute;
  307. right: 0.75rem;
  308. top: 50%;
  309. transform: translateY(-50%);
  310. width: 20px;
  311. height: 10px;
  312. border-radius: 2px;
  313. background: #2a2a2a;
  314. border: 1px solid rgba(255, 255, 255, 0.2);
  315. transition: all 0.3s ease;
  316. z-index: 1;
  317. box-shadow:
  318. inset 0 1px 2px rgba(0, 0, 0, 0.3),
  319. inset 0 -1px 2px rgba(255, 255, 255, 0.1);
  320. }
  321.  
  322. .theme-btn.active::after, #ramadanBtn.active::after {
  323. background: linear-gradient(180deg,
  324. rgba(239, 68, 68, 1) 0%,
  325. rgba(239, 68, 68, 0.8) 50%,
  326. rgba(239, 68, 68, 0.9) 100%
  327. );
  328. border: 1px solid rgba(239, 68, 68, 0.5);
  329. box-shadow:
  330. 0 0 2px rgba(239, 68, 68, 0.4),
  331. 0 0 4px rgba(239, 68, 68, 0.2),
  332. inset 0 -2px 4px rgba(0, 0, 0, 0.2),
  333. inset 0 2px 4px rgba(255, 255, 255, 0.8);
  334. animation: indicatorGlow 2s infinite;
  335. }
  336.  
  337. @keyframes indicatorGlow {
  338. 0%, 100% {
  339. background: linear-gradient(180deg,
  340. rgba(239, 68, 68, 1) 0%,
  341. rgba(239, 68, 68, 0.8) 50%,
  342. rgba(239, 68, 68, 0.9) 100%
  343. );
  344. box-shadow:
  345. 0 0 2px rgba(239, 68, 68, 0.4),
  346. 0 0 4px rgba(239, 68, 68, 0.2),
  347. inset 0 -2px 4px rgba(0, 0, 0, 0.2),
  348. inset 0 2px 4px rgba(255, 255, 255, 0.8);
  349. }
  350. 50% {
  351. background: linear-gradient(180deg,
  352. rgba(239, 68, 68, 0.95) 0%,
  353. rgba(239, 68, 68, 0.75) 50%,
  354. rgba(239, 68, 68, 0.85) 100%
  355. );
  356. box-shadow:
  357. 0 0 4px rgba(239, 68, 68, 0.6),
  358. 0 0 8px rgba(239, 68, 68, 0.4),
  359. inset 0 -2px 4px rgba(0, 0, 0, 0.2),
  360. inset 0 2px 4px rgba(255, 255, 255, 0.8);
  361. }
  362. }
  363.  
  364. /* Set permanent colors for light theme button */
  365. #lightThemeBtn {
  366. background: linear-gradient(135deg, #451a03 0%, #582402 100%);
  367. }
  368.  
  369. /* Dark theme - only modify the indicator */
  370. .theme-dark .theme-btn::after,
  371. .theme-dark #ramadanBtn::after {
  372. background: #1a1a1a;
  373. border-color: rgba(255, 255, 255, 0.1);
  374. box-shadow:
  375. inset 0 1px 3px rgba(0, 0, 0, 0.5),
  376. inset 0 -1px 2px rgba(255, 255, 255, 0.05);
  377. }
  378.  
  379. .theme-dark .theme-btn.active::after,
  380. .theme-dark #ramadanBtn.active::after {
  381. background: linear-gradient(180deg,
  382. rgba(239, 68, 68, 0.9) 0%,
  383. rgba(239, 68, 68, 0.7) 50%,
  384. rgba(239, 68, 68, 0.8) 100%
  385. );
  386. border-color: rgba(239, 68, 68, 0.3);
  387. box-shadow:
  388. 0 0 4px rgba(239, 68, 68, 0.4),
  389. 0 0 8px rgba(239, 68, 68, 0.2),
  390. inset 0 -2px 4px rgba(0, 0, 0, 0.3),
  391. inset 0 2px 4px rgba(255, 255, 255, 0.4);
  392. }
  393.  
  394. /* Remove redundant Ramadan button indicator styles */
  395. #ramadanBtn {
  396. background: linear-gradient(135deg, #5b21b6 0%, #6d28d9 100%);
  397. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25),
  398. 0 4px 8px rgba(0, 0, 0, 0.15),
  399. inset 0 2px 4px rgba(255, 255, 255, 0.1);
  400. }
  401.  
  402. #ramadanBtn::after {
  403. background: #2a1650;
  404. }
  405.  
  406. #ramadanBtn:hover {
  407. box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3),
  408. 0 8px 16px rgba(124, 58, 237, 0.2),
  409. inset 0 2px 4px rgba(255, 255, 255, 0.2);
  410. }
  411.  
  412. /* Dark theme support for control buttons */
  413. .theme-dark .control-button {
  414. background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
  415. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4),
  416. 0 4px 8px rgba(0, 0, 0, 0.3),
  417. inset 0 1px 1px rgba(255, 255, 255, 0.05);
  418. border: 1px solid rgba(255, 255, 255, 0.05);
  419. color: #e0e0ff;
  420. }
  421.  
  422. .theme-dark .control-button:hover {
  423. filter: brightness(1.1);
  424. transform: translateY(-2px) scale(1.02);
  425. }
  426.  
  427. .theme-dark .control-button:active {
  428. transform: translateY(1px) scale(0.98);
  429. }
  430.  
  431. /* Remove the light theme button override and keep only dark theme button override */
  432. .theme-dark #darkThemeBtn {
  433. background: linear-gradient(135deg, #172554 0%, #1e3a8a 100%);
  434. }
  435.  
  436. /* Add specific style for download button in both themes */
  437. #downloadButton {
  438. background: linear-gradient(135deg, #042f2e 0%, #134e4a 100%);
  439. }
  440.  
  441. .theme-dark #ramadanBtn {
  442. background: linear-gradient(135deg, #3b0764 0%, #4c1d95 100%);
  443. }
  444.  
  445. .schedule-organizer-btn {
  446. display: inline-flex;
  447. align-items: center;
  448. justify-content: center;
  449. padding: 10px 28px;
  450. background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
  451. color: white;
  452. border-radius: 12px;
  453. font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
  454. font-size: 1.1em;
  455. font-weight: 600;
  456. text-align: center;
  457. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  458. border: none;
  459. box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
  460. 0 4px 8px rgba(37, 99, 235, 0.1),
  461. inset 0 2px 4px rgba(255, 255, 255, 0.1);
  462. text-decoration: none;
  463. margin: 6px;
  464. min-width: 160px;
  465. white-space: nowrap;
  466. line-height: 1.5;
  467. cursor: pointer;
  468. position: relative;
  469. overflow: hidden;
  470. z-index: 100;
  471. visibility: visible !important;
  472. }
  473.  
  474. .schedule-organizer-btn::before {
  475. content: '';
  476. position: absolute;
  477. top: 0;
  478. left: 0;
  479. width: 100%;
  480. height: 100%;
  481. background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
  482. opacity: 0;
  483. transition: opacity 0.3s ease;
  484. }
  485.  
  486. .schedule-organizer-btn:hover {
  487. transform: translateY(-2px) scale(1.02);
  488. box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3),
  489. 0 8px 16px rgba(37, 99, 235, 0.2),
  490. inset 0 2px 4px rgba(255, 255, 255, 0.2);
  491. }
  492.  
  493. .schedule-organizer-btn:hover::before {
  494. opacity: 1;
  495. }
  496.  
  497. .schedule-organizer-btn:active {
  498. transform: translateY(1px) scale(0.98);
  499. box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
  500. inset 0 2px 4px rgba(0, 0, 0, 0.1);
  501. }
  502.  
  503. .schedule-organizer-btn.active {
  504. background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
  505. box-shadow: 0 2px 4px rgba(220, 38, 38, 0.2),
  506. 0 4px 8px rgba(220, 38, 38, 0.1),
  507. inset 0 2px 4px rgba(255, 255, 255, 0.1);
  508. }
  509.  
  510. /* Dark theme support */
  511. .theme-dark .schedule-organizer-btn {
  512. background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
  513. box-shadow: 0 2px 4px rgba(30, 64, 175, 0.3),
  514. 0 4px 8px rgba(30, 64, 175, 0.2),
  515. inset 0 2px 4px rgba(255, 255, 255, 0.05);
  516. }
  517.  
  518. .theme-dark .schedule-organizer-btn:hover {
  519. box-shadow: 0 4px 12px rgba(30, 64, 175, 0.4),
  520. 0 8px 16px rgba(30, 64, 175, 0.3),
  521. inset 0 2px 4px rgba(255, 255, 255, 0.1);
  522. }
  523.  
  524. .theme-dark .schedule-organizer-btn.active {
  525. background: linear-gradient(135deg, #b91c1c 0%, #dc2626 100%);
  526. }
  527.  
  528. .schedule-summary.theme-dark {
  529. background: linear-gradient(45deg, #1a1a2e, #232338);
  530. border-color: #2e2e4a;
  531. color: #e0e0ff;
  532. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
  533. margin: 7px auto 0;
  534. }
  535.  
  536. /* Dark theme loading overlay */
  537.  
  538. #newTable.theme-dark {
  539. background: #151b30;
  540. border-color: #2e2e4a;
  541. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
  542. margin: 0;
  543. }
  544.  
  545. #newTable.theme-dark thead tr {
  546. background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
  547. color: #ffffff;
  548. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
  549. }
  550.  
  551. #newTable.theme-dark th {
  552. border-left: 1px solid rgba(255, 255, 255, 0.2);
  553. background: transparent;
  554. }
  555.  
  556. #newTable.theme-dark th::after {
  557. background: rgba(255, 255, 255, 0.2);
  558. }
  559.  
  560. #newTable.theme-dark th .day-name {
  561. color: #ffffff;
  562. text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
  563. font-weight: 600;
  564. font-size: 18.2px;
  565. }
  566.  
  567. #newTable.theme-dark tbody tr {
  568. border-bottom: 1px solid #2e2e4a;
  569. }
  570.  
  571. #newTable.theme-dark tbody tr:hover {
  572. background-color: #1c2238;
  573. }
  574.  
  575. #newTable.theme-dark td {
  576. color: #e4e4e7;
  577. }
  578.  
  579. #newTable.theme-dark .break-cell {
  580. background: #1c2238;
  581. color: #b0b0c0;
  582. border-color: #2e2e4a;
  583. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  584. }
  585.  
  586. #newTable.theme-dark .lecture-cell {
  587. background: #1c2238;
  588. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  589. }
  590.  
  591. #newTable.theme-dark .lecture-cell strong {
  592. color: #e4e4e7;
  593. }
  594.  
  595. #newTable.theme-dark .lecture-hall {
  596. background: #1e2a4a;
  597. color: #a0b8ff;
  598. border-color: #2e3f6a;
  599. }
  600.  
  601. .download-group {
  602. display: flex;
  603. align-items: center;
  604. gap: 8px;
  605. background: var(--download-group-bg, #f0f0f0);
  606. padding: 4px;
  607. border-radius: 12px;
  608. border: 1px solid var(--download-group-border, #e0e0e0);
  609. transition: all 0.3s ease;
  610. }
  611.  
  612. .theme-dark .download-group {
  613. --download-group-bg: #1f1f1f;
  614. --download-group-border: #333;
  615. }
  616.  
  617. .download-group:hover {
  618. border-color: var(--download-group-hover-border, #ccc);
  619. box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  620. }
  621.  
  622. .theme-dark .download-group:hover {
  623. --download-group-hover-border: #444;
  624. }
  625.  
  626. .custom-checkbox-container {
  627. display: flex;
  628. align-items: center;
  629. gap: 8px;
  630. background: var(--checkbox-container-bg, #f5f5f5);
  631. padding: 8px 16px;
  632. border-radius: 8px;
  633. cursor: pointer;
  634. transition: all 0.3s ease;
  635. margin: 0;
  636. user-select: none;
  637. }
  638.  
  639. .theme-dark .custom-checkbox-container {
  640. --checkbox-container-bg: #2d2d2d;
  641. }
  642.  
  643. .custom-checkbox-container:hover {
  644. background: var(--checkbox-container-hover-bg, #e8e8e8);
  645. }
  646.  
  647. .theme-dark .custom-checkbox-container:hover {
  648. --checkbox-container-hover-bg: #363636;
  649. }
  650.  
  651. .checkbox-wrapper {
  652. position: relative;
  653. width: 18px;
  654. height: 18px;
  655. }
  656.  
  657. .checkbox-wrapper input {
  658. position: absolute;
  659. opacity: 0;
  660. cursor: pointer;
  661. height: 0;
  662. width: 0;
  663. }
  664.  
  665. .checkmark {
  666. position: absolute;
  667. top: 0;
  668. left: 0;
  669. height: 18px;
  670. width: 18px;
  671. background-color: var(--checkmark-bg, #ffffff);
  672. border: 2px solid var(--checkmark-border, #ccc);
  673. border-radius: 4px;
  674. transition: all 0.2s ease;
  675. }
  676.  
  677. .theme-dark .checkmark {
  678. --checkmark-bg: #404040;
  679. --checkmark-border: #666;
  680. }
  681.  
  682. .custom-checkbox-container:hover .checkmark {
  683. border-color: var(--checkmark-hover-border, #4CAF50);
  684. }
  685.  
  686. .theme-dark .custom-checkbox-container:hover .checkmark {
  687. --checkmark-hover-border: #888;
  688. }
  689.  
  690. .custom-checkbox-container input:checked ~ .checkmark {
  691. background-color: #4CAF50;
  692. border-color: #4CAF50;
  693. }
  694.  
  695. .custom-checkbox-container input:checked ~ .checkmark:after {
  696. content: '';
  697. position: absolute;
  698. left: 5px;
  699. top: 2px;
  700. width: 4px;
  701. height: 8px;
  702. border: solid white;
  703. border-width: 0 2px 2px 0;
  704. transform: rotate(45deg);
  705. }
  706.  
  707. /* Remove mobile-specific styles and desktop-specific styles */
  708. .control-buttons {
  709. display: flex;
  710. gap: 8px;
  711. align-items: center;
  712. flex-wrap: wrap;
  713. }
  714.  
  715. /* Mobile-specific styles */
  716. .mobile-buttons-container {
  717. width: 100%;
  718. box-sizing: border-box;
  719. direction: rtl;
  720. }
  721.  
  722. .mobile-action-button {
  723. display: block;
  724. width: 100%;
  725. padding: 12px 16px;
  726. background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
  727. color: #ffffff !important; /* Force white text color */
  728. border: none;
  729. border-radius: 10px;
  730. font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
  731. font-size: 1em;
  732. font-weight: 600;
  733. text-align: center;
  734. margin-bottom: 8px;
  735. cursor: pointer;
  736. text-decoration: none !important; /* Prevent text decoration */
  737. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1),
  738. inset 0 1px 2px rgba(255, 255, 255, 0.1);
  739. transition: all 0.2s ease;
  740. position: relative;
  741. overflow: hidden;
  742. -webkit-tap-highlight-color: transparent; /* Remove tap highlight on mobile */
  743. }
  744.  
  745. .mobile-action-button::before {
  746. content: '';
  747. position: absolute;
  748. top: 0;
  749. left: 0;
  750. width: 100%;
  751. height: 100%;
  752. background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
  753. opacity: 0;
  754. transition: opacity 0.2s ease;
  755. }
  756.  
  757. .mobile-action-button:hover {
  758. background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 100%);
  759. transform: translateY(-1px);
  760. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15),
  761. inset 0 1px 2px rgba(255, 255, 255, 0.2);
  762. color: #ffffff !important;
  763. }
  764.  
  765. .mobile-action-button:hover::before {
  766. opacity: 1;
  767. }
  768.  
  769. .mobile-action-button:active {
  770. transform: translateY(1px) scale(0.98);
  771. background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
  772. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1),
  773. inset 0 2px 4px rgba(0, 0, 0, 0.2);
  774. color: #ffffff !important;
  775. }
  776.  
  777. /* Dark theme support for mobile buttons */
  778. .theme-dark .mobile-action-button {
  779. background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
  780. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2),
  781. inset 0 1px 2px rgba(255, 255, 255, 0.05);
  782. color: #ffffff !important;
  783. }
  784.  
  785. .theme-dark .mobile-action-button:hover {
  786. background: linear-gradient(135deg, #1e293b 0%, #475569 100%);
  787. box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25),
  788. inset 0 1px 2px rgba(255, 255, 255, 0.1);
  789. color: #ffffff !important;
  790. }
  791.  
  792. .theme-dark .mobile-action-button:active {
  793. background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
  794. box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2),
  795. inset 0 2px 4px rgba(0, 0, 0, 0.3);
  796. color: #ffffff !important;
  797. }
  798.  
  799. /* Hide organize button on mobile */
  800. @media (max-width: 768px) {
  801. .schedule-organizer-btn {
  802. display: none !important;
  803. }
  804. }`);
  805. // Global variables
  806. let rows = [];
  807. const days = ['الأحد','الإثنين','الثلاثاء','الأربعاء','الخميس'];
  808. let newTable = {};
  809. let newTableNode;
  810. let on = false;
  811. let ramadanMode = false;
  812. let colors = ["Blue", "Black", "Crimson", "Green", "Grey", "OrangeRed", "Purple", "Red", "SpringGreen", "MediumTurquoise", "Navy", "GoldenRod"];
  813. let subject_colors = {};
  814. let color_index = 0;
  815. let currentTheme = 'light';
  816. let includeSummaryInDownload = false;
  817. let hastebinApiKey = 'c03fb6598a7bfcde22f4eed0931e691e2c4ed173f14f1cc598016d48cca00bb3287b230ddb5a300c931220ff2bf38ba8fccf946d6a7b4ada4ac8706d2eb3dc59';
  818.  
  819. // Time conversion functions
  820. function convertToRamadanTime(timeStr) {
  821. // Split the time range
  822. const [startTime, endTime] = timeStr.split(' - ');
  823. // Helper function to parse time
  824. function parseTime(time) {
  825. const [timeComponent, period] = time.trim().split(' ');
  826. const [hourStr, minuteStr] = timeComponent.split(':');
  827. let hour = parseInt(hourStr);
  828. const minute = parseInt(minuteStr);
  829. const isPM = period === 'م';
  830. if (isPM && hour !== 12) hour += 12;
  831. if (!isPM && hour === 12) hour = 0;
  832. return { hour, minute, period };
  833. }
  834.  
  835. // Parse start and end times to detect practical sessions
  836. const start = parseTime(startTime);
  837. const end = parseTime(endTime);
  838. // Determine if it's a practical session based on duration (80 minutes)
  839. const duration = ((end.hour - start.hour) * 60 + (end.minute - start.minute));
  840. const isPractical = Math.abs(duration - 80) <= 5; // Allow 5-minute flexibility
  841.  
  842. // Theoretical lecture time mappings
  843. const theoreticalMap = {
  844. '08:00 ص': { start: '09:30 ص', end: '10:05 ص' },
  845. '09:00 ص': { start: '10:10 ص', end: '10:45 ص' },
  846. '10:00 ص': { start: '10:50 ص', end: '11:25 ص' },
  847. '11:00 ص': { start: '11:30 ص', end: '12:05 م' },
  848. '12:00 م': { start: '12:10 م', end: '12:45 م' },
  849. '01:00 م': { start: '01:05 م', end: '01:40 م' },
  850. '02:00 م': { start: '01:45 م', end: '02:20 م' },
  851. '03:00 م': { start: '02:25 م', end: '03:00 م' },
  852. '04:00 م': { start: '03:05 م', end: '03:40 م' },
  853. '05:00 م': { start: '03:45 م', end: '04:20 م' },
  854. '06:00 م': 'غير مستخدم',
  855. '07:00 م': { start: '04:40 م', end: '05:15 م' }
  856. };
  857.  
  858. // Practical session time mappings
  859. const practicalMap = {
  860. '08:00 ص': { start: '09:30 ص', end: '10:20 ص' },
  861. '09:30 ص': { start: '10:25 ص', end: '11:15 ص' },
  862. '11:00 ص': { start: '11:20 ص', end: '12:10 م' },
  863. '12:30 م': { start: '12:15 م', end: '01:05 م' },
  864. '02:00 م': { start: '01:30 م', end: '02:20 م' },
  865. '03:30 م': { start: '02:25 م', end: '03:15 م' },
  866. '05:00 م': { start: '03:20 م', end: '04:10 م' }
  867. };
  868.  
  869. // Get the mapped time based on session type
  870. const timeMap = isPractical ? practicalMap : theoreticalMap;
  871. const mappedTime = timeMap[startTime];
  872.  
  873. // Handle "not in use" case
  874. if (mappedTime === 'غير مستخدم') {
  875. return 'غير مستخدم';
  876. }
  877.  
  878. // If no mapping found, return original time
  879. if (!mappedTime) {
  880. return timeStr;
  881. }
  882.  
  883. return `${mappedTime.start} - ${mappedTime.end}`;
  884. }
  885.  
  886. // Start initialization
  887. if (document.readyState === 'loading') {
  888. document.addEventListener('DOMContentLoaded', init);
  889. } else {
  890. init();
  891. }
  892.  
  893. // Main initialization function
  894. function waitForElement(selector, callback, maxTries = 100) {
  895. if (maxTries <= 0) {
  896. console.log('Element not found: ' + selector);
  897. return;
  898. }
  899. const element = document.getElementById(selector);
  900. if (element) {
  901. callback(element);
  902. return;
  903. }
  904. setTimeout(() => {
  905. waitForElement(selector, callback, maxTries - 1);
  906. }, 100);
  907. }
  908.  
  909. // Remove the DOMContentLoaded listener and replace with this:
  910. function init() {
  911. waitForElement('scheduleFrm:studScheduleTable', (element) => {
  912. try {
  913. initializeTableOrganizer();
  914. } catch (error) {
  915. console.error('Error initializing table organizer:', error);
  916. }
  917. });
  918. }
  919.  
  920. // Add error handling to the table check
  921. function initializeTableOrganizer() {
  922. const originalTableNode = document.getElementById('scheduleFrm:studScheduleTable');
  923. if (!originalTableNode) {
  924. console.log('Schedule table not found');
  925. return;
  926. }
  927.  
  928. // Check if mobile device
  929. if (isMobileDevice()) {
  930. // For mobile: Add mobile buttons to main content
  931. const mainContent = document.querySelector('.main_content.col-md-12');
  932. if (mainContent) {
  933. const mobileButtons = createMobileButtons();
  934. mainContent.insertBefore(mobileButtons, mainContent.firstChild);
  935. }
  936. return; // Don't add the regular organize button for mobile
  937. }
  938.  
  939. // Desktop version continues with existing code
  940. let button = document.createElement('span');
  941. let cell = document.createElement('td');
  942. button.classList.add("schedule-organizer-btn");
  943.  
  944. // Set initial button state
  945. button.innerHTML = on ? "الجدول الاصلي" : "نظم الجدول";
  946. if (on) {
  947. button.classList.add("active");
  948. originalTableNode.style.display = 'none';
  949. if (newTableNode) {
  950. newTableNode.style.display = null;
  951. } else {
  952. getTableInfo();
  953. getNewTable();
  954. appendTable();
  955. }
  956. } else {
  957. if (newTableNode) {
  958. newTableNode.style.display = 'none';
  959. }
  960. }
  961.  
  962. // Append button to table
  963. cell.appendChild(button);
  964. const printLink = document.getElementById("scheduleFrm:printLink");
  965. if (printLink && printLink.parentElement && printLink.parentElement.parentElement) {
  966. printLink.parentElement.parentElement.appendChild(cell);
  967. } else {
  968. // Fallback: append to the table directly if printLink is not found
  969. const firstRow = originalTableNode.querySelector('tr');
  970. if (firstRow) {
  971. firstRow.appendChild(cell);
  972. }
  973. }
  974.  
  975. // Add click handler
  976. button.onclick = function() {
  977. if (on) {
  978. on = false;
  979. button.classList.remove("active");
  980. button.innerHTML = "نظم الجدول";
  981. originalTableNode.style.display = null;
  982. newTableNode.style.display = 'none';
  983. document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
  984. } else {
  985. on = true;
  986. button.classList.add("active");
  987. button.innerHTML = "الجدول الاصلي";
  988. originalTableNode.style.display = 'none';
  989. if (newTableNode) {
  990. newTableNode.style.display = null;
  991. document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
  992. let summary = createSummary();
  993. originalTableNode.insertAdjacentElement('afterend', summary);
  994. } else {
  995. if (rows.length == 0) {
  996. getTableInfo();
  997. }
  998. getNewTable();
  999. appendTable();
  1000. }
  1001. }
  1002. };
  1003. }
  1004.  
  1005. // Helper function to get deepest text
  1006. function endText(node) {
  1007. if (!node.firstElementChild) {
  1008. return node.innerHTML;
  1009. } else {
  1010. return endText(node.firstElementChild);
  1011. }
  1012. }
  1013.  
  1014. // Get table information
  1015. function getTableInfo() {
  1016. const row1 = document.querySelectorAll(".ROW1");
  1017. const row2 = document.querySelectorAll(".ROW2");
  1018. function processRows(nodes) {
  1019. for (let i = 0; i < nodes.length; i++) {
  1020. let row_obj = {};
  1021. let row = nodes[i];
  1022. let cells = row.children;
  1023. for (let j = 0; j < cells.length; j++) {
  1024. try {
  1025. if (cells[j].dataset.th.includes("القاعة")) {
  1026. let headers = cells[j].dataset.th.split(/\s+/);
  1027. let lectures = cells[j].firstElementChild.firstElementChild.children;
  1028. row_obj["محاضرات"] = [];
  1029. for (let k = 0; k < lectures.length; k++) {
  1030. let data = {};
  1031. for (let l = 0; l < headers.length; l++) {
  1032. let currentHeader = headers[l];
  1033. data[currentHeader] = endText(lectures[k].children[l]).trim();
  1034. if (data[currentHeader].includes("&nbsp")) {
  1035. data[currentHeader] = data[currentHeader].split('; ')[1].trim().split(' ');
  1036. }
  1037. }
  1038. row_obj["محاضرات"].push(data);
  1039. }
  1040. } else {
  1041. let cellName = cells[j].dataset.th.trim();
  1042. row_obj[cellName] = endText(cells[j]).trim();
  1043. if (row_obj[cellName].includes("&nbsp")) {
  1044. row_obj[cellName] = row_obj[cellName].split('&')[0].trim();
  1045. }
  1046. }
  1047. } catch(err) {
  1048. console.log(err);
  1049. }
  1050. }
  1051. rows.push(row_obj);
  1052. }
  1053. }
  1054.  
  1055. processRows(row1);
  1056. processRows(row2);
  1057. }
  1058.  
  1059. function getNewTable() {
  1060. try {
  1061. // Populate the new table with the days and their lectures
  1062. for (i in days) {
  1063. newTable[days[i]] = [];
  1064. }
  1065. for (i in rows) {
  1066. let subjectLectures = rows[i]['محاضرات'];
  1067. for (j in subjectLectures) {
  1068. let lecture = subjectLectures[j];
  1069. let time = lecture['الوقت'];
  1070. // Convert time to Ramadan schedule if ramadanMode is enabled
  1071. if (ramadanMode) {
  1072. time = convertToRamadanTime(time);
  1073. }
  1074.  
  1075. function value(t) {
  1076. let hour = parseInt(t.slice(0, 2), 10);
  1077. let minutes = parseInt(t.slice(3, 5), 10);
  1078. let total = (hour * 60) + minutes;
  1079.  
  1080. if (t.slice(0, 10).includes('م') && hour != 12) {
  1081. total += 720;
  1082. }
  1083.  
  1084. return total;
  1085. }
  1086.  
  1087. function getLectureEndTime(timeStr) {
  1088. let parts = timeStr.split(' - ');
  1089. return value(parts[1]);
  1090. }
  1091.  
  1092. function getLectureStartTime(timeStr) {
  1093. let parts = timeStr.split(' - ');
  1094. return value(parts[0]);
  1095. }
  1096.  
  1097. for (k in lecture["اليوم"]) {
  1098. let day = days[parseInt(lecture["اليوم"][k])-1];
  1099. newTable[day].push({
  1100. subject: rows[i]['اسم المقرر'],
  1101. activity: rows[i]['النشاط'],
  1102. time: time,
  1103. place: lecture['القاعة'],
  1104. section: rows[i]['الشعبة'],
  1105. value: value(time),
  1106. endTime: getLectureEndTime(time),
  1107. startTime: getLectureStartTime(time)
  1108. });
  1109. if (!(rows[i]['اسم المقرر'] in subject_colors)){
  1110. subject_colors[rows[i]['اسم المقرر']] = colors[color_index];
  1111. color_index++;
  1112. }
  1113. }
  1114. }
  1115. }
  1116.  
  1117. // Sort lectures by time
  1118. for (i in newTable) {
  1119. newTable[i].sort((a, b) => a.startTime - b.startTime);
  1120. }
  1121.  
  1122. // Helper function to insert after index
  1123. function insert_after(element, array, index) {
  1124. let new_array = [];
  1125. for (i = 0; i < array.length; i++) {
  1126. if (i == index+1) {
  1127. new_array.push(element);
  1128. }
  1129. new_array.push(array[i]);
  1130. }
  1131. return new_array;
  1132. }
  1133.  
  1134. // Add breaks between lectures
  1135. for (d = 0; d < days.length; d++) {
  1136. let edited_day = JSON.parse(JSON.stringify(newTable[days[d]]));
  1137. let uni_day = newTable[days[d]];
  1138. let skip = 0;
  1139. for (l = 0; l < uni_day.length - 1; l++) {
  1140. let currentLectureEnd = uni_day[l].endTime;
  1141. let nextLectureStart = uni_day[l+1].startTime;
  1142. let breakTime = nextLectureStart - currentLectureEnd;
  1143. if (breakTime > 10) { // Only show breaks longer than 10 minutes
  1144. let break_obj = {
  1145. subject: null,
  1146. activity: "break",
  1147. time: null,
  1148. place: null,
  1149. value: breakTime
  1150. };
  1151. edited_day = insert_after(break_obj, edited_day, l+skip);
  1152. skip++;
  1153. }
  1154. }
  1155. newTable[days[d]] = edited_day;
  1156. }
  1157. } catch(err) {
  1158. console.log(err);
  1159. }
  1160. }
  1161.  
  1162. function getBreakText(hrs) {
  1163. const getBreakIcon = (hrs) => {
  1164. if (hrs >= 2) return '☕';
  1165. if (hrs >= 1) return '⏰';
  1166. return '⌛';
  1167. };
  1168. // Round down if extra minutes are 10 or less
  1169. const wholeHours = Math.floor(hrs);
  1170. const extraMinutes = Math.round((hrs - wholeHours) * 60);
  1171. const roundedHours = extraMinutes <= 10 ? wholeHours : hrs;
  1172. const icon = getBreakIcon(roundedHours);
  1173. let duration;
  1174. if (roundedHours === 2) {
  1175. duration = 'ساعتين';
  1176. } else if (roundedHours > 2) {
  1177. duration = `${Math.floor(roundedHours)} ساعات`;
  1178. } else if (roundedHours >= 1) {
  1179. duration = 'ساعة';
  1180. if (roundedHours > 1) {
  1181. const minutes = Math.round((roundedHours - 1) * 60);
  1182. if (minutes > 10) { // Only show minutes if more than 10
  1183. duration += ` و ${minutes} دقيقة`;
  1184. }
  1185. }
  1186. } else {
  1187. const minutes = Math.round(roundedHours * 60);
  1188. duration = `${minutes} دقيقة`;
  1189. }
  1190. return `<div class="break-content">${icon} ${duration} استراحة</div>`;
  1191. }
  1192.  
  1193. function getActivityIcon(activity) {
  1194. if (activity.includes('عملي')) return '🔬';
  1195. if (activity.includes('نظري')) return '📚';
  1196. return '📖';
  1197. }
  1198.  
  1199. function getActivityStyle(activity) {
  1200. if (activity.includes('عملي')) return 'background: #9c27b0; color: white; border-radius: 4px; padding: 2px 6px;';
  1201. if (activity.includes('نظري')) return 'background: #1976d2; color: white; border-radius: 4px; padding: 2px 6px;';
  1202. return 'background: #757575; color: white; border-radius: 4px; padding: 2px 6px;';
  1203. }
  1204.  
  1205. function downloadAsPNG(event) {
  1206. if (event) {
  1207. event.preventDefault();
  1208. }
  1209. //Create and show loading overlay
  1210. const loadingOverlay = document.createElement('div');
  1211. loadingOverlay.className = 'loading-notification';
  1212. loadingOverlay.innerHTML = `
  1213. <div class="notification-content">
  1214. <div class="modern-spinner"></div>
  1215. <div class="notification-text">
  1216. <div class="notification-title">جار تحميل الصورة...</div>
  1217. <div class="notification-subtitle">يرجى الانتظار بينما نقوم بمعالجة الجدول</div>
  1218. </div>
  1219. </div>
  1220. `;
  1221. // Add styles for the notification
  1222. const style = document.createElement('style');
  1223. style.textContent = `
  1224. .loading-notification {
  1225. position: fixed;
  1226. top: 20px;
  1227. right: 20px;
  1228. background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
  1229. border: 1px solid ${currentTheme === 'dark' ? '#333' : '#e0e0e0'};
  1230. border-radius: 12px;
  1231. padding: 16px;
  1232. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  1233. z-index: 10000;
  1234. max-width: 300px;
  1235. animation: slideIn 0.3s ease-out;
  1236. backdrop-filter: blur(10px);
  1237. }
  1238.  
  1239. .notification-content {
  1240. display: flex;
  1241. align-items: center;
  1242. gap: 12px;
  1243. }
  1244.  
  1245. .notification-text {
  1246. flex: 1;
  1247. }
  1248.  
  1249. .notification-title {
  1250. color: ${currentTheme === 'dark' ? '#ffffff' : '#000000'};
  1251. font-weight: 600;
  1252. margin-bottom: 4px;
  1253. }
  1254.  
  1255. .notification-subtitle {
  1256. color: ${currentTheme === 'dark' ? '#888' : '#666'};
  1257. font-size: 0.9em;
  1258. }
  1259.  
  1260. .modern-spinner {
  1261. width: 24px;
  1262. height: 24px;
  1263. border: 3px solid ${currentTheme === 'dark' ? '#333' : '#f0f0f0'};
  1264. border-top: 3px solid ${currentTheme === 'dark' ? '#4CAF50' : '#2196F3'};
  1265. border-radius: 50%;
  1266. animation: spin 1s linear infinite;
  1267. }
  1268.  
  1269. @keyframes spin {
  1270. 0% { transform: rotate(0deg); }
  1271. 100% { transform: rotate(360deg); }
  1272. }
  1273.  
  1274. @keyframes slideIn {
  1275. from {
  1276. opacity: 0;
  1277. transform: translateX(100px);
  1278. }
  1279. to {
  1280. opacity: 1;
  1281. transform: translateX(0);
  1282. }
  1283. }
  1284.  
  1285. @keyframes slideOut {
  1286. from {
  1287. opacity: 1;
  1288. transform: translateX(0);
  1289. }
  1290. to {
  1291. opacity: 0;
  1292. transform: translateX(100px);
  1293. }
  1294. }
  1295. `;
  1296. document.head.appendChild(style);
  1297. document.body.appendChild(loadingOverlay);
  1298. const element = document.getElementById('newTable');
  1299. const summary = document.querySelector('.schedule-summary');
  1300. // Create filename based on mode
  1301. const filename = ramadanMode ? 'الجدول_الدراسي_توقيت_رمضان.png' : 'الجدول_الدراسي.png';
  1302. // Calculate the maximum width needed
  1303. const tableWidth = element.offsetWidth;
  1304. const summaryWidth = summary ? summary.offsetWidth : 0;
  1305. const maxWidth = Math.max(tableWidth, summaryWidth);
  1306. const wrapper = document.createElement('div');
  1307. wrapper.style.cssText = `
  1308. background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
  1309. direction: rtl;
  1310. width: ${maxWidth}px;
  1311. margin: 0;
  1312. border-radius: 0;
  1313. display: flex;
  1314. flex-direction: column;
  1315. align-items: stretch;
  1316. position: relative;
  1317. `;
  1318. // Only include summary if checkbox is checked
  1319. if (includeSummaryInDownload && summary) {
  1320. const summaryClone = summary.cloneNode(true);
  1321. // Remove control buttons from summary clone
  1322. const controlButtons = summaryClone.querySelector('.control-buttons');
  1323. if (controlButtons) controlButtons.remove();
  1324. // Remove theme buttons and download button
  1325. summaryClone.querySelectorAll('.control-button, .theme-btn, label').forEach(btn => btn.remove());
  1326. // Ensure summary maintains consistent width
  1327. summaryClone.style.cssText = `
  1328. width: ${maxWidth}px;
  1329. margin: 0;
  1330. box-sizing: border-box;
  1331. background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
  1332. `;
  1333. wrapper.appendChild(summaryClone);
  1334. }
  1335. const tableClone = element.cloneNode(true);
  1336. // If in Ramadan mode, find a suitable cell for the indicator
  1337. if (ramadanMode) {
  1338. // Try to find an empty or break cell in the middle of the table
  1339. const rows = tableClone.querySelectorAll('tbody tr');
  1340. let indicatorPlaced = false;
  1341. // Calculate middle row
  1342. const middleRowIndex = Math.floor(rows.length / 2);
  1343. // First try: Look in the middle row
  1344. if (rows[middleRowIndex]) {
  1345. const cells = rows[middleRowIndex].children;
  1346. for (let cell of cells) {
  1347. if (!cell.innerHTML.trim() || cell.innerHTML.includes('استراحة')) {
  1348. const ramadanIndicator = `
  1349. <div style="
  1350. background: ${currentTheme === 'dark' ?
  1351. 'linear-gradient(135deg, #2d1f3d 0%, #1a1a2e 100%)' :
  1352. 'linear-gradient(135deg, #f3e5f5 0%, #e8eaf6 100%)'
  1353. };
  1354. padding: 12px 24px;
  1355. border-radius: 12px;
  1356. display: flex;
  1357. align-items: center;
  1358. justify-content: center;
  1359. gap: 12px;
  1360. font-size: 1.2em;
  1361. box-shadow: ${currentTheme === 'dark' ?
  1362. '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
  1363. '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
  1364. };
  1365. margin: 10px auto;
  1366. width: fit-content;
  1367. border: 2px solid ${currentTheme === 'dark' ? '#4a3f6b' : '#e1bee7'};
  1368. animation: ramadanGlow 2s ease-in-out infinite;
  1369. ">
  1370. <span style="font-size: 1.4em;">🌙</span>
  1371. <span style="
  1372. color: ${currentTheme === 'dark' ? '#fff' : '#000'};
  1373. font-weight: 500;
  1374. ">توقيت رمضان</span>
  1375. </div>
  1376. <style>
  1377. @keyframes ramadanGlow {
  1378. 0%, 100% {
  1379. box-shadow: ${currentTheme === 'dark' ?
  1380. '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
  1381. '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
  1382. };
  1383. }
  1384. 50% {
  1385. box-shadow: ${currentTheme === 'dark' ?
  1386. '0 4px 20px rgba(123, 97, 255, 0.3), 0 0 30px rgba(123, 97, 255, 0.2)' :
  1387. '0 4px 20px rgba(156, 39, 176, 0.2), 0 0 30px rgba(156, 39, 176, 0.1)'
  1388. };
  1389. }
  1390. }
  1391. </style>
  1392. `;
  1393. cell.innerHTML = ramadanIndicator;
  1394. indicatorPlaced = true;
  1395. break;
  1396. }
  1397. }
  1398. }
  1399. // Second try: Look in adjacent rows if middle row didn't work
  1400. if (!indicatorPlaced) {
  1401. for (let offset = 1; offset <= 2; offset++) {
  1402. const rowsToTry = [
  1403. rows[middleRowIndex - offset],
  1404. rows[middleRowIndex + offset]
  1405. ];
  1406. for (const row of rowsToTry) {
  1407. if (!row) continue;
  1408. const cells = row.children;
  1409. for (let cell of cells) {
  1410. if (!cell.innerHTML.trim() || cell.innerHTML.includes('استراحة')) {
  1411. const ramadanIndicator = `
  1412. <div style="
  1413. background: ${currentTheme === 'dark' ?
  1414. 'linear-gradient(135deg, #2d1f3d 0%, #1a1a2e 100%)' :
  1415. 'linear-gradient(135deg, #f3e5f5 0%, #e8eaf6 100%)'
  1416. };
  1417. padding: 12px 24px;
  1418. border-radius: 12px;
  1419. display: flex;
  1420. align-items: center;
  1421. justify-content: center;
  1422. gap: 12px;
  1423. font-size: 1.2em;
  1424. box-shadow: ${currentTheme === 'dark' ?
  1425. '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
  1426. '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
  1427. };
  1428. margin: 10px auto;
  1429. width: fit-content;
  1430. border: 2px solid ${currentTheme === 'dark' ? '#4a3f6b' : '#e1bee7'};
  1431. animation: ramadanGlow 2s ease-in-out infinite;
  1432. ">
  1433. <span style="font-size: 1.4em;">🌙</span>
  1434. <span style="
  1435. color: ${currentTheme === 'dark' ? '#fff' : '#000'};
  1436. font-weight: 500;
  1437. ">توقيت رمضان</span>
  1438. </div>
  1439. <style>
  1440. @keyframes ramadanGlow {
  1441. 0%, 100% {
  1442. box-shadow: ${currentTheme === 'dark' ?
  1443. '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
  1444. '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
  1445. };
  1446. }
  1447. 50% {
  1448. box-shadow: ${currentTheme === 'dark' ?
  1449. '0 4px 20px rgba(123, 97, 255, 0.3), 0 0 30px rgba(123, 97, 255, 0.2)' :
  1450. '0 4px 20px rgba(156, 39, 176, 0.2), 0 0 30px rgba(156, 39, 176, 0.1)'
  1451. };
  1452. }
  1453. }
  1454. </style>
  1455. `;
  1456. cell.innerHTML = ramadanIndicator;
  1457. indicatorPlaced = true;
  1458. break;
  1459. }
  1460. }
  1461. if (indicatorPlaced) break;
  1462. }
  1463. if (indicatorPlaced) break;
  1464. }
  1465. }
  1466. }
  1467. wrapper.appendChild(tableClone);
  1468. document.body.appendChild(wrapper);
  1469. // Enhanced style preservation
  1470. const preserveStyles = (element) => {
  1471. const computedStyle = window.getComputedStyle(element);
  1472. const importantStyles = [
  1473. 'font-family', 'font-size', 'font-weight', 'color', 'background',
  1474. 'padding', 'margin', 'border', 'text-align', 'direction',
  1475. 'display', 'width', 'height', 'border-radius', 'box-shadow',
  1476. 'grid-template-columns', 'gap', 'background-color', 'border-color',
  1477. 'border-width', 'border-style', 'line-height', 'letter-spacing',
  1478. 'text-decoration', 'text-transform', 'vertical-align', 'position',
  1479. 'top', 'left', 'right', 'bottom', 'z-index', 'opacity',
  1480. 'transform', 'transition', 'box-sizing', 'overflow'
  1481. ];
  1482. let styleString = importantStyles.map(property =>
  1483. `${property}:${computedStyle.getPropertyValue(property)}`
  1484. ).join(';');
  1485. // Preserve existing inline styles
  1486. if (element.style.cssText) {
  1487. styleString += ';' + element.style.cssText;
  1488. }
  1489. element.style.cssText = styleString;
  1490. Array.from(element.children).forEach(preserveStyles);
  1491. };
  1492. preserveStyles(wrapper);
  1493. // Use fixed scale of 7
  1494. const scale = 7;
  1495. html2canvas(wrapper, {
  1496. backgroundColor: '#ffffff',
  1497. scale: scale,
  1498. logging: false,
  1499. useCORS: true,
  1500. allowTaint: true,
  1501. width: wrapper.offsetWidth,
  1502. height: wrapper.offsetHeight,
  1503. onclone: function(clonedDoc) {
  1504. const clonedWrapper = clonedDoc.body.lastChild;
  1505. preserveStyles(clonedWrapper);
  1506. }
  1507. }).then(canvas => {
  1508. wrapper.remove();
  1509. // Add slide out animation before removing
  1510. loadingOverlay.style.animation = 'slideOut 0.3s ease-in';
  1511. setTimeout(() => {
  1512. loadingOverlay.remove();
  1513. style.remove();
  1514. }, 300);
  1515. try {
  1516. const image = canvas.toDataURL('image/png', 1.0);
  1517. const link = document.createElement('a');
  1518. link.download = filename; // Use the new filename
  1519. link.href = image;
  1520. document.body.appendChild(link);
  1521. link.click();
  1522. document.body.removeChild(link);
  1523. } catch (error) {
  1524. console.error('Error saving image:', error);
  1525. alert('خطأ في حفظ الصورة. يرجى المحاولة مرة أخرى.');
  1526. }
  1527. }).catch(error => {
  1528. console.error('Error generating PNG:', error);
  1529. // Add slide out animation before removing
  1530. loadingOverlay.style.animation = 'slideOut 0.3s ease-in';
  1531. setTimeout(() => {
  1532. loadingOverlay.remove();
  1533. style.remove();
  1534. }, 300);
  1535. if (error.message.includes('memory')) {
  1536. alert('خطأ: الصورة كبيرة جداً. جاري المحاولة بجودة أقل...');
  1537. setTimeout(() => {
  1538. html2canvas(wrapper, {
  1539. backgroundColor: '#ffffff',
  1540. scale: 6,
  1541. logging: false,
  1542. useCORS: true,
  1543. allowTaint: true,
  1544. width: wrapper.offsetWidth,
  1545. height: wrapper.offsetHeight
  1546. }).then(canvas => {
  1547. const image = canvas.toDataURL('image/png', 1.0);
  1548. const link = document.createElement('a');
  1549. link.download = filename; // Use the new filename
  1550. link.href = image;
  1551. document.body.appendChild(link);
  1552. link.click();
  1553. document.body.removeChild(link);
  1554. });
  1555. }, 100);
  1556. } else {
  1557. alert('حدث خطأ أثناء إنشاء الصورة. يرجى المحاولة مرة أخرى.');
  1558. }
  1559. wrapper.remove();
  1560. });
  1561. }
  1562.  
  1563. function toggleTheme(theme) {
  1564. currentTheme = theme;
  1565. const table = document.getElementById('newTable');
  1566. if (!table) return;
  1567. table.classList.remove('theme-light', 'theme-dark');
  1568. table.classList.add(`theme-${theme}`);
  1569. // Update summary section theme
  1570. const summary = document.querySelector('.schedule-summary');
  1571. if (summary) {
  1572. summary.classList.remove('theme-light', 'theme-dark');
  1573. summary.classList.add(`theme-${theme}`);
  1574. // Update button states
  1575. const lightThemeBtn = summary.querySelector('#lightThemeBtn');
  1576. const darkThemeBtn = summary.querySelector('#darkThemeBtn');
  1577. if (lightThemeBtn) {
  1578. lightThemeBtn.classList.toggle('active', theme === 'light');
  1579. }
  1580. if (darkThemeBtn) {
  1581. darkThemeBtn.classList.toggle('active', theme === 'dark');
  1582. }
  1583. }
  1584. }
  1585.  
  1586. function createSummary() {
  1587. let summary = document.createElement('div');
  1588. summary.classList.add('schedule-summary', `theme-${currentTheme}`);
  1589. let totalHours = 0;
  1590. let subjectCount = new Set();
  1591. let daysWithClasses = new Set();
  1592. let maxLectures = 0;
  1593. let busyDays = [];
  1594. for (let day in newTable) {
  1595. let dayLectures = newTable[day].filter(slot => slot.activity !== "break");
  1596. if (dayLectures.length > 0) {
  1597. daysWithClasses.add(day);
  1598. if (dayLectures.length > maxLectures) {
  1599. maxLectures = dayLectures.length;
  1600. busyDays = [day];
  1601. } else if (dayLectures.length === maxLectures) {
  1602. busyDays.push(day);
  1603. }
  1604. }
  1605. dayLectures.forEach(slot => {
  1606. totalHours += (slot.time ? 1 : 0);
  1607. subjectCount.add(slot.subject);
  1608. });
  1609. }
  1610.  
  1611. // Desktop UI
  1612. summary.innerHTML = `
  1613. <div style="display: flex; align-items: center; justify-content: center; gap: 20px; flex-wrap: wrap;">
  1614. <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#1a2f4d' : '#e3f2fd'}; padding: 8px 16px; border-radius: 8px;">
  1615. <span style="font-weight: 500;">📚 المواد:</span>
  1616. <span>${subjectCount.size}</span>
  1617. </div>
  1618. <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#2d1f3d' : '#f3e5f5'}; padding: 8px 16px; border-radius: 8px;">
  1619. <span style="font-weight: 500;">⏰ الساعات:</span>
  1620. <span>${totalHours}</span>
  1621. </div>
  1622. <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#1f3d2d' : '#e8f5e9'}; padding: 8px 16px; border-radius: 8px;">
  1623. <span style="font-weight: 500;">📅 أيام الدراسة:</span>
  1624. <span>${daysWithClasses.size}</span>
  1625. </div>
  1626. <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#3d2d1f' : '#fff3e0'}; padding: 8px 16px; border-radius: 8px;">
  1627. <span style="font-weight: 500;">📊 اليوم الأكثر:</span>
  1628. <span>${busyDays.join(', ')} (${maxLectures})</span>
  1629. </div>
  1630. <div class="control-buttons">
  1631. <button class="control-button theme-btn ${currentTheme === 'light' ? 'active' : ''}" id="lightThemeBtn">
  1632. ☀️ فاتح
  1633. </button>
  1634. <button class="control-button theme-btn ${currentTheme === 'dark' ? 'active' : ''}" id="darkThemeBtn">
  1635. 🌙 داكن
  1636. </button>
  1637. <button class="control-button ${ramadanMode ? 'active' : ''}" id="ramadanBtn">
  1638. 🕌 توقيت رمضان
  1639. </button>
  1640. <div class="download-group">
  1641. <button class="control-button" id="downloadButton">
  1642. 💾 تحميل كصورة
  1643. </button>
  1644. <label class="custom-checkbox-container">
  1645. <div class="checkbox-wrapper">
  1646. <input type="checkbox" id="includeSummaryCheckbox" ${includeSummaryInDownload ? 'checked' : ''}>
  1647. <span class="checkmark"></span>
  1648. </div>
  1649. <span>تضمين الملخص</span>
  1650. </label>
  1651. </div>
  1652. </div>
  1653. </div>
  1654. `;
  1655. setTimeout(() => {
  1656. const downloadButton = summary.querySelector('#downloadButton');
  1657. const lightThemeBtn = summary.querySelector('#lightThemeBtn');
  1658. const darkThemeBtn = summary.querySelector('#darkThemeBtn');
  1659. const ramadanBtn = summary.querySelector('#ramadanBtn');
  1660. const includeSummaryCheckbox = summary.querySelector('#includeSummaryCheckbox');
  1661. if (downloadButton) {
  1662. downloadButton.addEventListener('click', downloadAsPNG);
  1663. }
  1664. if (lightThemeBtn) {
  1665. lightThemeBtn.addEventListener('click', () => {
  1666. toggleTheme('light');
  1667. appendTable();
  1668. });
  1669. }
  1670. if (darkThemeBtn) {
  1671. darkThemeBtn.addEventListener('click', () => {
  1672. toggleTheme('dark');
  1673. appendTable();
  1674. });
  1675. }
  1676. if (ramadanBtn) {
  1677. ramadanBtn.addEventListener('click', () => {
  1678. ramadanMode = !ramadanMode;
  1679. ramadanBtn.classList.toggle('active');
  1680. getNewTable();
  1681. appendTable();
  1682. });
  1683. }
  1684.  
  1685. if (includeSummaryCheckbox) {
  1686. includeSummaryCheckbox.addEventListener('change', (e) => {
  1687. includeSummaryInDownload = e.target.checked;
  1688. });
  1689. }
  1690. }, 0);
  1691. return summary;
  1692. }
  1693.  
  1694. function appendTable() {
  1695. // Remove any existing organized tables and summaries
  1696. if (newTableNode) {
  1697. newTableNode.remove();
  1698. }
  1699. document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
  1700.  
  1701. const originalTableNode = document.getElementById('scheduleFrm:studScheduleTable');
  1702. // Continue with normal table creation
  1703. let table = document.createElement('table');
  1704. table.id = "newTable";
  1705. table.classList.add('rowFlow', `theme-${currentTheme}`);
  1706. table.cellPadding = '0';
  1707. table.cellSpacing = '0';
  1708. table.border = '1';
  1709. // Create a wrapper div for the table
  1710. const tableWrapper = document.createElement('div');
  1711. tableWrapper.className = 'table-wrapper';
  1712. originalTableNode.insertAdjacentElement('afterend', tableWrapper);
  1713. tableWrapper.appendChild(table);
  1714.  
  1715. let thead = document.createElement('thead');
  1716. let tbody = document.createElement('tbody');
  1717. table.appendChild(thead);
  1718. table.appendChild(tbody);
  1719.  
  1720. const dayNamesEn = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday'];
  1721. days.forEach((day, i) => {
  1722. let th = document.createElement('th');
  1723. th.innerHTML = `
  1724. <div class="day-name">${day}</div>
  1725. `;
  1726. th.classList.add('HEADING');
  1727. th.scope = "col";
  1728. thead.appendChild(th);
  1729. });
  1730.  
  1731. function maxDayLength(obj) {
  1732. return Math.max(...Object.values(obj).map(day => day.length));
  1733. }
  1734.  
  1735. const maxLength = maxDayLength(newTable);
  1736.  
  1737. // Create empty rows
  1738. for (let i = 0; i < maxLength; i++) {
  1739. let tr = document.createElement('tr');
  1740. tbody.appendChild(tr);
  1741. for (let j = 0; j < days.length; j++) {
  1742. let td = document.createElement('td');
  1743. tr.appendChild(td);
  1744. }
  1745. }
  1746.  
  1747. let trs = tbody.children;
  1748. days.forEach((day, i) => {
  1749. let currentDay = newTable[day];
  1750. currentDay.forEach((lecture, j) => {
  1751. if (lecture.activity == "break") {
  1752. let hrs = lecture.value/60;
  1753. trs[j].children[i].innerHTML = `<div class="break-cell">${getBreakText(hrs)}</div>`;
  1754. } else {
  1755. let subjectColor = subject_colors[lecture.subject];
  1756. // Adjust color for dark mode if needed
  1757. if (currentTheme === 'dark') {
  1758. // Make the color more visible in dark mode
  1759. subjectColor = adjustColorForDarkMode(subjectColor);
  1760. }
  1761. let activityStyle = getActivityStyle(lecture.activity);
  1762. if (currentTheme === 'dark') {
  1763. activityStyle = activityStyle.replace('background: #9c27b0', 'background: #4a1259')
  1764. .replace('background: #1976d2', 'background: #1a3f6b')
  1765. .replace('background: #757575', 'background: #3d3d3d');
  1766. }
  1767. let content = `<div style="margin-bottom: 2px;">
  1768. <strong style="font-size: 1.05em; color: ${currentTheme === 'dark' ? '#e4e4e7' : 'inherit'}">${lecture.subject}</strong>
  1769. <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px;">
  1770. <div style="text-align: right;">
  1771. <div style="${activityStyle}">
  1772. ${getActivityIcon(lecture.activity)} ${lecture.activity}
  1773. </div>
  1774. <div style="background: ${currentTheme === 'dark' ? '#1a2f3a' : '#e8eaf6'}; border-radius: 6px; padding: 3px 4px; color: ${currentTheme === 'dark' ? '#8ebbff' : '#283593'}; display: inline-block; margin-top: 3px;">
  1775. 🔢 الشعبة: ${lecture.section}
  1776. </div>
  1777. </div>
  1778. <div style="text-align: left;">
  1779. <div style="font-weight: bold; color: ${currentTheme === 'dark' ? '#8ebbff' : '#1a237e'}; white-space: nowrap; font-size: 0.95em;">${formatTimeDisplay(lecture.time)}</div>
  1780. <div class="lecture-hall">🏛️ ${lecture.place}</div>
  1781. </div>
  1782. </div>
  1783. </div>`;
  1784. trs[j].children[i].innerHTML = `<div class="lecture-cell" style="border-left-color: ${subjectColor};">${content}</div>`;
  1785. }
  1786. });
  1787. });
  1788.  
  1789. newTableNode = table;
  1790. let summary = createSummary();
  1791. summary.style.cssText = `
  1792. width: 100%;
  1793. max-width: 1100px;
  1794. margin: 5px auto;
  1795. overflow-x: auto;
  1796. display: block;
  1797. `;
  1798. originalTableNode.insertAdjacentElement('afterend', summary);
  1799. }
  1800.  
  1801. // Helper function to adjust colors for dark mode
  1802. function adjustColorForDarkMode(color) {
  1803. // Convert color to RGB if it's a named color
  1804. let tempDiv = document.createElement('div');
  1805. tempDiv.style.color = color;
  1806. document.body.appendChild(tempDiv);
  1807. let rgbColor = window.getComputedStyle(tempDiv).color;
  1808. document.body.removeChild(tempDiv);
  1809. // Parse RGB values
  1810. let rgb = rgbColor.match(/\d+/g).map(Number);
  1811. // Calculate luminance
  1812. let luminance = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
  1813. // For very dark colors (especially black or near-black)
  1814. if (luminance < 0.2) {
  1815. // Convert to a light gray-blue tint
  1816. return `rgb(176, 196, 222)`; // Light steel blue
  1817. }
  1818. // For dark colors
  1819. if (luminance < 0.5) {
  1820. // Increase brightness more significantly
  1821. let adjustedRgb = rgb.map(value => {
  1822. return Math.min(255, value + 80);
  1823. });
  1824. return `rgb(${adjustedRgb.join(',')})`;
  1825. }
  1826. // For already light colors, just slight adjustment
  1827. let adjustedRgb = rgb.map(value => {
  1828. return Math.min(255, value + 40);
  1829. });
  1830. return `rgb(${adjustedRgb.join(',')})`;
  1831. }
  1832.  
  1833. // Add this helper function before appendTable()
  1834. function formatTimeDisplay(timeStr) {
  1835. // Split the time range
  1836. const [startTime, endTime] = timeStr.split(' - ');
  1837. // Split each time into components
  1838. const [startTimeComponent, startPeriod] = startTime.trim().split(' ');
  1839. const [endTimeComponent, endPeriod] = endTime.trim().split(' ');
  1840. // If both periods are the same, show it only once at the end
  1841. if (startPeriod === endPeriod) {
  1842. return `${startTimeComponent} - ${endTimeComponent} ${startPeriod}`;
  1843. }
  1844. // If periods are different, keep both but make it more compact
  1845. return `${startTimeComponent}${startPeriod} - ${endTimeComponent}${endPeriod}`;
  1846. }
  1847.  
  1848. // Add mobile detection function
  1849. function isMobileDevice() {
  1850. return (window.innerWidth <= 768);
  1851. }
  1852.  
  1853. // Add notification function
  1854. function showNotification(title, subtitle, type = 'success', duration = 3000) {
  1855. const notification = document.createElement('div');
  1856. notification.className = 'loading-notification';
  1857. // Set icon based on type
  1858. const icon = type === 'success' ? '✅' : type === 'error' ? '❌' : '⚠️';
  1859. notification.innerHTML = `
  1860. <div class="notification-content">
  1861. <div class="notification-icon">${icon}</div>
  1862. <div class="notification-text">
  1863. <div class="notification-title">${title}</div>
  1864. ${subtitle ? `<div class="notification-subtitle">${subtitle}</div>` : ''}
  1865. </div>
  1866. </div>
  1867. `;
  1868. // Add styles for the notification
  1869. const style = document.createElement('style');
  1870. style.textContent = `
  1871. .loading-notification {
  1872. position: fixed;
  1873. top: 20px;
  1874. right: 20px;
  1875. background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
  1876. border: 1px solid ${currentTheme === 'dark' ? '#333' : '#e0e0e0'};
  1877. border-radius: 12px;
  1878. padding: 16px;
  1879. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  1880. z-index: 10000;
  1881. max-width: 300px;
  1882. animation: slideIn 0.3s ease-out;
  1883. backdrop-filter: blur(10px);
  1884. }
  1885.  
  1886. .notification-content {
  1887. display: flex;
  1888. align-items: center;
  1889. gap: 12px;
  1890. }
  1891.  
  1892. .notification-icon {
  1893. font-size: 24px;
  1894. line-height: 1;
  1895. }
  1896.  
  1897. .notification-text {
  1898. flex: 1;
  1899. }
  1900.  
  1901. .notification-title {
  1902. color: ${currentTheme === 'dark' ? '#ffffff' : '#000000'};
  1903. font-weight: 600;
  1904. margin-bottom: 4px;
  1905. }
  1906.  
  1907. .notification-subtitle {
  1908. color: ${currentTheme === 'dark' ? '#888' : '#666'};
  1909. font-size: 0.9em;
  1910. }
  1911.  
  1912. @keyframes slideIn {
  1913. from {
  1914. opacity: 0;
  1915. transform: translateX(100px);
  1916. }
  1917. to {
  1918. opacity: 1;
  1919. transform: translateX(0);
  1920. }
  1921. }
  1922.  
  1923. @keyframes slideOut {
  1924. from {
  1925. opacity: 1;
  1926. transform: translateX(0);
  1927. }
  1928. to {
  1929. opacity: 0;
  1930. transform: translateX(100px);
  1931. }
  1932. }
  1933. `;
  1934. document.head.appendChild(style);
  1935. document.body.appendChild(notification);
  1936. // Remove notification after duration
  1937. setTimeout(() => {
  1938. notification.style.animation = 'slideOut 0.3s ease-in';
  1939. setTimeout(() => {
  1940. notification.remove();
  1941. style.remove();
  1942. }, 300);
  1943. }, duration);
  1944. }
  1945.  
  1946. // Update copyScheduleJSON function
  1947. async function copyScheduleJSON() {
  1948. try {
  1949. if (rows.length === 0) {
  1950. getTableInfo();
  1951. getNewTable();
  1952. }
  1953.  
  1954. // Create a cleaned version of the schedule without breaks and time values
  1955. const cleanedSchedule = {};
  1956. for (const day in newTable) {
  1957. cleanedSchedule[day] = newTable[day]
  1958. .filter(lecture => lecture.activity !== "break")
  1959. .map(lecture => ({
  1960. subject: lecture.subject,
  1961. activity: lecture.activity,
  1962. time: lecture.time,
  1963. place: lecture.place,
  1964. section: lecture.section
  1965. }));
  1966. }
  1967.  
  1968. // Create the final format
  1969. const formattedData = {
  1970. subjects: Array.from(new Set(rows.map(row => row['اسم المقرر']).filter(Boolean))),
  1971. days: days,
  1972. schedule: cleanedSchedule
  1973. };
  1974.  
  1975. const scheduleData = JSON.stringify(formattedData);
  1976.  
  1977. // Try to create a Hastebin paste
  1978. try {
  1979. // Prepare headers with authentication if API key is available
  1980. const headers = {
  1981. 'Content-Type': 'text/plain'
  1982. };
  1983. // Add authorization header if API key is available
  1984. if (hastebinApiKey) {
  1985. headers['Authorization'] = `Bearer ${hastebinApiKey}`;
  1986. }
  1987. // Using Hastebin API (hastebin.com)
  1988. const hastebinResponse = await fetch('https://hastebin.com/documents', {
  1989. method: 'POST',
  1990. headers: headers,
  1991. body: scheduleData
  1992. });
  1993.  
  1994. if (!hastebinResponse.ok) {
  1995. throw new Error(`Hastebin creation failed: ${hastebinResponse.status}`);
  1996. }
  1997.  
  1998. const hasteData = await hastebinResponse.json();
  1999. const hasteKey = hasteData.key;
  2000.  
  2001. // Open the website with the hastebin key
  2002. const baseUrl = 'https://jkc66.github.io/IU_Table_Organizer/cptable.html';
  2003. window.open(`${baseUrl}?haste=${hasteKey}`, '_blank');
  2004. showNotification('تم فتح منظم الجدول! ✨', 'تم نقل البيانات تلقائياً', 'success');
  2005.  
  2006. } catch (hasteError) {
  2007. console.error('Hastebin error:', hasteError);
  2008. // If hastebin creation fails, fall back to copy method
  2009. fallbackCopy(scheduleData);
  2010. }
  2011.  
  2012. } catch (error) {
  2013. console.error('Processing error:', error);
  2014. showNotification('حدث خطأ في معالجة الجدول ❌', 'جاري المحاولة بطريقة بديلة...', 'error');
  2015. fallbackCopy(scheduleData);
  2016. }
  2017. }
  2018.  
  2019. // New helper function for clipboard operations
  2020. function copyToClipboard(text) {
  2021. // Try the modern Clipboard API first
  2022. if (navigator.clipboard && window.isSecureContext) {
  2023. navigator.clipboard.writeText(text)
  2024. .then(() => {
  2025. console.log('Clipboard API: Copy successful');
  2026. return true;
  2027. })
  2028. .catch(err => {
  2029. console.error('Clipboard API failed:', err);
  2030. return fallbackCopyToClipboard(text);
  2031. });
  2032. } else {
  2033. return fallbackCopyToClipboard(text);
  2034. }
  2035. }
  2036.  
  2037. function fallbackCopyToClipboard(text) {
  2038. const textarea = document.createElement('textarea');
  2039. textarea.value = text;
  2040. document.body.appendChild(textarea);
  2041. textarea.select();
  2042. try {
  2043. const success = document.execCommand('copy');
  2044. console.log('Fallback copy success:', success);
  2045. return success;
  2046. } catch (e) {
  2047. console.error('Fallback copy failed:', e);
  2048. return false;
  2049. } finally {
  2050. document.body.removeChild(textarea);
  2051. }
  2052. }
  2053.  
  2054. // Update fallback copy to use new helper
  2055. function fallbackCopy(scheduleData) {
  2056. console.log('Entering fallback copy with data length:', scheduleData.length);
  2057. const success = copyToClipboard(scheduleData);
  2058. if (success) {
  2059. showNotification('تم نسخ البيانات بنجاح! ✨', 'يرجى فتح موقع منظم الجدول ولصق البيانات هناك', 'success');
  2060. } else {
  2061. showNotification('حدث خطأ أثناء النسخ ❌', 'يرجى المحاولة مرة أخرى لاحقاً', 'error');
  2062. }
  2063. // Open the website in a new tab
  2064. window.open('https://jkc66.github.io/IU_Table_Organizer/cptable.html', '_blank');
  2065. }
  2066.  
  2067. // Add function to create mobile buttons
  2068. function createMobileButtons() {
  2069. const mobileButtonsContainer = document.createElement('div');
  2070. mobileButtonsContainer.className = 'mobile-buttons-container';
  2071. mobileButtonsContainer.style.cssText = `
  2072. display: flex;
  2073. flex-direction: column;
  2074. gap: 10px;
  2075. padding: 15px;
  2076. background: #f5f5f5;
  2077. border-radius: 12px;
  2078. margin: 10px 0;
  2079. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  2080. `;
  2081.  
  2082. // Create View Table button
  2083. const viewTableButton = document.createElement('button');
  2084. viewTableButton.className = 'mobile-action-button';
  2085. viewTableButton.innerHTML = '📱 فتح منظم الجدول';
  2086. viewTableButton.onclick = copyScheduleJSON; // This will now handle both copying and redirecting
  2087.  
  2088. mobileButtonsContainer.appendChild(viewTableButton);
  2089.  
  2090. return mobileButtonsContainer;
  2091. }
  2092. }})();