您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A script to order the lectures table according to weekdays on the Islamic University website
- // ==UserScript==
- // @name IU Table Organizer
- // @description A script to order the lectures table according to weekdays on the Islamic University website
- // @name:en IU Table Organizer
- // @description:en A script to order the lectures table according to weekdays on the Islamic University website
- // @name:ar منظم جدول الجامعة الاسلامية
- // @description:ar اضافة لتعديل مظهر الجدول بالجامعة الاسلامية الى جدول مرتب تبعا لايام الاسبوع بضغطة زر
- // @include https://eduportal.iu.edu.sa/iu/ui/student/homeIndex.faces
- // @include https://eduportal.iu.edu.sa/iu/ui/student/*/*/*
- // @include http://eduportal.iu.edu.sa/iu/ui/student/*
- // @include https://eduportal.iu.edu.sa/iu/ui/student/student_schedule/index/studentScheduleIndex.faces
- // @version 4.3
- // @icon https://www.google.com/s2/favicons?domain=sso.iu.edu.sa
- // @namespace https://greasyfork.org/users/814159
- // @icon https://icons.iconarchive.com/icons/fatcow/farm-fresh/32/table-icon.png
- // @license Mozilla Public License 2.0
- // @grant GM_addStyle
- // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js
- // ==/UserScript==
- (function() {{
- 'use strict';
- // Add styles
- GM_addStyle(`#newTable {
- border-collapse: collapse;
- margin: 0;
- font-size: 0.9em;
- border-radius: 8px;
- overflow: hidden;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
- background: white;
- width: fit-content;
- min-width: auto;
- table-layout: fixed;
- display: table;
- direction: rtl;
- }
- /* Add styles for the table wrapper */
- .table-wrapper {
- width: 100%;
- max-width: 1100px;
- margin: 0 auto;
- overflow: hidden;
- padding: 0;
- display: flex;
- justify-content: center;
- border-radius: 8px;
- }
- #newTable thead tr {
- background: linear-gradient(135deg, #1a237e 0%, #0d47a1 100%);
- color: #ffffff;
- text-align: center;
- font-weight: bold;
- height: 60px;
- position: relative;
- box-shadow: 0 3px 6px rgba(0,0,0,0.1);
- font-feature-settings: "kern", "liga", "clig", "calt", "arab";
- -webkit-font-feature-settings: "kern", "liga", "clig", "calt", "arab";
- font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
- }
- #newTable th {
- padding: 1px;
- position: relative;
- font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
- border-left: 1px solid rgba(255,255,255,0.15);
- transition: background-color 0.3s ease;
- width: 220px; /* 1100px / 5 columns */
- }
- #newTable th:last-child {
- border-left: none;
- }
- #newTable th::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
- height: 3px;
- background: rgba(255,255,255,0.1);
- transform: scaleX(0.7);
- transition: transform 0.3s ease;
- }
- #newTable th:hover::after {
- transform: scaleX(1);
- }
- #newTable th .day-name {
- font-size: 1.3em;
- font-weight: 600;
- margin-bottom: 2px;
- text-shadow: 1px 1px 2px rgba(0,0,0,0.2);
- text-align: center;
- direction: rtl;
- font-family: "Noto Kufi Arabic", "Segoe UI", sans-serif;
- font-feature-settings: "kern", "liga", "clig", "calt";
- -webkit-font-feature-settings: "kern", "liga", "clig", "calt";
- }
- #newTable td {
- padding: 1px;
- text-align: center;
- vertical-align: middle;
- height: auto;
- width: 220px; /* 1100px / 5 columns */
- font-size: 0.85em;
- direction: rtl;
- }
- #newTable td:empty {
- padding: 0;
- height: 0;
- }
- #newTable tbody tr {
- border-bottom: 1px solid #e0e0e0;
- transition: background-color 0.3s ease;
- }
- #newTable tbody tr:hover {
- background-color: #f5f5f5;
- }
- .break-cell {
- background: linear-gradient(135deg, #f5f5f5 0%, #ffffff 100%);
- color: #424242;
- font-style: italic;
- padding: 4px;
- border-radius: 6px;
- margin: 1px;
- font-size: 1.1em;
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
- text-align: center;
- border: 1px solid #e0e0e0;
- display: inline-block;
- box-sizing: border-box;
- width: fit-content;
- margin: 0 auto;
- }
- .break-content {
- display: inline-block;
- white-space: nowrap;
- padding: 0 8px;
- }
- .lecture-cell {
- border-left: 4px solid;
- padding: 4px;
- background: #fff;
- border-radius: 6px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- margin: 1px;
- transition: all 0.2s ease;
- display: block;
- box-sizing: border-box;
- }
- .lecture-cell:hover {
- transform: translateX(-2px);
- }
- .lecture-cell strong {
- display: block;
- margin-bottom: 1px;
- font-size: 0.95em;
- }
- .lecture-cell div {
- margin: 0px;
- line-height: 1.15;
- }
- .lecture-cell > div {
- margin-bottom: 2px;
- }
- .lecture-cell .lecture-hall {
- display: block;
- background: #e8f5e9;
- padding: 4px 8px;
- border-radius: 6px;
- font-size: 1.1em;
- color: #2e7d32;
- margin-top: 5px;
- font-weight: 500;
- border: 1px solid #c8e6c9;
- text-align: center;
- }
- /* Update grid layout margins and gaps */
- .lecture-cell > div > div {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 6px;
- margin-top: 6px;
- }
- .schedule-summary {
- background: linear-gradient(45deg, #f5f5f5, #fff);
- border-radius: 8px;
- padding: 10px;
- margin: 7px auto 0;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- border: 1px solid #e0e0e0;
- width: 100%;
- box-sizing: border-box;
- min-width: auto;
- }
- .schedule-summary > div {
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 20px;
- flex-wrap: wrap;
- width: 100%;
- box-sizing: border-box;
- }
- .schedule-summary div > div {
- flex: 0 1 auto;
- min-width: fit-content;
- white-space: nowrap;
- }
- .control-buttons {
- display: flex;
- gap: 8px;
- align-items: center;
- flex-wrap: wrap;
- }
- .control-button {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 10px 20px;
- border-radius: 12px;
- font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
- font-size: 0.95em;
- font-weight: 600;
- text-align: center;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- border: none;
- background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25),
- 0 4px 8px rgba(0, 0, 0, 0.15),
- inset 0 2px 4px rgba(255, 255, 255, 0.1);
- text-decoration: none;
- margin: 4px 8px;
- min-width: 120px;
- white-space: nowrap;
- line-height: 1.5;
- cursor: pointer;
- color: white;
- position: relative;
- overflow: visible;
- }
- .control-button::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- .control-button:hover {
- transform: translateY(-2px) scale(1.02);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35),
- 0 8px 16px rgba(0, 0, 0, 0.25),
- inset 0 2px 4px rgba(255, 255, 255, 0.15);
- filter: brightness(1.1);
- }
- .control-button:hover::before {
- opacity: 1;
- }
- .control-button:active {
- transform: translateY(1px) scale(0.98);
- box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
- inset 0 2px 4px rgba(0, 0, 0, 0.1);
- }
- /* Theme buttons specific styles */
- .theme-btn, #ramadanBtn {
- min-width: 100px;
- backdrop-filter: blur(8px);
- position: relative;
- padding-right: 3rem;
- }
- .theme-btn::after, #ramadanBtn::after {
- content: '';
- position: absolute;
- right: 0.75rem;
- top: 50%;
- transform: translateY(-50%);
- width: 20px;
- height: 10px;
- border-radius: 2px;
- background: #2a2a2a;
- border: 1px solid rgba(255, 255, 255, 0.2);
- transition: all 0.3s ease;
- z-index: 1;
- box-shadow:
- inset 0 1px 2px rgba(0, 0, 0, 0.3),
- inset 0 -1px 2px rgba(255, 255, 255, 0.1);
- }
- .theme-btn.active::after, #ramadanBtn.active::after {
- background: linear-gradient(180deg,
- rgba(239, 68, 68, 1) 0%,
- rgba(239, 68, 68, 0.8) 50%,
- rgba(239, 68, 68, 0.9) 100%
- );
- border: 1px solid rgba(239, 68, 68, 0.5);
- box-shadow:
- 0 0 2px rgba(239, 68, 68, 0.4),
- 0 0 4px rgba(239, 68, 68, 0.2),
- inset 0 -2px 4px rgba(0, 0, 0, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.8);
- animation: indicatorGlow 2s infinite;
- }
- @keyframes indicatorGlow {
- 0%, 100% {
- background: linear-gradient(180deg,
- rgba(239, 68, 68, 1) 0%,
- rgba(239, 68, 68, 0.8) 50%,
- rgba(239, 68, 68, 0.9) 100%
- );
- box-shadow:
- 0 0 2px rgba(239, 68, 68, 0.4),
- 0 0 4px rgba(239, 68, 68, 0.2),
- inset 0 -2px 4px rgba(0, 0, 0, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.8);
- }
- 50% {
- background: linear-gradient(180deg,
- rgba(239, 68, 68, 0.95) 0%,
- rgba(239, 68, 68, 0.75) 50%,
- rgba(239, 68, 68, 0.85) 100%
- );
- box-shadow:
- 0 0 4px rgba(239, 68, 68, 0.6),
- 0 0 8px rgba(239, 68, 68, 0.4),
- inset 0 -2px 4px rgba(0, 0, 0, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.8);
- }
- }
- /* Set permanent colors for light theme button */
- #lightThemeBtn {
- background: linear-gradient(135deg, #451a03 0%, #582402 100%);
- }
- /* Dark theme - only modify the indicator */
- .theme-dark .theme-btn::after,
- .theme-dark #ramadanBtn::after {
- background: #1a1a1a;
- border-color: rgba(255, 255, 255, 0.1);
- box-shadow:
- inset 0 1px 3px rgba(0, 0, 0, 0.5),
- inset 0 -1px 2px rgba(255, 255, 255, 0.05);
- }
- .theme-dark .theme-btn.active::after,
- .theme-dark #ramadanBtn.active::after {
- background: linear-gradient(180deg,
- rgba(239, 68, 68, 0.9) 0%,
- rgba(239, 68, 68, 0.7) 50%,
- rgba(239, 68, 68, 0.8) 100%
- );
- border-color: rgba(239, 68, 68, 0.3);
- box-shadow:
- 0 0 4px rgba(239, 68, 68, 0.4),
- 0 0 8px rgba(239, 68, 68, 0.2),
- inset 0 -2px 4px rgba(0, 0, 0, 0.3),
- inset 0 2px 4px rgba(255, 255, 255, 0.4);
- }
- /* Remove redundant Ramadan button indicator styles */
- #ramadanBtn {
- background: linear-gradient(135deg, #5b21b6 0%, #6d28d9 100%);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.25),
- 0 4px 8px rgba(0, 0, 0, 0.15),
- inset 0 2px 4px rgba(255, 255, 255, 0.1);
- }
- #ramadanBtn::after {
- background: #2a1650;
- }
- #ramadanBtn:hover {
- box-shadow: 0 4px 12px rgba(124, 58, 237, 0.3),
- 0 8px 16px rgba(124, 58, 237, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.2);
- }
- /* Dark theme support for control buttons */
- .theme-dark .control-button {
- background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4),
- 0 4px 8px rgba(0, 0, 0, 0.3),
- inset 0 1px 1px rgba(255, 255, 255, 0.05);
- border: 1px solid rgba(255, 255, 255, 0.05);
- color: #e0e0ff;
- }
- .theme-dark .control-button:hover {
- filter: brightness(1.1);
- transform: translateY(-2px) scale(1.02);
- }
- .theme-dark .control-button:active {
- transform: translateY(1px) scale(0.98);
- }
- /* Remove the light theme button override and keep only dark theme button override */
- .theme-dark #darkThemeBtn {
- background: linear-gradient(135deg, #172554 0%, #1e3a8a 100%);
- }
- /* Add specific style for download button in both themes */
- #downloadButton {
- background: linear-gradient(135deg, #042f2e 0%, #134e4a 100%);
- }
- .theme-dark #ramadanBtn {
- background: linear-gradient(135deg, #3b0764 0%, #4c1d95 100%);
- }
- .schedule-organizer-btn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: 10px 28px;
- background: linear-gradient(135deg, #2563eb 0%, #3b82f6 100%);
- color: white;
- border-radius: 12px;
- font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
- font-size: 1.1em;
- font-weight: 600;
- text-align: center;
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
- border: none;
- box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
- 0 4px 8px rgba(37, 99, 235, 0.1),
- inset 0 2px 4px rgba(255, 255, 255, 0.1);
- text-decoration: none;
- margin: 6px;
- min-width: 160px;
- white-space: nowrap;
- line-height: 1.5;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- z-index: 100;
- visibility: visible !important;
- }
- .schedule-organizer-btn::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
- opacity: 0;
- transition: opacity 0.3s ease;
- }
- .schedule-organizer-btn:hover {
- transform: translateY(-2px) scale(1.02);
- box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3),
- 0 8px 16px rgba(37, 99, 235, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.2);
- }
- .schedule-organizer-btn:hover::before {
- opacity: 1;
- }
- .schedule-organizer-btn:active {
- transform: translateY(1px) scale(0.98);
- box-shadow: 0 2px 4px rgba(37, 99, 235, 0.2),
- inset 0 2px 4px rgba(0, 0, 0, 0.1);
- }
- .schedule-organizer-btn.active {
- background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%);
- box-shadow: 0 2px 4px rgba(220, 38, 38, 0.2),
- 0 4px 8px rgba(220, 38, 38, 0.1),
- inset 0 2px 4px rgba(255, 255, 255, 0.1);
- }
- /* Dark theme support */
- .theme-dark .schedule-organizer-btn {
- background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
- box-shadow: 0 2px 4px rgba(30, 64, 175, 0.3),
- 0 4px 8px rgba(30, 64, 175, 0.2),
- inset 0 2px 4px rgba(255, 255, 255, 0.05);
- }
- .theme-dark .schedule-organizer-btn:hover {
- box-shadow: 0 4px 12px rgba(30, 64, 175, 0.4),
- 0 8px 16px rgba(30, 64, 175, 0.3),
- inset 0 2px 4px rgba(255, 255, 255, 0.1);
- }
- .theme-dark .schedule-organizer-btn.active {
- background: linear-gradient(135deg, #b91c1c 0%, #dc2626 100%);
- }
- .schedule-summary.theme-dark {
- background: linear-gradient(45deg, #1a1a2e, #232338);
- border-color: #2e2e4a;
- color: #e0e0ff;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
- margin: 7px auto 0;
- }
- /* Dark theme loading overlay */
- #newTable.theme-dark {
- background: #151b30;
- border-color: #2e2e4a;
- box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
- margin: 0;
- }
- #newTable.theme-dark thead tr {
- background: linear-gradient(135deg, #1f2937 0%, #111827 100%);
- color: #ffffff;
- box-shadow: 0 3px 6px rgba(0, 0, 0, 0.3);
- }
- #newTable.theme-dark th {
- border-left: 1px solid rgba(255, 255, 255, 0.2);
- background: transparent;
- }
- #newTable.theme-dark th::after {
- background: rgba(255, 255, 255, 0.2);
- }
- #newTable.theme-dark th .day-name {
- color: #ffffff;
- text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
- font-weight: 600;
- font-size: 18.2px;
- }
- #newTable.theme-dark tbody tr {
- border-bottom: 1px solid #2e2e4a;
- }
- #newTable.theme-dark tbody tr:hover {
- background-color: #1c2238;
- }
- #newTable.theme-dark td {
- color: #e4e4e7;
- }
- #newTable.theme-dark .break-cell {
- background: #1c2238;
- color: #b0b0c0;
- border-color: #2e2e4a;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
- }
- #newTable.theme-dark .lecture-cell {
- background: #1c2238;
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
- }
- #newTable.theme-dark .lecture-cell strong {
- color: #e4e4e7;
- }
- #newTable.theme-dark .lecture-hall {
- background: #1e2a4a;
- color: #a0b8ff;
- border-color: #2e3f6a;
- }
- .download-group {
- display: flex;
- align-items: center;
- gap: 8px;
- background: var(--download-group-bg, #f0f0f0);
- padding: 4px;
- border-radius: 12px;
- border: 1px solid var(--download-group-border, #e0e0e0);
- transition: all 0.3s ease;
- }
- .theme-dark .download-group {
- --download-group-bg: #1f1f1f;
- --download-group-border: #333;
- }
- .download-group:hover {
- border-color: var(--download-group-hover-border, #ccc);
- box-shadow: 0 2px 8px rgba(0,0,0,0.1);
- }
- .theme-dark .download-group:hover {
- --download-group-hover-border: #444;
- }
- .custom-checkbox-container {
- display: flex;
- align-items: center;
- gap: 8px;
- background: var(--checkbox-container-bg, #f5f5f5);
- padding: 8px 16px;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.3s ease;
- margin: 0;
- user-select: none;
- }
- .theme-dark .custom-checkbox-container {
- --checkbox-container-bg: #2d2d2d;
- }
- .custom-checkbox-container:hover {
- background: var(--checkbox-container-hover-bg, #e8e8e8);
- }
- .theme-dark .custom-checkbox-container:hover {
- --checkbox-container-hover-bg: #363636;
- }
- .checkbox-wrapper {
- position: relative;
- width: 18px;
- height: 18px;
- }
- .checkbox-wrapper input {
- position: absolute;
- opacity: 0;
- cursor: pointer;
- height: 0;
- width: 0;
- }
- .checkmark {
- position: absolute;
- top: 0;
- left: 0;
- height: 18px;
- width: 18px;
- background-color: var(--checkmark-bg, #ffffff);
- border: 2px solid var(--checkmark-border, #ccc);
- border-radius: 4px;
- transition: all 0.2s ease;
- }
- .theme-dark .checkmark {
- --checkmark-bg: #404040;
- --checkmark-border: #666;
- }
- .custom-checkbox-container:hover .checkmark {
- border-color: var(--checkmark-hover-border, #4CAF50);
- }
- .theme-dark .custom-checkbox-container:hover .checkmark {
- --checkmark-hover-border: #888;
- }
- .custom-checkbox-container input:checked ~ .checkmark {
- background-color: #4CAF50;
- border-color: #4CAF50;
- }
- .custom-checkbox-container input:checked ~ .checkmark:after {
- content: '';
- position: absolute;
- left: 5px;
- top: 2px;
- width: 4px;
- height: 8px;
- border: solid white;
- border-width: 0 2px 2px 0;
- transform: rotate(45deg);
- }
- /* Remove mobile-specific styles and desktop-specific styles */
- .control-buttons {
- display: flex;
- gap: 8px;
- align-items: center;
- flex-wrap: wrap;
- }
- /* Mobile-specific styles */
- .mobile-buttons-container {
- width: 100%;
- box-sizing: border-box;
- direction: rtl;
- }
- .mobile-action-button {
- display: block;
- width: 100%;
- padding: 12px 16px;
- background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%);
- color: #ffffff !important; /* Force white text color */
- border: none;
- border-radius: 10px;
- font-family: "Segoe UI", "Traditional Arabic", Tahoma, Geneva, Verdana, sans-serif;
- font-size: 1em;
- font-weight: 600;
- text-align: center;
- margin-bottom: 8px;
- cursor: pointer;
- text-decoration: none !important; /* Prevent text decoration */
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1),
- inset 0 1px 2px rgba(255, 255, 255, 0.1);
- transition: all 0.2s ease;
- position: relative;
- overflow: hidden;
- -webkit-tap-highlight-color: transparent; /* Remove tap highlight on mobile */
- }
- .mobile-action-button::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0));
- opacity: 0;
- transition: opacity 0.2s ease;
- }
- .mobile-action-button:hover {
- background: linear-gradient(135deg, #1e3a8a 0%, #2563eb 100%);
- transform: translateY(-1px);
- box-shadow: 0 3px 6px rgba(0, 0, 0, 0.15),
- inset 0 1px 2px rgba(255, 255, 255, 0.2);
- color: #ffffff !important;
- }
- .mobile-action-button:hover::before {
- opacity: 1;
- }
- .mobile-action-button:active {
- transform: translateY(1px) scale(0.98);
- background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1),
- inset 0 2px 4px rgba(0, 0, 0, 0.2);
- color: #ffffff !important;
- }
- /* Dark theme support for mobile buttons */
- .theme-dark .mobile-action-button {
- background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2),
- inset 0 1px 2px rgba(255, 255, 255, 0.05);
- color: #ffffff !important;
- }
- .theme-dark .mobile-action-button:hover {
- background: linear-gradient(135deg, #1e293b 0%, #475569 100%);
- box-shadow: 0 3px 6px rgba(0, 0, 0, 0.25),
- inset 0 1px 2px rgba(255, 255, 255, 0.1);
- color: #ffffff !important;
- }
- .theme-dark .mobile-action-button:active {
- background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2),
- inset 0 2px 4px rgba(0, 0, 0, 0.3);
- color: #ffffff !important;
- }
- /* Hide organize button on mobile */
- @media (max-width: 768px) {
- .schedule-organizer-btn {
- display: none !important;
- }
- }`);
- // Global variables
- let rows = [];
- const days = ['الأحد','الإثنين','الثلاثاء','الأربعاء','الخميس'];
- let newTable = {};
- let newTableNode;
- let on = false;
- let ramadanMode = false;
- let colors = ["Blue", "Black", "Crimson", "Green", "Grey", "OrangeRed", "Purple", "Red", "SpringGreen", "MediumTurquoise", "Navy", "GoldenRod"];
- let subject_colors = {};
- let color_index = 0;
- let currentTheme = 'light';
- let includeSummaryInDownload = false;
- let hastebinApiKey = 'c03fb6598a7bfcde22f4eed0931e691e2c4ed173f14f1cc598016d48cca00bb3287b230ddb5a300c931220ff2bf38ba8fccf946d6a7b4ada4ac8706d2eb3dc59';
- // Time conversion functions
- function convertToRamadanTime(timeStr) {
- // Split the time range
- const [startTime, endTime] = timeStr.split(' - ');
- // Helper function to parse time
- function parseTime(time) {
- const [timeComponent, period] = time.trim().split(' ');
- const [hourStr, minuteStr] = timeComponent.split(':');
- let hour = parseInt(hourStr);
- const minute = parseInt(minuteStr);
- const isPM = period === 'م';
- if (isPM && hour !== 12) hour += 12;
- if (!isPM && hour === 12) hour = 0;
- return { hour, minute, period };
- }
- // Parse start and end times to detect practical sessions
- const start = parseTime(startTime);
- const end = parseTime(endTime);
- // Determine if it's a practical session based on duration (80 minutes)
- const duration = ((end.hour - start.hour) * 60 + (end.minute - start.minute));
- const isPractical = Math.abs(duration - 80) <= 5; // Allow 5-minute flexibility
- // Theoretical lecture time mappings
- const theoreticalMap = {
- '08:00 ص': { start: '09:30 ص', end: '10:05 ص' },
- '09:00 ص': { start: '10:10 ص', end: '10:45 ص' },
- '10:00 ص': { start: '10:50 ص', end: '11:25 ص' },
- '11:00 ص': { start: '11:30 ص', end: '12:05 م' },
- '12:00 م': { start: '12:10 م', end: '12:45 م' },
- '01:00 م': { start: '01:05 م', end: '01:40 م' },
- '02:00 م': { start: '01:45 م', end: '02:20 م' },
- '03:00 م': { start: '02:25 م', end: '03:00 م' },
- '04:00 م': { start: '03:05 م', end: '03:40 م' },
- '05:00 م': { start: '03:45 م', end: '04:20 م' },
- '06:00 م': 'غير مستخدم',
- '07:00 م': { start: '04:40 م', end: '05:15 م' }
- };
- // Practical session time mappings
- const practicalMap = {
- '08:00 ص': { start: '09:30 ص', end: '10:20 ص' },
- '09:30 ص': { start: '10:25 ص', end: '11:15 ص' },
- '11:00 ص': { start: '11:20 ص', end: '12:10 م' },
- '12:30 م': { start: '12:15 م', end: '01:05 م' },
- '02:00 م': { start: '01:30 م', end: '02:20 م' },
- '03:30 م': { start: '02:25 م', end: '03:15 م' },
- '05:00 م': { start: '03:20 م', end: '04:10 م' }
- };
- // Get the mapped time based on session type
- const timeMap = isPractical ? practicalMap : theoreticalMap;
- const mappedTime = timeMap[startTime];
- // Handle "not in use" case
- if (mappedTime === 'غير مستخدم') {
- return 'غير مستخدم';
- }
- // If no mapping found, return original time
- if (!mappedTime) {
- return timeStr;
- }
- return `${mappedTime.start} - ${mappedTime.end}`;
- }
- // Start initialization
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', init);
- } else {
- init();
- }
- // Main initialization function
- function waitForElement(selector, callback, maxTries = 100) {
- if (maxTries <= 0) {
- console.log('Element not found: ' + selector);
- return;
- }
- const element = document.getElementById(selector);
- if (element) {
- callback(element);
- return;
- }
- setTimeout(() => {
- waitForElement(selector, callback, maxTries - 1);
- }, 100);
- }
- // Remove the DOMContentLoaded listener and replace with this:
- function init() {
- waitForElement('scheduleFrm:studScheduleTable', (element) => {
- try {
- initializeTableOrganizer();
- } catch (error) {
- console.error('Error initializing table organizer:', error);
- }
- });
- }
- // Add error handling to the table check
- function initializeTableOrganizer() {
- const originalTableNode = document.getElementById('scheduleFrm:studScheduleTable');
- if (!originalTableNode) {
- console.log('Schedule table not found');
- return;
- }
- // Check if mobile device
- if (isMobileDevice()) {
- // For mobile: Add mobile buttons to main content
- const mainContent = document.querySelector('.main_content.col-md-12');
- if (mainContent) {
- const mobileButtons = createMobileButtons();
- mainContent.insertBefore(mobileButtons, mainContent.firstChild);
- }
- return; // Don't add the regular organize button for mobile
- }
- // Desktop version continues with existing code
- let button = document.createElement('span');
- let cell = document.createElement('td');
- button.classList.add("schedule-organizer-btn");
- // Set initial button state
- button.innerHTML = on ? "الجدول الاصلي" : "نظم الجدول";
- if (on) {
- button.classList.add("active");
- originalTableNode.style.display = 'none';
- if (newTableNode) {
- newTableNode.style.display = null;
- } else {
- getTableInfo();
- getNewTable();
- appendTable();
- }
- } else {
- if (newTableNode) {
- newTableNode.style.display = 'none';
- }
- }
- // Append button to table
- cell.appendChild(button);
- const printLink = document.getElementById("scheduleFrm:printLink");
- if (printLink && printLink.parentElement && printLink.parentElement.parentElement) {
- printLink.parentElement.parentElement.appendChild(cell);
- } else {
- // Fallback: append to the table directly if printLink is not found
- const firstRow = originalTableNode.querySelector('tr');
- if (firstRow) {
- firstRow.appendChild(cell);
- }
- }
- // Add click handler
- button.onclick = function() {
- if (on) {
- on = false;
- button.classList.remove("active");
- button.innerHTML = "نظم الجدول";
- originalTableNode.style.display = null;
- newTableNode.style.display = 'none';
- document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
- } else {
- on = true;
- button.classList.add("active");
- button.innerHTML = "الجدول الاصلي";
- originalTableNode.style.display = 'none';
- if (newTableNode) {
- newTableNode.style.display = null;
- document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
- let summary = createSummary();
- originalTableNode.insertAdjacentElement('afterend', summary);
- } else {
- if (rows.length == 0) {
- getTableInfo();
- }
- getNewTable();
- appendTable();
- }
- }
- };
- }
- // Helper function to get deepest text
- function endText(node) {
- if (!node.firstElementChild) {
- return node.innerHTML;
- } else {
- return endText(node.firstElementChild);
- }
- }
- // Get table information
- function getTableInfo() {
- const row1 = document.querySelectorAll(".ROW1");
- const row2 = document.querySelectorAll(".ROW2");
- function processRows(nodes) {
- for (let i = 0; i < nodes.length; i++) {
- let row_obj = {};
- let row = nodes[i];
- let cells = row.children;
- for (let j = 0; j < cells.length; j++) {
- try {
- if (cells[j].dataset.th.includes("القاعة")) {
- let headers = cells[j].dataset.th.split(/\s+/);
- let lectures = cells[j].firstElementChild.firstElementChild.children;
- row_obj["محاضرات"] = [];
- for (let k = 0; k < lectures.length; k++) {
- let data = {};
- for (let l = 0; l < headers.length; l++) {
- let currentHeader = headers[l];
- data[currentHeader] = endText(lectures[k].children[l]).trim();
- if (data[currentHeader].includes(" ")) {
- data[currentHeader] = data[currentHeader].split('; ')[1].trim().split(' ');
- }
- }
- row_obj["محاضرات"].push(data);
- }
- } else {
- let cellName = cells[j].dataset.th.trim();
- row_obj[cellName] = endText(cells[j]).trim();
- if (row_obj[cellName].includes(" ")) {
- row_obj[cellName] = row_obj[cellName].split('&')[0].trim();
- }
- }
- } catch(err) {
- console.log(err);
- }
- }
- rows.push(row_obj);
- }
- }
- processRows(row1);
- processRows(row2);
- }
- function getNewTable() {
- try {
- // Populate the new table with the days and their lectures
- for (i in days) {
- newTable[days[i]] = [];
- }
- for (i in rows) {
- let subjectLectures = rows[i]['محاضرات'];
- for (j in subjectLectures) {
- let lecture = subjectLectures[j];
- let time = lecture['الوقت'];
- // Convert time to Ramadan schedule if ramadanMode is enabled
- if (ramadanMode) {
- time = convertToRamadanTime(time);
- }
- function value(t) {
- let hour = parseInt(t.slice(0, 2), 10);
- let minutes = parseInt(t.slice(3, 5), 10);
- let total = (hour * 60) + minutes;
- if (t.slice(0, 10).includes('م') && hour != 12) {
- total += 720;
- }
- return total;
- }
- function getLectureEndTime(timeStr) {
- let parts = timeStr.split(' - ');
- return value(parts[1]);
- }
- function getLectureStartTime(timeStr) {
- let parts = timeStr.split(' - ');
- return value(parts[0]);
- }
- for (k in lecture["اليوم"]) {
- let day = days[parseInt(lecture["اليوم"][k])-1];
- newTable[day].push({
- subject: rows[i]['اسم المقرر'],
- activity: rows[i]['النشاط'],
- time: time,
- place: lecture['القاعة'],
- section: rows[i]['الشعبة'],
- value: value(time),
- endTime: getLectureEndTime(time),
- startTime: getLectureStartTime(time)
- });
- if (!(rows[i]['اسم المقرر'] in subject_colors)){
- subject_colors[rows[i]['اسم المقرر']] = colors[color_index];
- color_index++;
- }
- }
- }
- }
- // Sort lectures by time
- for (i in newTable) {
- newTable[i].sort((a, b) => a.startTime - b.startTime);
- }
- // Helper function to insert after index
- function insert_after(element, array, index) {
- let new_array = [];
- for (i = 0; i < array.length; i++) {
- if (i == index+1) {
- new_array.push(element);
- }
- new_array.push(array[i]);
- }
- return new_array;
- }
- // Add breaks between lectures
- for (d = 0; d < days.length; d++) {
- let edited_day = JSON.parse(JSON.stringify(newTable[days[d]]));
- let uni_day = newTable[days[d]];
- let skip = 0;
- for (l = 0; l < uni_day.length - 1; l++) {
- let currentLectureEnd = uni_day[l].endTime;
- let nextLectureStart = uni_day[l+1].startTime;
- let breakTime = nextLectureStart - currentLectureEnd;
- if (breakTime > 10) { // Only show breaks longer than 10 minutes
- let break_obj = {
- subject: null,
- activity: "break",
- time: null,
- place: null,
- value: breakTime
- };
- edited_day = insert_after(break_obj, edited_day, l+skip);
- skip++;
- }
- }
- newTable[days[d]] = edited_day;
- }
- } catch(err) {
- console.log(err);
- }
- }
- function getBreakText(hrs) {
- const getBreakIcon = (hrs) => {
- if (hrs >= 2) return '☕';
- if (hrs >= 1) return '⏰';
- return '⌛';
- };
- // Round down if extra minutes are 10 or less
- const wholeHours = Math.floor(hrs);
- const extraMinutes = Math.round((hrs - wholeHours) * 60);
- const roundedHours = extraMinutes <= 10 ? wholeHours : hrs;
- const icon = getBreakIcon(roundedHours);
- let duration;
- if (roundedHours === 2) {
- duration = 'ساعتين';
- } else if (roundedHours > 2) {
- duration = `${Math.floor(roundedHours)} ساعات`;
- } else if (roundedHours >= 1) {
- duration = 'ساعة';
- if (roundedHours > 1) {
- const minutes = Math.round((roundedHours - 1) * 60);
- if (minutes > 10) { // Only show minutes if more than 10
- duration += ` و ${minutes} دقيقة`;
- }
- }
- } else {
- const minutes = Math.round(roundedHours * 60);
- duration = `${minutes} دقيقة`;
- }
- return `<div class="break-content">${icon} ${duration} استراحة</div>`;
- }
- function getActivityIcon(activity) {
- if (activity.includes('عملي')) return '🔬';
- if (activity.includes('نظري')) return '📚';
- return '📖';
- }
- function getActivityStyle(activity) {
- if (activity.includes('عملي')) return 'background: #9c27b0; color: white; border-radius: 4px; padding: 2px 6px;';
- if (activity.includes('نظري')) return 'background: #1976d2; color: white; border-radius: 4px; padding: 2px 6px;';
- return 'background: #757575; color: white; border-radius: 4px; padding: 2px 6px;';
- }
- function downloadAsPNG(event) {
- if (event) {
- event.preventDefault();
- }
- //Create and show loading overlay
- const loadingOverlay = document.createElement('div');
- loadingOverlay.className = 'loading-notification';
- loadingOverlay.innerHTML = `
- <div class="notification-content">
- <div class="modern-spinner"></div>
- <div class="notification-text">
- <div class="notification-title">جار تحميل الصورة...</div>
- <div class="notification-subtitle">يرجى الانتظار بينما نقوم بمعالجة الجدول</div>
- </div>
- </div>
- `;
- // Add styles for the notification
- const style = document.createElement('style');
- style.textContent = `
- .loading-notification {
- position: fixed;
- top: 20px;
- right: 20px;
- background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
- border: 1px solid ${currentTheme === 'dark' ? '#333' : '#e0e0e0'};
- border-radius: 12px;
- padding: 16px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- z-index: 10000;
- max-width: 300px;
- animation: slideIn 0.3s ease-out;
- backdrop-filter: blur(10px);
- }
- .notification-content {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .notification-text {
- flex: 1;
- }
- .notification-title {
- color: ${currentTheme === 'dark' ? '#ffffff' : '#000000'};
- font-weight: 600;
- margin-bottom: 4px;
- }
- .notification-subtitle {
- color: ${currentTheme === 'dark' ? '#888' : '#666'};
- font-size: 0.9em;
- }
- .modern-spinner {
- width: 24px;
- height: 24px;
- border: 3px solid ${currentTheme === 'dark' ? '#333' : '#f0f0f0'};
- border-top: 3px solid ${currentTheme === 'dark' ? '#4CAF50' : '#2196F3'};
- border-radius: 50%;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- @keyframes slideIn {
- from {
- opacity: 0;
- transform: translateX(100px);
- }
- to {
- opacity: 1;
- transform: translateX(0);
- }
- }
- @keyframes slideOut {
- from {
- opacity: 1;
- transform: translateX(0);
- }
- to {
- opacity: 0;
- transform: translateX(100px);
- }
- }
- `;
- document.head.appendChild(style);
- document.body.appendChild(loadingOverlay);
- const element = document.getElementById('newTable');
- const summary = document.querySelector('.schedule-summary');
- // Create filename based on mode
- const filename = ramadanMode ? 'الجدول_الدراسي_توقيت_رمضان.png' : 'الجدول_الدراسي.png';
- // Calculate the maximum width needed
- const tableWidth = element.offsetWidth;
- const summaryWidth = summary ? summary.offsetWidth : 0;
- const maxWidth = Math.max(tableWidth, summaryWidth);
- const wrapper = document.createElement('div');
- wrapper.style.cssText = `
- background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
- direction: rtl;
- width: ${maxWidth}px;
- margin: 0;
- border-radius: 0;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- position: relative;
- `;
- // Only include summary if checkbox is checked
- if (includeSummaryInDownload && summary) {
- const summaryClone = summary.cloneNode(true);
- // Remove control buttons from summary clone
- const controlButtons = summaryClone.querySelector('.control-buttons');
- if (controlButtons) controlButtons.remove();
- // Remove theme buttons and download button
- summaryClone.querySelectorAll('.control-button, .theme-btn, label').forEach(btn => btn.remove());
- // Ensure summary maintains consistent width
- summaryClone.style.cssText = `
- width: ${maxWidth}px;
- margin: 0;
- box-sizing: border-box;
- background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
- `;
- wrapper.appendChild(summaryClone);
- }
- const tableClone = element.cloneNode(true);
- // If in Ramadan mode, find a suitable cell for the indicator
- if (ramadanMode) {
- // Try to find an empty or break cell in the middle of the table
- const rows = tableClone.querySelectorAll('tbody tr');
- let indicatorPlaced = false;
- // Calculate middle row
- const middleRowIndex = Math.floor(rows.length / 2);
- // First try: Look in the middle row
- if (rows[middleRowIndex]) {
- const cells = rows[middleRowIndex].children;
- for (let cell of cells) {
- if (!cell.innerHTML.trim() || cell.innerHTML.includes('استراحة')) {
- const ramadanIndicator = `
- <div style="
- background: ${currentTheme === 'dark' ?
- 'linear-gradient(135deg, #2d1f3d 0%, #1a1a2e 100%)' :
- 'linear-gradient(135deg, #f3e5f5 0%, #e8eaf6 100%)'
- };
- padding: 12px 24px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12px;
- font-size: 1.2em;
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
- '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
- };
- margin: 10px auto;
- width: fit-content;
- border: 2px solid ${currentTheme === 'dark' ? '#4a3f6b' : '#e1bee7'};
- animation: ramadanGlow 2s ease-in-out infinite;
- ">
- <span style="font-size: 1.4em;">🌙</span>
- <span style="
- color: ${currentTheme === 'dark' ? '#fff' : '#000'};
- font-weight: 500;
- ">توقيت رمضان</span>
- </div>
- <style>
- @keyframes ramadanGlow {
- 0%, 100% {
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
- '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
- };
- }
- 50% {
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 20px rgba(123, 97, 255, 0.3), 0 0 30px rgba(123, 97, 255, 0.2)' :
- '0 4px 20px rgba(156, 39, 176, 0.2), 0 0 30px rgba(156, 39, 176, 0.1)'
- };
- }
- }
- </style>
- `;
- cell.innerHTML = ramadanIndicator;
- indicatorPlaced = true;
- break;
- }
- }
- }
- // Second try: Look in adjacent rows if middle row didn't work
- if (!indicatorPlaced) {
- for (let offset = 1; offset <= 2; offset++) {
- const rowsToTry = [
- rows[middleRowIndex - offset],
- rows[middleRowIndex + offset]
- ];
- for (const row of rowsToTry) {
- if (!row) continue;
- const cells = row.children;
- for (let cell of cells) {
- if (!cell.innerHTML.trim() || cell.innerHTML.includes('استراحة')) {
- const ramadanIndicator = `
- <div style="
- background: ${currentTheme === 'dark' ?
- 'linear-gradient(135deg, #2d1f3d 0%, #1a1a2e 100%)' :
- 'linear-gradient(135deg, #f3e5f5 0%, #e8eaf6 100%)'
- };
- padding: 12px 24px;
- border-radius: 12px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12px;
- font-size: 1.2em;
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
- '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
- };
- margin: 10px auto;
- width: fit-content;
- border: 2px solid ${currentTheme === 'dark' ? '#4a3f6b' : '#e1bee7'};
- animation: ramadanGlow 2s ease-in-out infinite;
- ">
- <span style="font-size: 1.4em;">🌙</span>
- <span style="
- color: ${currentTheme === 'dark' ? '#fff' : '#000'};
- font-weight: 500;
- ">توقيت رمضان</span>
- </div>
- <style>
- @keyframes ramadanGlow {
- 0%, 100% {
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 15px rgba(123, 97, 255, 0.2), 0 0 20px rgba(123, 97, 255, 0.1)' :
- '0 4px 15px rgba(156, 39, 176, 0.1), 0 0 20px rgba(156, 39, 176, 0.05)'
- };
- }
- 50% {
- box-shadow: ${currentTheme === 'dark' ?
- '0 4px 20px rgba(123, 97, 255, 0.3), 0 0 30px rgba(123, 97, 255, 0.2)' :
- '0 4px 20px rgba(156, 39, 176, 0.2), 0 0 30px rgba(156, 39, 176, 0.1)'
- };
- }
- }
- </style>
- `;
- cell.innerHTML = ramadanIndicator;
- indicatorPlaced = true;
- break;
- }
- }
- if (indicatorPlaced) break;
- }
- if (indicatorPlaced) break;
- }
- }
- }
- wrapper.appendChild(tableClone);
- document.body.appendChild(wrapper);
- // Enhanced style preservation
- const preserveStyles = (element) => {
- const computedStyle = window.getComputedStyle(element);
- const importantStyles = [
- 'font-family', 'font-size', 'font-weight', 'color', 'background',
- 'padding', 'margin', 'border', 'text-align', 'direction',
- 'display', 'width', 'height', 'border-radius', 'box-shadow',
- 'grid-template-columns', 'gap', 'background-color', 'border-color',
- 'border-width', 'border-style', 'line-height', 'letter-spacing',
- 'text-decoration', 'text-transform', 'vertical-align', 'position',
- 'top', 'left', 'right', 'bottom', 'z-index', 'opacity',
- 'transform', 'transition', 'box-sizing', 'overflow'
- ];
- let styleString = importantStyles.map(property =>
- `${property}:${computedStyle.getPropertyValue(property)}`
- ).join(';');
- // Preserve existing inline styles
- if (element.style.cssText) {
- styleString += ';' + element.style.cssText;
- }
- element.style.cssText = styleString;
- Array.from(element.children).forEach(preserveStyles);
- };
- preserveStyles(wrapper);
- // Use fixed scale of 7
- const scale = 7;
- html2canvas(wrapper, {
- backgroundColor: '#ffffff',
- scale: scale,
- logging: false,
- useCORS: true,
- allowTaint: true,
- width: wrapper.offsetWidth,
- height: wrapper.offsetHeight,
- onclone: function(clonedDoc) {
- const clonedWrapper = clonedDoc.body.lastChild;
- preserveStyles(clonedWrapper);
- }
- }).then(canvas => {
- wrapper.remove();
- // Add slide out animation before removing
- loadingOverlay.style.animation = 'slideOut 0.3s ease-in';
- setTimeout(() => {
- loadingOverlay.remove();
- style.remove();
- }, 300);
- try {
- const image = canvas.toDataURL('image/png', 1.0);
- const link = document.createElement('a');
- link.download = filename; // Use the new filename
- link.href = image;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- } catch (error) {
- console.error('Error saving image:', error);
- alert('خطأ في حفظ الصورة. يرجى المحاولة مرة أخرى.');
- }
- }).catch(error => {
- console.error('Error generating PNG:', error);
- // Add slide out animation before removing
- loadingOverlay.style.animation = 'slideOut 0.3s ease-in';
- setTimeout(() => {
- loadingOverlay.remove();
- style.remove();
- }, 300);
- if (error.message.includes('memory')) {
- alert('خطأ: الصورة كبيرة جداً. جاري المحاولة بجودة أقل...');
- setTimeout(() => {
- html2canvas(wrapper, {
- backgroundColor: '#ffffff',
- scale: 6,
- logging: false,
- useCORS: true,
- allowTaint: true,
- width: wrapper.offsetWidth,
- height: wrapper.offsetHeight
- }).then(canvas => {
- const image = canvas.toDataURL('image/png', 1.0);
- const link = document.createElement('a');
- link.download = filename; // Use the new filename
- link.href = image;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- });
- }, 100);
- } else {
- alert('حدث خطأ أثناء إنشاء الصورة. يرجى المحاولة مرة أخرى.');
- }
- wrapper.remove();
- });
- }
- function toggleTheme(theme) {
- currentTheme = theme;
- const table = document.getElementById('newTable');
- if (!table) return;
- table.classList.remove('theme-light', 'theme-dark');
- table.classList.add(`theme-${theme}`);
- // Update summary section theme
- const summary = document.querySelector('.schedule-summary');
- if (summary) {
- summary.classList.remove('theme-light', 'theme-dark');
- summary.classList.add(`theme-${theme}`);
- // Update button states
- const lightThemeBtn = summary.querySelector('#lightThemeBtn');
- const darkThemeBtn = summary.querySelector('#darkThemeBtn');
- if (lightThemeBtn) {
- lightThemeBtn.classList.toggle('active', theme === 'light');
- }
- if (darkThemeBtn) {
- darkThemeBtn.classList.toggle('active', theme === 'dark');
- }
- }
- }
- function createSummary() {
- let summary = document.createElement('div');
- summary.classList.add('schedule-summary', `theme-${currentTheme}`);
- let totalHours = 0;
- let subjectCount = new Set();
- let daysWithClasses = new Set();
- let maxLectures = 0;
- let busyDays = [];
- for (let day in newTable) {
- let dayLectures = newTable[day].filter(slot => slot.activity !== "break");
- if (dayLectures.length > 0) {
- daysWithClasses.add(day);
- if (dayLectures.length > maxLectures) {
- maxLectures = dayLectures.length;
- busyDays = [day];
- } else if (dayLectures.length === maxLectures) {
- busyDays.push(day);
- }
- }
- dayLectures.forEach(slot => {
- totalHours += (slot.time ? 1 : 0);
- subjectCount.add(slot.subject);
- });
- }
- // Desktop UI
- summary.innerHTML = `
- <div style="display: flex; align-items: center; justify-content: center; gap: 20px; flex-wrap: wrap;">
- <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#1a2f4d' : '#e3f2fd'}; padding: 8px 16px; border-radius: 8px;">
- <span style="font-weight: 500;">📚 المواد:</span>
- <span>${subjectCount.size}</span>
- </div>
- <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#2d1f3d' : '#f3e5f5'}; padding: 8px 16px; border-radius: 8px;">
- <span style="font-weight: 500;">⏰ الساعات:</span>
- <span>${totalHours}</span>
- </div>
- <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#1f3d2d' : '#e8f5e9'}; padding: 8px 16px; border-radius: 8px;">
- <span style="font-weight: 500;">📅 أيام الدراسة:</span>
- <span>${daysWithClasses.size}</span>
- </div>
- <div style="display: flex; align-items: center; gap: 4px; background: ${currentTheme === 'dark' ? '#3d2d1f' : '#fff3e0'}; padding: 8px 16px; border-radius: 8px;">
- <span style="font-weight: 500;">📊 اليوم الأكثر:</span>
- <span>${busyDays.join(', ')} (${maxLectures})</span>
- </div>
- <div class="control-buttons">
- <button class="control-button theme-btn ${currentTheme === 'light' ? 'active' : ''}" id="lightThemeBtn">
- ☀️ فاتح
- </button>
- <button class="control-button theme-btn ${currentTheme === 'dark' ? 'active' : ''}" id="darkThemeBtn">
- 🌙 داكن
- </button>
- <button class="control-button ${ramadanMode ? 'active' : ''}" id="ramadanBtn">
- 🕌 توقيت رمضان
- </button>
- <div class="download-group">
- <button class="control-button" id="downloadButton">
- 💾 تحميل كصورة
- </button>
- <label class="custom-checkbox-container">
- <div class="checkbox-wrapper">
- <input type="checkbox" id="includeSummaryCheckbox" ${includeSummaryInDownload ? 'checked' : ''}>
- <span class="checkmark"></span>
- </div>
- <span>تضمين الملخص</span>
- </label>
- </div>
- </div>
- </div>
- `;
- setTimeout(() => {
- const downloadButton = summary.querySelector('#downloadButton');
- const lightThemeBtn = summary.querySelector('#lightThemeBtn');
- const darkThemeBtn = summary.querySelector('#darkThemeBtn');
- const ramadanBtn = summary.querySelector('#ramadanBtn');
- const includeSummaryCheckbox = summary.querySelector('#includeSummaryCheckbox');
- if (downloadButton) {
- downloadButton.addEventListener('click', downloadAsPNG);
- }
- if (lightThemeBtn) {
- lightThemeBtn.addEventListener('click', () => {
- toggleTheme('light');
- appendTable();
- });
- }
- if (darkThemeBtn) {
- darkThemeBtn.addEventListener('click', () => {
- toggleTheme('dark');
- appendTable();
- });
- }
- if (ramadanBtn) {
- ramadanBtn.addEventListener('click', () => {
- ramadanMode = !ramadanMode;
- ramadanBtn.classList.toggle('active');
- getNewTable();
- appendTable();
- });
- }
- if (includeSummaryCheckbox) {
- includeSummaryCheckbox.addEventListener('change', (e) => {
- includeSummaryInDownload = e.target.checked;
- });
- }
- }, 0);
- return summary;
- }
- function appendTable() {
- // Remove any existing organized tables and summaries
- if (newTableNode) {
- newTableNode.remove();
- }
- document.querySelectorAll('.schedule-summary').forEach(el => el.remove());
- const originalTableNode = document.getElementById('scheduleFrm:studScheduleTable');
- // Continue with normal table creation
- let table = document.createElement('table');
- table.id = "newTable";
- table.classList.add('rowFlow', `theme-${currentTheme}`);
- table.cellPadding = '0';
- table.cellSpacing = '0';
- table.border = '1';
- // Create a wrapper div for the table
- const tableWrapper = document.createElement('div');
- tableWrapper.className = 'table-wrapper';
- originalTableNode.insertAdjacentElement('afterend', tableWrapper);
- tableWrapper.appendChild(table);
- let thead = document.createElement('thead');
- let tbody = document.createElement('tbody');
- table.appendChild(thead);
- table.appendChild(tbody);
- const dayNamesEn = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday'];
- days.forEach((day, i) => {
- let th = document.createElement('th');
- th.innerHTML = `
- <div class="day-name">${day}</div>
- `;
- th.classList.add('HEADING');
- th.scope = "col";
- thead.appendChild(th);
- });
- function maxDayLength(obj) {
- return Math.max(...Object.values(obj).map(day => day.length));
- }
- const maxLength = maxDayLength(newTable);
- // Create empty rows
- for (let i = 0; i < maxLength; i++) {
- let tr = document.createElement('tr');
- tbody.appendChild(tr);
- for (let j = 0; j < days.length; j++) {
- let td = document.createElement('td');
- tr.appendChild(td);
- }
- }
- let trs = tbody.children;
- days.forEach((day, i) => {
- let currentDay = newTable[day];
- currentDay.forEach((lecture, j) => {
- if (lecture.activity == "break") {
- let hrs = lecture.value/60;
- trs[j].children[i].innerHTML = `<div class="break-cell">${getBreakText(hrs)}</div>`;
- } else {
- let subjectColor = subject_colors[lecture.subject];
- // Adjust color for dark mode if needed
- if (currentTheme === 'dark') {
- // Make the color more visible in dark mode
- subjectColor = adjustColorForDarkMode(subjectColor);
- }
- let activityStyle = getActivityStyle(lecture.activity);
- if (currentTheme === 'dark') {
- activityStyle = activityStyle.replace('background: #9c27b0', 'background: #4a1259')
- .replace('background: #1976d2', 'background: #1a3f6b')
- .replace('background: #757575', 'background: #3d3d3d');
- }
- let content = `<div style="margin-bottom: 2px;">
- <strong style="font-size: 1.05em; color: ${currentTheme === 'dark' ? '#e4e4e7' : 'inherit'}">${lecture.subject}</strong>
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px;">
- <div style="text-align: right;">
- <div style="${activityStyle}">
- ${getActivityIcon(lecture.activity)} ${lecture.activity}
- </div>
- <div style="background: ${currentTheme === 'dark' ? '#1a2f3a' : '#e8eaf6'}; border-radius: 6px; padding: 3px 4px; color: ${currentTheme === 'dark' ? '#8ebbff' : '#283593'}; display: inline-block; margin-top: 3px;">
- 🔢 الشعبة: ${lecture.section}
- </div>
- </div>
- <div style="text-align: left;">
- <div style="font-weight: bold; color: ${currentTheme === 'dark' ? '#8ebbff' : '#1a237e'}; white-space: nowrap; font-size: 0.95em;">${formatTimeDisplay(lecture.time)}</div>
- <div class="lecture-hall">🏛️ ${lecture.place}</div>
- </div>
- </div>
- </div>`;
- trs[j].children[i].innerHTML = `<div class="lecture-cell" style="border-left-color: ${subjectColor};">${content}</div>`;
- }
- });
- });
- newTableNode = table;
- let summary = createSummary();
- summary.style.cssText = `
- width: 100%;
- max-width: 1100px;
- margin: 5px auto;
- overflow-x: auto;
- display: block;
- `;
- originalTableNode.insertAdjacentElement('afterend', summary);
- }
- // Helper function to adjust colors for dark mode
- function adjustColorForDarkMode(color) {
- // Convert color to RGB if it's a named color
- let tempDiv = document.createElement('div');
- tempDiv.style.color = color;
- document.body.appendChild(tempDiv);
- let rgbColor = window.getComputedStyle(tempDiv).color;
- document.body.removeChild(tempDiv);
- // Parse RGB values
- let rgb = rgbColor.match(/\d+/g).map(Number);
- // Calculate luminance
- let luminance = (0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2]) / 255;
- // For very dark colors (especially black or near-black)
- if (luminance < 0.2) {
- // Convert to a light gray-blue tint
- return `rgb(176, 196, 222)`; // Light steel blue
- }
- // For dark colors
- if (luminance < 0.5) {
- // Increase brightness more significantly
- let adjustedRgb = rgb.map(value => {
- return Math.min(255, value + 80);
- });
- return `rgb(${adjustedRgb.join(',')})`;
- }
- // For already light colors, just slight adjustment
- let adjustedRgb = rgb.map(value => {
- return Math.min(255, value + 40);
- });
- return `rgb(${adjustedRgb.join(',')})`;
- }
- // Add this helper function before appendTable()
- function formatTimeDisplay(timeStr) {
- // Split the time range
- const [startTime, endTime] = timeStr.split(' - ');
- // Split each time into components
- const [startTimeComponent, startPeriod] = startTime.trim().split(' ');
- const [endTimeComponent, endPeriod] = endTime.trim().split(' ');
- // If both periods are the same, show it only once at the end
- if (startPeriod === endPeriod) {
- return `${startTimeComponent} - ${endTimeComponent} ${startPeriod}`;
- }
- // If periods are different, keep both but make it more compact
- return `${startTimeComponent}${startPeriod} - ${endTimeComponent}${endPeriod}`;
- }
- // Add mobile detection function
- function isMobileDevice() {
- return (window.innerWidth <= 768);
- }
- // Add notification function
- function showNotification(title, subtitle, type = 'success', duration = 3000) {
- const notification = document.createElement('div');
- notification.className = 'loading-notification';
- // Set icon based on type
- const icon = type === 'success' ? '✅' : type === 'error' ? '❌' : '⚠️';
- notification.innerHTML = `
- <div class="notification-content">
- <div class="notification-icon">${icon}</div>
- <div class="notification-text">
- <div class="notification-title">${title}</div>
- ${subtitle ? `<div class="notification-subtitle">${subtitle}</div>` : ''}
- </div>
- </div>
- `;
- // Add styles for the notification
- const style = document.createElement('style');
- style.textContent = `
- .loading-notification {
- position: fixed;
- top: 20px;
- right: 20px;
- background: ${currentTheme === 'dark' ? '#1a1a1a' : '#ffffff'};
- border: 1px solid ${currentTheme === 'dark' ? '#333' : '#e0e0e0'};
- border-radius: 12px;
- padding: 16px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
- z-index: 10000;
- max-width: 300px;
- animation: slideIn 0.3s ease-out;
- backdrop-filter: blur(10px);
- }
- .notification-content {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .notification-icon {
- font-size: 24px;
- line-height: 1;
- }
- .notification-text {
- flex: 1;
- }
- .notification-title {
- color: ${currentTheme === 'dark' ? '#ffffff' : '#000000'};
- font-weight: 600;
- margin-bottom: 4px;
- }
- .notification-subtitle {
- color: ${currentTheme === 'dark' ? '#888' : '#666'};
- font-size: 0.9em;
- }
- @keyframes slideIn {
- from {
- opacity: 0;
- transform: translateX(100px);
- }
- to {
- opacity: 1;
- transform: translateX(0);
- }
- }
- @keyframes slideOut {
- from {
- opacity: 1;
- transform: translateX(0);
- }
- to {
- opacity: 0;
- transform: translateX(100px);
- }
- }
- `;
- document.head.appendChild(style);
- document.body.appendChild(notification);
- // Remove notification after duration
- setTimeout(() => {
- notification.style.animation = 'slideOut 0.3s ease-in';
- setTimeout(() => {
- notification.remove();
- style.remove();
- }, 300);
- }, duration);
- }
- // Update copyScheduleJSON function
- async function copyScheduleJSON() {
- try {
- if (rows.length === 0) {
- getTableInfo();
- getNewTable();
- }
- // Create a cleaned version of the schedule without breaks and time values
- const cleanedSchedule = {};
- for (const day in newTable) {
- cleanedSchedule[day] = newTable[day]
- .filter(lecture => lecture.activity !== "break")
- .map(lecture => ({
- subject: lecture.subject,
- activity: lecture.activity,
- time: lecture.time,
- place: lecture.place,
- section: lecture.section
- }));
- }
- // Create the final format
- const formattedData = {
- subjects: Array.from(new Set(rows.map(row => row['اسم المقرر']).filter(Boolean))),
- days: days,
- schedule: cleanedSchedule
- };
- const scheduleData = JSON.stringify(formattedData);
- // Try to create a Hastebin paste
- try {
- // Prepare headers with authentication if API key is available
- const headers = {
- 'Content-Type': 'text/plain'
- };
- // Add authorization header if API key is available
- if (hastebinApiKey) {
- headers['Authorization'] = `Bearer ${hastebinApiKey}`;
- }
- // Using Hastebin API (hastebin.com)
- const hastebinResponse = await fetch('https://hastebin.com/documents', {
- method: 'POST',
- headers: headers,
- body: scheduleData
- });
- if (!hastebinResponse.ok) {
- throw new Error(`Hastebin creation failed: ${hastebinResponse.status}`);
- }
- const hasteData = await hastebinResponse.json();
- const hasteKey = hasteData.key;
- // Open the website with the hastebin key
- const baseUrl = 'https://jkc66.github.io/IU_Table_Organizer/cptable.html';
- window.open(`${baseUrl}?haste=${hasteKey}`, '_blank');
- showNotification('تم فتح منظم الجدول! ✨', 'تم نقل البيانات تلقائياً', 'success');
- } catch (hasteError) {
- console.error('Hastebin error:', hasteError);
- // If hastebin creation fails, fall back to copy method
- fallbackCopy(scheduleData);
- }
- } catch (error) {
- console.error('Processing error:', error);
- showNotification('حدث خطأ في معالجة الجدول ❌', 'جاري المحاولة بطريقة بديلة...', 'error');
- fallbackCopy(scheduleData);
- }
- }
- // New helper function for clipboard operations
- function copyToClipboard(text) {
- // Try the modern Clipboard API first
- if (navigator.clipboard && window.isSecureContext) {
- navigator.clipboard.writeText(text)
- .then(() => {
- console.log('Clipboard API: Copy successful');
- return true;
- })
- .catch(err => {
- console.error('Clipboard API failed:', err);
- return fallbackCopyToClipboard(text);
- });
- } else {
- return fallbackCopyToClipboard(text);
- }
- }
- function fallbackCopyToClipboard(text) {
- const textarea = document.createElement('textarea');
- textarea.value = text;
- document.body.appendChild(textarea);
- textarea.select();
- try {
- const success = document.execCommand('copy');
- console.log('Fallback copy success:', success);
- return success;
- } catch (e) {
- console.error('Fallback copy failed:', e);
- return false;
- } finally {
- document.body.removeChild(textarea);
- }
- }
- // Update fallback copy to use new helper
- function fallbackCopy(scheduleData) {
- console.log('Entering fallback copy with data length:', scheduleData.length);
- const success = copyToClipboard(scheduleData);
- if (success) {
- showNotification('تم نسخ البيانات بنجاح! ✨', 'يرجى فتح موقع منظم الجدول ولصق البيانات هناك', 'success');
- } else {
- showNotification('حدث خطأ أثناء النسخ ❌', 'يرجى المحاولة مرة أخرى لاحقاً', 'error');
- }
- // Open the website in a new tab
- window.open('https://jkc66.github.io/IU_Table_Organizer/cptable.html', '_blank');
- }
- // Add function to create mobile buttons
- function createMobileButtons() {
- const mobileButtonsContainer = document.createElement('div');
- mobileButtonsContainer.className = 'mobile-buttons-container';
- mobileButtonsContainer.style.cssText = `
- display: flex;
- flex-direction: column;
- gap: 10px;
- padding: 15px;
- background: #f5f5f5;
- border-radius: 12px;
- margin: 10px 0;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- `;
- // Create View Table button
- const viewTableButton = document.createElement('button');
- viewTableButton.className = 'mobile-action-button';
- viewTableButton.innerHTML = '📱 فتح منظم الجدول';
- viewTableButton.onclick = copyScheduleJSON; // This will now handle both copying and redirecting
- mobileButtonsContainer.appendChild(viewTableButton);
- return mobileButtonsContainer;
- }
- }})();