coursewatcher 1.0.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,15 @@ npx coursewatcher
20
20
  npx coursewatcher --port 8080 --no-browser
21
21
  ```
22
22
 
23
+ ## CLI Options
24
+
25
+ | Argument | Description | Default |
26
+ |----------|-------------|---------|
27
+ | `[path]` | Path to course directory | `.` (Current directory) |
28
+ | `-p, --port` | Port to run the server on | `3000` |
29
+ | `--no-browser` | Prevent browser from opening automatically | `false` |
30
+
31
+
23
32
  ## Installation
24
33
 
25
34
  ```bash
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "coursewatcher",
3
- "version": "1.0.1",
3
+ "version": "1.3.0",
4
4
  "description": "A CLI tool and web interface for tracking progress in downloaded video courses",
5
5
  "main": "src/server.js",
6
6
  "bin": {
7
- "coursewatcher": "./bin/coursewatcher.js"
7
+ "coursewatcher": "./src/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node bin/coursewatcher.js",
11
- "dev": "node bin/coursewatcher.js",
10
+ "start": "node src/cli.js",
11
+ "dev": "node src/cli.js",
12
12
  "test": "jest",
13
13
  "test:coverage": "jest --coverage",
14
14
  "test:watch": "jest --watch"
@@ -34,7 +34,6 @@
34
34
  "url": "https://github.com/serhiishtokal/CourseWatcher/issues"
35
35
  },
36
36
  "files": [
37
- "bin/",
38
37
  "src/",
39
38
  "views/",
40
39
  "public/"
@@ -45,7 +44,8 @@
45
44
  "commander": "^12.1.0",
46
45
  "ejs": "^3.1.10",
47
46
  "express": "^4.21.1",
48
- "open": "^8.4.2"
47
+ "open": "^8.4.2",
48
+ "plyr": "^3.8.4"
49
49
  },
