coursewatcher 1.3.0 → 2.0.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 +26 -23
- package/dist/app/cli/main.js +36 -0
- package/dist/app/server/create-app.js +151 -0
- package/dist/app/server/start-server.js +72 -0
- package/dist/modules/catalog/catalog-mappers.js +49 -0
- package/dist/modules/catalog/catalog-repository.js +168 -0
- package/dist/modules/catalog/catalog-service.js +76 -0
- package/dist/modules/notes/notes-repository.js +25 -0
- package/dist/modules/notes/notes-service.js +28 -0
- package/dist/modules/playback/playback-repository.js +32 -0
- package/dist/modules/playback/playback-service.js +89 -0
- package/dist/platform/config/app-config.js +24 -0
- package/dist/platform/config/package-info.js +21 -0
- package/dist/platform/database/database-manager.js +101 -0
- package/dist/platform/errors/app-error.js +30 -0
- package/dist/platform/logging/logger.js +21 -0
- package/dist/shared/contracts/api.js +2 -0
- package/dist/web/assets/api-client-hFlLSS3K.js +1 -0
- package/dist/web/assets/catalog-route-pOIhR3yd.js +1 -0
- package/dist/web/assets/index-CwspbIw1.js +10 -0
- package/dist/web/assets/index-VjwsJnuQ.css +1 -0
- package/dist/web/assets/jsx-runtime-C2ZT__TU.js +4 -0
- package/dist/web/assets/playback-route-Bmy_Z7k7.js +2 -0
- package/dist/web/assets/search-route-CeGVOVPT.js +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +75 -57
- package/public/css/styles.css +0 -1375
- package/public/js/player.js +0 -359
- package/src/cli.js +0 -45
- package/src/controllers/video-controller.js +0 -175
- package/src/models/database.js +0 -169
- package/src/server.js +0 -179
- package/src/services/notes-service.js +0 -97
- package/src/services/progress-service.js +0 -148
- package/src/services/video-service.js +0 -354
- package/src/utils/config.js +0 -56
- package/src/utils/errors.js +0 -57
- package/src/utils/logger.js +0 -48
- package/views/layouts/main.ejs +0 -13
- package/views/pages/error.ejs +0 -25
- package/views/pages/index.ejs +0 -101
- package/views/pages/player.ejs +0 -161
- package/views/pages/search.ejs +0 -63
- package/views/partials/footer.ejs +0 -3
- package/views/partials/head.ejs +0 -8
- package/views/partials/header.ejs +0 -14
- package/views/partials/video-card.ejs +0 -36
package/views/pages/player.ejs
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<%- include('../partials/head', { title }) %>
|
|
4
|
-
|
|
5
|
-
<body>
|
|
6
|
-
<%- include('../partials/header') %>
|
|
7
|
-
|
|
8
|
-
<main class="container player-container">
|
|
9
|
-
<div class="player-layout">
|
|
10
|
-
<!-- Main Player Section -->
|
|
11
|
-
<div class="player-main">
|
|
12
|
-
<!-- Video Player Section -->
|
|
13
|
-
<section class="player-section">
|
|
14
|
-
<h1 class="video-title">
|
|
15
|
-
<%= video.title %>
|
|
16
|
-
</h1>
|
|
17
|
-
<div class="video-wrapper">
|
|
18
|
-
<video id="videoPlayer" class="plyr" playsinline controls
|
|
19
|
-
data-video-id="<%= video.id %>" data-saved-position="<%= video.position %>"
|
|
20
|
-
data-next-video-url="<%= adjacent.next ? '/video/' + adjacent.next : '' %>">
|
|
21
|
-
<source src="/api/videos/<%= video.id %>/stream" type="video/mp4">
|
|
22
|
-
Your browser does not support the video tag.
|
|
23
|
-
</video>
|
|
24
|
-
|
|
25
|
-
<!-- Auto-play Countdown Overlay -->
|
|
26
|
-
<div id="autoplayOverlay" class="autoplay-overlay hidden">
|
|
27
|
-
<div class="autoplay-content">
|
|
28
|
-
<button id="skipToNext" class="autoplay-countdown-ring"
|
|
29
|
-
title="Click to play now">
|
|
30
|
-
<svg viewBox="0 0 100 100">
|
|
31
|
-
<circle cx="50" cy="50" r="45" class="countdown-bg" />
|
|
32
|
-
<circle cx="50" cy="50" r="45" class="countdown-progress"
|
|
33
|
-
id="countdownProgress" />
|
|
34
|
-
</svg>
|
|
35
|
-
<span id="countdownNumber" class="countdown-number">5</span>
|
|
36
|
-
</button>
|
|
37
|
-
<p class="autoplay-text">Up Next</p>
|
|
38
|
-
<p id="nextVideoTitle" class="autoplay-next-title"></p>
|
|
39
|
-
<button id="cancelAutoplay" class="btn autoplay-cancel">Cancel</button>
|
|
40
|
-
</div>
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<!-- Navigation -->
|
|
45
|
-
<div class="player-nav">
|
|
46
|
-
<% if (adjacent.prev) { %>
|
|
47
|
-
<a href="/video/<%= adjacent.prev %>" class="nav-button prev">
|
|
48
|
-
← Previous
|
|
49
|
-
</a>
|
|
50
|
-
<% } else { %>
|
|
51
|
-
<span class="nav-button disabled">← Previous</span>
|
|
52
|
-
<% } %>
|
|
53
|
-
|
|
54
|
-
<a href="/" class="nav-button home">📚 Back to Course</a>
|
|
55
|
-
|
|
56
|
-
<% if (adjacent.next) { %>
|
|
57
|
-
<a href="/video/<%= adjacent.next %>" class="nav-button next">
|
|
58
|
-
Next →
|
|
59
|
-
</a>
|
|
60
|
-
<% } else { %>
|
|
61
|
-
<span class="nav-button disabled">Next →</span>
|
|
62
|
-
<% } %>
|
|
63
|
-
</div>
|
|
64
|
-
</section>
|
|
65
|
-
|
|
66
|
-
<!-- Video Info Section -->
|
|
67
|
-
<section class="video-info-section">
|
|
68
|
-
|
|
69
|
-
<!-- Status Controls -->
|
|
70
|
-
<div class="status-controls">
|
|
71
|
-
<span class="status-label">Status:</span>
|
|
72
|
-
<div class="status-buttons">
|
|
73
|
-
<button class="status-btn <%= video.status === 'unwatched' ? 'active' : '' %>"
|
|
74
|
-
data-status="unwatched">
|
|
75
|
-
○ Unwatched
|
|
76
|
-
</button>
|
|
77
|
-
<button class="status-btn <%= video.status === 'in-progress' ? 'active' : '' %>"
|
|
78
|
-
data-status="in-progress">
|
|
79
|
-
▶ In Progress
|
|
80
|
-
</button>
|
|
81
|
-
<button class="status-btn <%= video.status === 'completed' ? 'active' : '' %>"
|
|
82
|
-
data-status="completed">
|
|
83
|
-
✓ Completed
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<!-- Keyboard Shortcuts Help -->
|
|
89
|
-
<details class="shortcuts-help">
|
|
90
|
-
<summary>⌨️ Keyboard Shortcuts</summary>
|
|
91
|
-
<div class="shortcuts-grid">
|
|
92
|
-
<div class="shortcut"><kbd>Space</kbd> / <kbd>K</kbd> Play/Pause</div>
|
|
93
|
-
<div class="shortcut"><kbd>M</kbd> Mute/Unmute</div>
|
|
94
|
-
<div class="shortcut"><kbd>F</kbd> Fullscreen</div>
|
|
95
|
-
<div class="shortcut"><kbd>←</kbd> Back 10s</div>
|
|
96
|
-
<div class="shortcut"><kbd>→</kbd> Forward 10s</div>
|
|
97
|
-
<div class="shortcut"><kbd>↑</kbd> Volume Up</div>
|
|
98
|
-
<div class="shortcut"><kbd>↓</kbd> Volume Down</div>
|
|
99
|
-
<div class="shortcut"><kbd>C</kbd> Captions</div>
|
|
100
|
-
</div>
|
|
101
|
-
</details>
|
|
102
|
-
</section>
|
|
103
|
-
|
|
104
|
-
<!-- Notes Section -->
|
|
105
|
-
<section class="notes-section">
|
|
106
|
-
<h2 class="section-title">📝 Notes</h2>
|
|
107
|
-
<textarea id="notesEditor" class="notes-editor"
|
|
108
|
-
placeholder="Take notes for this video... (Markdown supported)"><%= notes.content || '' %></textarea>
|
|
109
|
-
<div class="notes-actions">
|
|
110
|
-
<button id="saveNotes" class="btn btn-primary">Save Notes</button>
|
|
111
|
-
<span id="notesSaveStatus" class="save-status"></span>
|
|
112
|
-
</div>
|
|
113
|
-
</section>
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<!-- Queue Panel -->
|
|
117
|
-
<aside class="queue-panel">
|
|
118
|
-
<div class="queue-header">
|
|
119
|
-
<h2 class="queue-title">📋 Queue</h2>
|
|
120
|
-
<span class="queue-module-name">
|
|
121
|
-
<%= queue.moduleName %>
|
|
122
|
-
</span>
|
|
123
|
-
</div>
|
|
124
|
-
<div class="queue-list">
|
|
125
|
-
<% queue.videos.forEach((qVideo, index)=> { %>
|
|
126
|
-
<a href="/video/<%= qVideo.id %>"
|
|
127
|
-
class="queue-item <%= qVideo.id === queue.currentId ? 'active' : '' %>"
|
|
128
|
-
data-video-id="<%= qVideo.id %>" data-video-title="<%= qVideo.title %>">
|
|
129
|
-
<span class="queue-item-number">
|
|
130
|
-
<%= index + 1 %>
|
|
131
|
-
</span>
|
|
132
|
-
<div class="queue-item-content">
|
|
133
|
-
<span class="queue-item-title">
|
|
134
|
-
<%= qVideo.title %>
|
|
135
|
-
</span>
|
|
136
|
-
<div class="queue-item-meta">
|
|
137
|
-
<span class="queue-item-status status-<%= qVideo.status %>">
|
|
138
|
-
<% if (qVideo.status==='completed' ) { %>✓
|
|
139
|
-
<% } else if (qVideo.status==='in-progress' ) { %>▶
|
|
140
|
-
<% } else { %>○<% } %>
|
|
141
|
-
</span>
|
|
142
|
-
<% if (qVideo.id===queue.currentId) { %>
|
|
143
|
-
<span class="queue-item-playing">Now Playing</span>
|
|
144
|
-
<% } %>
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
</a>
|
|
148
|
-
<% }); %>
|
|
149
|
-
</div>
|
|
150
|
-
</aside>
|
|
151
|
-
</div>
|
|
152
|
-
</main>
|
|
153
|
-
|
|
154
|
-
<%- include('../partials/footer') %>
|
|
155
|
-
|
|
156
|
-
<link rel="stylesheet" href="/lib/plyr/plyr.css" />
|
|
157
|
-
<script src="/lib/plyr/plyr.js"></script>
|
|
158
|
-
<script src="/static/js/player.js"></script>
|
|
159
|
-
</body>
|
|
160
|
-
|
|
161
|
-
</html>
|
package/views/pages/search.ejs
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<%- include('../partials/head', { title }) %>
|
|
4
|
-
|
|
5
|
-
<body>
|
|
6
|
-
<%- include('../partials/header') %>
|
|
7
|
-
|
|
8
|
-
<main class="container">
|
|
9
|
-
<section class="search-section">
|
|
10
|
-
<h1 class="page-title">🔍 Search Results</h1>
|
|
11
|
-
|
|
12
|
-
<form action="/search" method="GET" class="search-form-large">
|
|
13
|
-
<input type="text" name="q" value="<%= query %>" placeholder="Search videos..."
|
|
14
|
-
class="search-input-large" autofocus>
|
|
15
|
-
<button type="submit" class="btn btn-primary">Search</button>
|
|
16
|
-
</form>
|
|
17
|
-
|
|
18
|
-
<% if (query) { %>
|
|
19
|
-
<p class="search-meta">
|
|
20
|
-
Found <strong>
|
|
21
|
-
<%= results.length %>
|
|
22
|
-
</strong> result<%= results.length !==1 ? 's' : '' %>
|
|
23
|
-
for "<%= query %>"
|
|
24
|
-
</p>
|
|
25
|
-
<% } %>
|
|
26
|
-
|
|
27
|
-
<% if (results.length> 0) { %>
|
|
28
|
-
<div class="search-results">
|
|
29
|
-
<% results.forEach(video=> { %>
|
|
30
|
-
<a href="/video/<%= video.id %>" class="search-result-card">
|
|
31
|
-
<div class="result-status">
|
|
32
|
-
<% if (video.status==='completed' ) { %>
|
|
33
|
-
<span class="status-icon completed">✓</span>
|
|
34
|
-
<% } else if (video.status==='in-progress' ) { %>
|
|
35
|
-
<span class="status-icon in-progress">▶</span>
|
|
36
|
-
<% } else { %>
|
|
37
|
-
<span class="status-icon unwatched">○</span>
|
|
38
|
-
<% } %>
|
|
39
|
-
</div>
|
|
40
|
-
<div class="result-content">
|
|
41
|
-
<h3 class="result-title">
|
|
42
|
-
<%= video.title %>
|
|
43
|
-
</h3>
|
|
44
|
-
<% if (video.module_name) { %>
|
|
45
|
-
<span class="result-module">📁 <%= video.module_name %></span>
|
|
46
|
-
<% } %>
|
|
47
|
-
</div>
|
|
48
|
-
</a>
|
|
49
|
-
<% }) %>
|
|
50
|
-
</div>
|
|
51
|
-
<% } else if (query) { %>
|
|
52
|
-
<div class="empty-state">
|
|
53
|
-
<p>No videos found matching "<%= query %>"</p>
|
|
54
|
-
<p class="empty-hint">Try a different search term</p>
|
|
55
|
-
</div>
|
|
56
|
-
<% } %>
|
|
57
|
-
</section>
|
|
58
|
-
</main>
|
|
59
|
-
|
|
60
|
-
<%- include('../partials/footer') %>
|
|
61
|
-
</body>
|
|
62
|
-
|
|
63
|
-
</html>
|
package/views/partials/head.ejs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<header class="header">
|
|
2
|
-
<div class="header-content">
|
|
3
|
-
<a href="/" class="logo">
|
|
4
|
-
<span class="logo-icon">📚</span>
|
|
5
|
-
<span class="logo-text">CourseWatcher</span>
|
|
6
|
-
</a>
|
|
7
|
-
<nav class="nav">
|
|
8
|
-
<form action="/search" method="GET" class="search-form">
|
|
9
|
-
<input type="text" name="q" placeholder="Search videos..." class="search-input" autocomplete="off">
|
|
10
|
-
<button type="submit" class="search-button">🔍</button>
|
|
11
|
-
</form>
|
|
12
|
-
</nav>
|
|
13
|
-
</div>
|
|
14
|
-
</header>
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
<a href="/video/<%= video.id %>" class="video-card">
|
|
2
|
-
<div class="video-card-icon">
|
|
3
|
-
<% if (video.status==='completed' ) { %>
|
|
4
|
-
<span class="status-icon completed">✓</span>
|
|
5
|
-
<% } else if (video.status==='in-progress' ) { %>
|
|
6
|
-
<span class="status-icon in-progress">▶</span>
|
|
7
|
-
<% } else { %>
|
|
8
|
-
<span class="status-icon unwatched">○</span>
|
|
9
|
-
<% } %>
|
|
10
|
-
</div>
|
|
11
|
-
<div class="video-card-content">
|
|
12
|
-
<h3 class="video-card-title">
|
|
13
|
-
<%= video.title %>
|
|
14
|
-
</h3>
|
|
15
|
-
<% if (video.duration> 0) { %>
|
|
16
|
-
<div class="video-card-progress">
|
|
17
|
-
<div class="progress-bar">
|
|
18
|
-
<div class="progress-fill"
|
|
19
|
-
style="width: <%= Math.round((video.position / video.duration) * 100) %>%"></div>
|
|
20
|
-
</div>
|
|
21
|
-
<span class="progress-text">
|
|
22
|
-
<%= Math.round((video.position / video.duration) * 100) %>%
|
|
23
|
-
</span>
|
|
24
|
-
</div>
|
|
25
|
-
<% } %>
|
|
26
|
-
</div>
|
|
27
|
-
<div class="video-card-badge">
|
|
28
|
-
<% if (video.status==='completed' ) { %>
|
|
29
|
-
<span class="badge badge-success">Completed</span>
|
|
30
|
-
<% } else if (video.status==='in-progress' ) { %>
|
|
31
|
-
<span class="badge badge-warning">In Progress</span>
|
|
32
|
-
<% } else { %>
|
|
33
|
-
<span class="badge badge-default">Not Started</span>
|
|
34
|
-
<% } %>
|
|
35
|
-
</div>
|
|
36
|
-
</a>
|