50
50
  "devDependencies": {
51
51
  "jest": "^29.7.0",
@@ -54,4 +54,4 @@
54
54
  "engines": {
55
55
  "node": ">=18.0.0"
56
56
  }
57
- }
57
+ }
@@ -504,7 +504,7 @@ a:hover {
504
504
  ========================================== */
505
505
 
506
506
  .player-container {
507
- max-width: 1400px;
507
+ max-width: 1600px;
508
508
  }
509
509
 
510
510
  .player-section {
@@ -614,91 +614,23 @@ a:hover {
614
614
  color: white;
615
615
  }
616
616
 
617
- .playback-controls {
618
- display: flex;
619
- gap: var(--space-xl);
620
- flex-wrap: wrap;
621
- }
622
-
623
- .control-group {
624
- display: flex;
625
- align-items: center;
626
- gap: var(--space-sm);
627
- }
628
-
629
- .control-label {
630
- color: var(--color-text-muted);
631
- font-size: 0.85rem;
632
- margin-right: var(--space-sm);
633
- }
634
-
635
- .control-btn {
636
- padding: var(--space-xs) var(--space-md);
637
- background: var(--color-bg-secondary);
638
- border: 1px solid var(--color-border);
639
- border-radius: var(--radius-md);
640
- color: var(--color-text);
641
- cursor: pointer;
642
- font-size: 0.8rem;
643
- transition: all var(--transition-fast);
644
- }
645
-
646
- .control-btn:hover {
647
- background: var(--color-primary);
648
- border-color: var(--color-primary);
649
- }
650
-
651
- .control-value {
652
- min-width: 50px;
653
- text-align: center;
654
- font-weight: 600;
655
- color: var(--color-primary);
656
- }
657
-
658
617
  /* ==========================================
659
- Keyboard Shortcuts
618
+ Plyr Customization
660
619
  ========================================== */
661
620
 
662
- .shortcuts-help {
663
- margin-top: var(--space-lg);
664
- padding-top: var(--space-lg);
665
- border-top: 1px solid var(--color-border);
666
- }
667
-
668
- .shortcuts-help summary {
669
- cursor: pointer;
670
- color: var(--color-text-secondary);
671
- font-size: 0.9rem;
672
- user-select: none;
673
- }
674
-
675
- .shortcuts-grid {
676
- display: grid;
677
- grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
678
- gap: var(--space-sm);
679
- margin-top: var(--space-md);
680
- }
681
-
682
- .shortcut {
683
- display: flex;
684
- align-items: center;
685
- gap: var(--space-sm);
686
- font-size: 0.85rem;
687
- color: var(--color-text-secondary);
621
+ :root {
622
+ --plyr-color-main: var(--color-primary);
623
+ --plyr-video-background: #000;
624
+ --plyr-menu-background: var(--color-surface);
625
+ --plyr-menu-color: var(--color-text);
626
+ --plyr-menu-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
627
+ --plyr-font-family: var(--font-sans);
688
628
  }
689
629
 
690
- kbd {
691
- display: inline-flex;
692
- align-items: center;
693
- justify-content: center;
694
- min-width: 28px;
695
- padding: var(--space-xs) var(--space-sm);
696
- background: var(--color-bg-secondary);
697
- border: 1px solid var(--color-border);
698
- border-radius: var(--radius-sm);
699
- font-family: var(--font-mono);
700
- font-size: 0.75rem;
701
- color: var(--color-text);
630
+ .plyr--video {
631
+ border-radius: var(--radius-lg);
632
+ overflow: hidden;
633
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
702
634
  }
703
635
 
704
636
  /* ==========================================
@@ -932,4 +864,512 @@ kbd {
932
864
  justify-content: center;
933
865
  min-width: 120px;
934
866
  }
867
+ }
868
+
869
+ /* ==========================================
870
+ Video Player Overlay Controls
871
+ ========================================== */
872
+
873
+ .video-wrapper {
874
+ position: relative;
875
+ overflow: hidden;
876
+ /* Ensure controls don't spill out */
877
+ group: video-group;
878
+ /* For potential future container queries */
879
+ background: black;
880
+ /* Prevent white flashes */
881
+ }
882
+
883
+ .video-wrapper:hover .overlay-controls,
884
+ .video-wrapper:focus-within .overlay-controls,
885
+ .video-player.paused+.overlay-controls,
886
+ .overlay-controls:hover {
887
+ opacity: 1;
888
+ pointer-events: auto;
889
+ }
890
+
891
+ .overlay-controls {
892
+ position: absolute;
893
+ bottom: 0;
894
+ left: 0;
895
+ right: 0;
896
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0.6) 60%, transparent 100%);
897
+ padding: var(--space-md);
898
+ opacity: 0;
899
+ transition: opacity var(--transition-normal);
900
+ pointer-events: none;
901
+ /* Let clicks pass through when hidden */
902
+ display: flex;
903
+ flex-direction: column;
904
+ gap: var(--space-sm);
905
+ z-index: 10;
906
+ }
907
+
908
+ .controls-row {
909
+ display: flex;
910
+ align-items: center;
911
+ justify-content: space-between;
912
+ gap: var(--space-md);
913
+ }
914
+
915
+ .control-group {
916
+ display: flex;
917
+ align-items: center;
918
+ gap: var(--space-md);
919
+ }
920
+
921
+ .control-group.right {
922
+ justify-content: flex-end;
923
+ }
924
+
925
+ /* Icon Buttons */
926
+ .control-btn-icon {
927
+ background: transparent;
928
+ border: none;
929
+ color: white;
930
+ font-size: 1.2rem;
931
+ cursor: pointer;
932
+ padding: var(--space-xs);
933
+ border-radius: var(--radius-sm);
934
+ transition: all var(--transition-fast);
935
+ display: flex;
936
+ align-items: center;
937
+ justify-content: center;
938
+ width: 32px;
939
+ height: 32px;
940
+ }
941
+
942
+ .control-btn-icon:hover {
943
+ background: rgba(255, 255, 255, 0.2);
944
+ transform: scale(1.1);
945
+ }
946
+
947
+ /* Text Buttons (Speed) */
948
+ .control-btn-text {
949
+ background: rgba(255, 255, 255, 0.1);
950
+ border: 1px solid rgba(255, 255, 255, 0.2);
951
+ color: white;
952
+ font-size: 1rem;
953
+ cursor: pointer;
954
+ padding: 0 var(--space-sm);
955
+ border-radius: var(--radius-sm);
956
+ height: 24px;
957
+ transition: all var(--transition-fast);
958
+ display: flex;
959
+ align-items: center;
960
+ justify-content: center;
961
+ }
962
+
963
+ .control-btn-text:hover {
964
+ background: var(--color-primary);
965
+ border-color: var(--color-primary);
966
+ }
967
+
968
+ /* Speed Control */
969
+ .speed-control {
970
+ display: flex;
971
+ align-items: center;
972
+ gap: var(--space-sm);
973
+ background: rgba(0, 0, 0, 0.5);
974
+ padding: var(--space-xs) var(--space-sm);
975
+ border-radius: var(--radius-md);
976
+ }
977
+
978
+ .speed-value {
979
+ color: white;
980
+ font-weight: 600;
981
+ font-size: 0.9rem;
982
+ min-width: 40px;
983
+ text-align: center;
984
+ }
985
+
986
+ /* Time Display */
987
+ .time-display {
988
+ color: white;
989
+ font-size: 0.9rem;
990
+ font-feature-settings: "tnum";
991
+ font-variant-numeric: tabular-nums;
992
+ margin-left: var(--space-sm);
993
+ }
994
+
995
+ /* Seek Bar */
996
+ .seek-bar-container {
997
+ position: relative;
998
+ height: 6px;
999
+ width: 100%;
1000
+ cursor: pointer;
1001
+ display: flex;
1002
+ align-items: center;
1003
+ margin-bottom: var(--space-xs);
1004
+ }
1005
+
1006
+ .seek-bar-container:hover .seek-bar-bg {
1007
+ height: 8px;
1008
+ }
1009
+
1010
+ .seek-bar-bg {
1011
+ background: rgba(255, 255, 255, 0.3);
1012
+ height: 4px;
1013
+ width: 100%;
1014
+ border-radius: 2px;
1015
+ transition: height var(--transition-fast);
1016
+ position: absolute;
1017
+ top: 50%;
1018
+ transform: translateY(-50%);
1019
+ pointer-events: none;
1020
+ }
1021
+
1022
+ .seek-bar-fill {
1023
+ background: var(--color-primary);
1024
+ height: 100%;
1025
+ width: 0%;
1026
+ border-radius: 2px;
1027
+ }
1028
+
1029
+ .seek-slider {
1030
+ position: absolute;
1031
+ width: 100%;
1032
+ height: 100%;
1033
+ opacity: 0;
1034
+ cursor: pointer;
1035
+ margin: 0;
1036
+ z-index: 2;
1037
+ }
1038
+
1039
+ /* Hide default controls on fullscreen if needed (we rely on custom ones now) */
1040
+ /* .video-player::-webkit-media-controls { display:none !important; } */
1041
+
1042
+ /* ==========================================
1043
+ Player Layout with Queue
1044
+ ========================================== */
1045
+
1046
+ .player-layout {
1047
+ display: flex;
1048
+ gap: var(--space-md);
1049
+ /* Reduced gap */
1050
+ align-items: flex-start;
1051
+ }
1052
+
1053
+ .player-main {
1054
+ flex: 1;
1055
+ min-width: 0;
1056
+ }
1057
+
1058
+ /* ==========================================
1059
+ Queue Panel
1060
+ ========================================== */
1061
+
1062
+ .queue-panel {
1063
+ width: 30%;
1064
+ /* Slightly narrower width */
1065
+ min-width: 300px;
1066
+ max-width: 450px;
1067
+ flex-shrink: 0;
1068
+ background: var(--color-bg-secondary);
1069
+ border-radius: var(--radius-lg);
1070
+ border: 1px solid var(--color-border);
1071
+ overflow: hidden;
1072
+ position: sticky;
1073
+ top: 100px;
1074
+ max-height: calc(100vh - 120px);
1075
+ display: flex;
1076
+ flex-direction: column;
1077
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
1078
+ }
1079
+
1080
+ .queue-header {
1081
+ padding: var(--space-md) var(--space-lg);
1082
+ border-bottom: 1px solid var(--color-border);
1083
+ background: rgba(255, 255, 255, 0.03);
1084
+ }
1085
+
1086
+ .queue-title {
1087
+ font-size: 1rem;
1088
+ font-weight: 600;
1089
+ margin-bottom: 2px;
1090
+ color: var(--color-text);
1091
+ text-transform: uppercase;
1092
+ letter-spacing: 0.5px;
1093
+ }
1094
+
1095
+ .queue-module-name {
1096
+ font-size: 0.8rem;
1097
+ color: var(--color-text-muted);
1098
+ display: block;
1099
+ white-space: nowrap;
1100
+ overflow: hidden;
1101
+ text-overflow: ellipsis;
1102
+ }
1103
+
1104
+ .queue-list {
1105
+ overflow-y: auto;
1106
+ flex: 1;
1107
+ padding: var(--space-sm);
1108
+ scrollbar-width: thin;
1109
+ scrollbar-color: var(--color-border) transparent;
1110
+ }
1111
+
1112
+ .queue-list::-webkit-scrollbar {
1113
+ width: 6px;
1114
+ }
1115
+
1116
+ .queue-list::-webkit-scrollbar-track {
1117
+ background: transparent;
1118
+ }
1119
+
1120
+ .queue-list::-webkit-scrollbar-thumb {
1121
+ background-color: var(--color-border);
1122
+ border-radius: 3px;
1123
+ }
1124
+
1125
+ .queue-item {
1126
+ display: flex;
1127
+ align-items: center;
1128
+ gap: var(--space-md);
1129
+ padding: var(--space-md);
1130
+ /* More comfortable padding */
1131
+ border-radius: var(--radius-md);
1132
+ color: var(--color-text-secondary);
1133
+ transition: all var(--transition-fast);
1134
+ margin-bottom: 2px;
1135
+ text-decoration: none;
1136
+ border: 1px solid transparent;
1137
+ }
1138
+
1139
+ .queue-item:hover {
1140
+ background: var(--color-bg-tertiary);
1141
+ color: var(--color-text);
1142
+ transform: translateX(2px);
1143
+ }
1144
+
1145
+ .queue-item.active {
1146
+ background: var(--color-surface);
1147
+ color: var(--color-primary);
1148
+ border-color: var(--color-primary);
1149
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
1150
+ }
1151
+
1152
+ .queue-item.active:hover {
1153
+ background: var(--color-surface);
1154
+ transform: none;
1155
+ }
1156
+
1157
+ .queue-item-number {
1158
+ flex-shrink: 0;
1159
+ width: 28px;
1160
+ height: 28px;
1161
+ display: flex;
1162
+ align-items: center;
1163
+ justify-content: center;
1164
+ font-size: 0.8rem;
1165
+ font-weight: 600;
1166
+ background: rgba(255, 255, 255, 0.05);
1167
+ border-radius: 50%;
1168
+ /* Circle instead of square */
1169
+ color: var(--color-text-muted);
1170
+ transition: all var(--transition-fast);
1171
+ }
1172
+
1173
+ .queue-item.active .queue-item-number {
1174
+ background: var(--color-primary);
1175
+ color: white;
1176
+ }
1177
+
1178
+ .queue-item-content {
1179
+ flex: 1;
1180
+ min-width: 0;
1181
+ display: flex;
1182
+ flex-direction: column;
1183
+ gap: 2px;
1184
+ }
1185
+
1186
+ .queue-item-title {
1187
+ font-size: 0.9rem;
1188
+ font-weight: 500;
1189
+ display: block;
1190
+ /* Multi-line clamp instead of single line ellipsis for better readability */
1191
+ display: -webkit-box;
1192
+ -webkit-line-clamp: 2;
1193
+ -webkit-box-orient: vertical;
1194
+ overflow: hidden;
1195
+ line-height: 1.4;
1196
+ color: inherit;
1197
+ }
1198
+
1199
+ .queue-item.active .queue-item-title {
1200
+ color: var(--color-text);
1201
+ font-weight: 600;
1202
+ }
1203
+
1204
+ .queue-item-meta {
1205
+ display: flex;
1206
+ align-items: center;
1207
+ gap: var(--space-sm);
1208
+ margin-top: 2px;
1209
+ }
1210
+
1211
+ .queue-item-status {
1212
+ font-size: 0.75rem;
1213
+ display: flex;
1214
+ align-items: center;
1215
+ }
1216
+
1217
+ .queue-item-status.status-completed {
1218
+ color: var(--color-success);
1219
+ }
1220
+
1221
+ .queue-item-status.status-in-progress {
1222
+ color: var(--color-warning);
1223
+ }
1224
+
1225
+ .queue-item-status.status-unwatched {
1226
+ color: var(--color-text-muted);
1227
+ }
1228
+
1229
+ .queue-item.active .queue-item-status {
1230
+ /* color: var(--color-primary); */
1231
+ }
1232
+
1233
+ .queue-item-playing {
1234
+ font-size: 0.65rem;
1235
+ font-weight: 700;
1236
+ text-transform: uppercase;
1237
+ letter-spacing: 0.5px;
1238
+ color: var(--color-primary);
1239
+ background: rgba(124, 58, 237, 0.1);
1240
+ padding: 2px 6px;
1241
+ border-radius: 4px;
1242
+ }
1243
+
1244
+ /* ==========================================
1245
+ Autoplay Countdown Overlay
1246
+ ========================================== */
1247
+
1248
+ .autoplay-overlay {
1249
+ position: absolute;
1250
+ top: 0;
1251
+ left: 0;
1252
+ right: 0;
1253
+ bottom: 0;
1254
+ background: rgba(0, 0, 0, 0.85);
1255
+ display: flex;
1256
+ align-items: center;
1257
+ justify-content: center;
1258
+ z-index: 100;
1259
+ backdrop-filter: blur(8px);
1260
+ }
1261
+
1262
+ .autoplay-overlay.hidden {
1263
+ display: none;
1264
+ }
1265
+
1266
+ .autoplay-content {
1267
+ text-align: center;
1268
+ color: white;
1269
+ }
1270
+
1271
+ .autoplay-countdown-ring {
1272
+ position: relative;
1273
+ width: 120px;
1274
+ height: 120px;
1275
+ margin: 0 auto var(--space-lg);
1276
+ background: none;
1277
+ border: none;
1278
+ padding: 0;
1279
+ cursor: pointer;
1280
+ transition: transform var(--transition-fast);
1281
+ }
1282
+
1283
+ .autoplay-countdown-ring:hover {
1284
+ transform: scale(1.05);
1285
+ }
1286
+
1287
+ .autoplay-countdown-ring:hover .countdown-progress {
1288
+ stroke: var(--color-primary-hover);
1289
+ }
1290
+
1291
+ .autoplay-countdown-ring svg {
1292
+ width: 100%;
1293
+ height: 100%;
1294
+ transform: rotate(-90deg);
1295
+ }
1296
+
1297
+ .countdown-bg {
1298
+ fill: none;
1299
+ stroke: rgba(255, 255, 255, 0.2);
1300
+ stroke-width: 6;
1301
+ }
1302
+
1303
+ .countdown-progress {
1304
+ fill: none;
1305
+ stroke: var(--color-primary);
1306
+ stroke-width: 6;
1307
+ stroke-linecap: round;
1308
+ stroke-dasharray: 283;
1309
+ stroke-dashoffset: 0;
1310
+ transition: stroke-dashoffset 1s linear;
1311
+ }
1312
+
1313
+ .countdown-number {
1314
+ position: absolute;
1315
+ top: 50%;
1316
+ left: 50%;
1317
+ transform: translate(-50%, -50%);
1318
+ font-size: 2.5rem;
1319
+ font-weight: 700;
1320
+ color: white;
1321
+ text-shadow: 0 0 20px rgba(124, 58, 237, 0.8), 0 2px 4px rgba(0, 0, 0, 0.5);
1322
+ }
1323
+
1324
+ .autoplay-text {
1325
+ font-size: 0.9rem;
1326
+ color: var(--color-text-secondary);
1327
+ margin-bottom: var(--space-xs);
1328
+ text-transform: uppercase;
1329
+ letter-spacing: 1px;
1330
+ }
1331
+
1332
+ .autoplay-next-title {
1333
+ font-size: 1.25rem;
1334
+ font-weight: 600;
1335
+ margin-bottom: var(--space-lg);
1336
+ max-width: 400px;
1337
+ overflow: hidden;
1338
+ text-overflow: ellipsis;
1339
+ white-space: nowrap;
1340
+ }
1341
+
1342
+ .autoplay-cancel {
1343
+ background: rgba(255, 255, 255, 0.1);
1344
+ border: 1px solid rgba(255, 255, 255, 0.3);
1345
+ color: white;
1346
+ padding: var(--space-sm) var(--space-xl);
1347
+ font-size: 0.9rem;
1348
+ }
1349
+
1350
+ .autoplay-cancel:hover {
1351
+ background: rgba(255, 255, 255, 0.2);
1352
+ border-color: rgba(255, 255, 255, 0.5);
1353
+ transform: none;
1354
+ box-shadow: none;
1355
+ }
1356
+
1357
+ /* Responsive: Queue below player on smaller screens */
1358
+ @media (max-width: 1024px) {
1359
+ .player-layout {
1360
+ flex-direction: column;
1361
+ }
1362
+
1363
+ .queue-panel {
1364
+ width: 100%;
1365
+ position: static;
1366
+ max-height: 400px;
1367
+ }
1368
+ }
1369
+
1370
+ /* Fullscreen autoplay overlay */
1371
+ :fullscreen .autoplay-overlay,
1372
+ :-webkit-full-screen .autoplay-overlay,
1373
+ :-moz-full-screen .autoplay-overlay {
1374
+ position: fixed;
935
1375
  }