@dytsou/calendar-build 1.1.1 → 2.0.1
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/LICENSE +9 -0
- package/README.md +174 -17
- package/index.html.template +303 -52
- package/package.json +23 -8
- package/scripts/build.js +192 -0
- package/scripts/encrypt-urls.js +76 -0
- package/worker.js +480 -0
- package/wrangler.toml.example +34 -0
- package/build.js +0 -53
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 dytsou
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,32 +1,96 @@
|
|
|
1
1
|
# Calendar App
|
|
2
2
|
|
|
3
|
-
A simple calendar application that displays multiple
|
|
3
|
+
A simple calendar application that displays multiple calendar feeds using Open Web Calendar.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Display multiple calendar feeds simultaneously
|
|
8
|
+
- Dark theme with customizable styling
|
|
9
|
+
- Full-height responsive design
|
|
10
|
+
- URL parameters for view mode and date selection
|
|
11
|
+
- Clean interface without menu buttons or navigation controls
|
|
4
12
|
|
|
5
13
|
## Setup
|
|
6
14
|
|
|
15
|
+
### 1. Configure Cloudflare Worker Secrets
|
|
16
|
+
|
|
17
|
+
Calendar URLs are now managed via Cloudflare Worker secrets for better security and centralized management.
|
|
18
|
+
|
|
19
|
+
**Set up your Cloudflare Worker secrets:**
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Set calendar URLs (comma-separated, can be plain or fernet:// encrypted)
|
|
23
|
+
wrangler secret put CALENDAR_URL
|
|
24
|
+
# Enter: https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics,https://calendar.google.com/calendar/ical/another%40gmail.com/public/basic.ics
|
|
25
|
+
|
|
26
|
+
# Set encryption key (if using fernet:// encrypted URLs)
|
|
27
|
+
wrangler secret put ENCRYPTION_KEY
|
|
28
|
+
# Enter your Fernet encryption key (base64url-encoded 32-byte key)
|
|
29
|
+
# Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''))"
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Getting Google Calendar iCal URLs:**
|
|
33
|
+
- Go to your Google Calendar settings
|
|
34
|
+
- Find the calendar you want to share
|
|
35
|
+
- Click "Integrate calendar" or "Get shareable link"
|
|
36
|
+
- Copy the "Public URL to iCal format" link
|
|
37
|
+
- URL-encode special characters (e.g., `@` becomes `%40`)
|
|
38
|
+
|
|
39
|
+
### 2. Configure Local Development (Optional)
|
|
40
|
+
|
|
7
41
|
1. Copy `.env.example` to `.env`:
|
|
42
|
+
|
|
8
43
|
```bash
|
|
9
44
|
cp .env.example .env
|
|
10
45
|
```
|
|
11
46
|
|
|
12
|
-
2. Edit `.env` and
|
|
47
|
+
2. Edit `.env` and set your Cloudflare Worker URL (optional):
|
|
48
|
+
|
|
13
49
|
```
|
|
14
|
-
|
|
50
|
+
WORKER_URL=https://cal-proxy.yourdomain.com
|
|
15
51
|
```
|
|
16
52
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
pnpm install
|
|
21
|
-
pnpm run build
|
|
22
|
-
# or use the global command if installed globally
|
|
23
|
-
calendar-build
|
|
24
|
-
|
|
25
|
-
# Option 2: Direct execution
|
|
26
|
-
node build.js
|
|
27
|
-
```
|
|
53
|
+
If not set, defaults to `https://open-web-calendar.hosted.quelltext.eu`
|
|
54
|
+
|
|
55
|
+
### 3. Build the HTML File
|
|
28
56
|
|
|
29
|
-
|
|
57
|
+
```bash
|
|
58
|
+
# Option 1: Using npm/pnpm (after installing)
|
|
59
|
+
pnpm install
|
|
60
|
+
pnpm run build
|
|
61
|
+
# or use the global command if installed globally
|
|
62
|
+
calendar-build
|
|
63
|
+
|
|
64
|
+
# Option 2: Direct execution
|
|
65
|
+
node scripts/build.js
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 4. Deploy and Open
|
|
69
|
+
|
|
70
|
+
- Deploy `index.html` to your hosting service (GitHub Pages, etc.)
|
|
71
|
+
- Open the deployed page in your browser
|
|
72
|
+
- The Cloudflare Worker will automatically add calendar URLs from secrets to all requests
|
|
73
|
+
|
|
74
|
+
## URL Parameters
|
|
75
|
+
|
|
76
|
+
The calendar supports URL parameters for navigation:
|
|
77
|
+
|
|
78
|
+
- **`?mode=month`** - Show month view
|
|
79
|
+
- **`?mode=week`** - Show week view (default)
|
|
80
|
+
- **`?mode=day`** - Show day view
|
|
81
|
+
- **`?date=YYYYMMDD`** - Navigate to a specific date (e.g., `?date=20250115`)
|
|
82
|
+
- **`?theme=dark`** - Force dark theme
|
|
83
|
+
- **`?theme=light`** - Force light theme
|
|
84
|
+
|
|
85
|
+
**Theme:** The calendar uses your browser's color scheme preference by default. You can override it using the `?theme=` URL parameter.
|
|
86
|
+
|
|
87
|
+
**Examples:**
|
|
88
|
+
|
|
89
|
+
- `index.html?mode=month` - Month view (uses browser theme preference)
|
|
90
|
+
- `index.html?mode=week&date=20250115` - Week view for the week containing January 15, 2025
|
|
91
|
+
- `index.html?mode=day&date=20250320` - Day view for March 20, 2025
|
|
92
|
+
- `index.html?theme=light` - Light theme with default week view
|
|
93
|
+
- `index.html?mode=month&theme=dark` - Month view with dark theme
|
|
30
94
|
|
|
31
95
|
## Install Package
|
|
32
96
|
|
|
@@ -38,6 +102,7 @@ This package is published to **both registries**:
|
|
|
38
102
|
### Installation from npmjs (Default - Recommended)
|
|
39
103
|
|
|
40
104
|
**Global installation:**
|
|
105
|
+
|
|
41
106
|
```bash
|
|
42
107
|
npm install -g @dytsou/calendar-build
|
|
43
108
|
# or
|
|
@@ -45,11 +110,13 @@ pnpm install -g @dytsou/calendar-build
|
|
|
45
110
|
```
|
|
46
111
|
|
|
47
112
|
Then use anywhere:
|
|
113
|
+
|
|
48
114
|
```bash
|
|
49
115
|
calendar-build
|
|
50
116
|
```
|
|
51
117
|
|
|
52
118
|
**Local installation:**
|
|
119
|
+
|
|
53
120
|
```bash
|
|
54
121
|
npm install @dytsou/calendar-build
|
|
55
122
|
# or
|
|
@@ -57,6 +124,7 @@ pnpm install @dytsou/calendar-build
|
|
|
57
124
|
```
|
|
58
125
|
|
|
59
126
|
Then use:
|
|
127
|
+
|
|
60
128
|
```bash
|
|
61
129
|
npx calendar-build
|
|
62
130
|
# or
|
|
@@ -77,12 +145,14 @@ Create or edit `.npmrc` file in your home directory:
|
|
|
77
145
|
```
|
|
78
146
|
|
|
79
147
|
**2. Get your GitHub token:**
|
|
148
|
+
|
|
80
149
|
1. Go to https://github.com/settings/tokens
|
|
81
150
|
2. Click "Generate new token" → "Generate new token (classic)"
|
|
82
151
|
3. Select `read:packages` permission
|
|
83
152
|
4. Copy the token and replace `YOUR_GITHUB_TOKEN` in `.npmrc`
|
|
84
153
|
|
|
85
154
|
**3. Install:**
|
|
155
|
+
|
|
86
156
|
```bash
|
|
87
157
|
npm install -g @dytsou/calendar-build
|
|
88
158
|
# or
|
|
@@ -91,8 +161,95 @@ pnpm install -g @dytsou/calendar-build
|
|
|
91
161
|
|
|
92
162
|
## Development
|
|
93
163
|
|
|
94
|
-
|
|
95
|
-
|
|
164
|
+
### Project Structure
|
|
165
|
+
|
|
166
|
+
- `index.html.template` - Template file with placeholders for calendar URLs
|
|
167
|
+
- `scripts/build.js` - Build script that injects calendar URLs from `.env` and updates year in LICENSE
|
|
168
|
+
- `scripts/encrypt-urls.js` - Helper script to encrypt calendar URLs using Fernet
|
|
96
169
|
- `.env` - Local environment file (not committed to git)
|
|
97
170
|
- `.env.example` - Example environment file template
|
|
98
171
|
|
|
172
|
+
### Scripts
|
|
173
|
+
|
|
174
|
+
- `pnpm run build` - Build the HTML file from template
|
|
175
|
+
- `pnpm format` - Format code with Prettier
|
|
176
|
+
- `pnpm format:check` - Check code formatting
|
|
177
|
+
|
|
178
|
+
### Build Process
|
|
179
|
+
|
|
180
|
+
The build script:
|
|
181
|
+
|
|
182
|
+
1. Reads `WORKER_URL` from `.env` (optional, defaults to open-web-calendar)
|
|
183
|
+
2. Replaces `{{WORKER_URL}}` placeholder in the template
|
|
184
|
+
3. Updates `{{YEAR}}` placeholder with current year in LICENSE and HTML
|
|
185
|
+
4. Generates `index.html` ready for deployment
|
|
186
|
+
|
|
187
|
+
**Note:** Calendar URLs are now managed via Cloudflare Worker secrets (`CALENDAR_URL`), not in `.env` or the HTML file. The worker automatically adds them to all calendar requests.
|
|
188
|
+
|
|
189
|
+
## Cloudflare Worker Setup
|
|
190
|
+
|
|
191
|
+
This project uses a Cloudflare Worker to manage calendar URLs securely. Calendar URLs are stored as Cloudflare Worker secrets, keeping them out of the HTML and GitHub secrets.
|
|
192
|
+
|
|
193
|
+
### Setup Instructions
|
|
194
|
+
|
|
195
|
+
1. **Copy wrangler.toml.example to wrangler.toml:**
|
|
196
|
+
```bash
|
|
197
|
+
cp wrangler.toml.example wrangler.toml
|
|
198
|
+
```
|
|
199
|
+
Then edit `wrangler.toml` and customize it for your environment (e.g., add custom domain routes).
|
|
200
|
+
|
|
201
|
+
2. **Install Wrangler CLI:**
|
|
202
|
+
```bash
|
|
203
|
+
npm install -g wrangler
|
|
204
|
+
# or
|
|
205
|
+
pnpm add -g wrangler
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
3. **Login to Cloudflare:**
|
|
209
|
+
```bash
|
|
210
|
+
wrangler login
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
4. **Set Calendar URLs Secret:**
|
|
214
|
+
```bash
|
|
215
|
+
wrangler secret put CALENDAR_URL
|
|
216
|
+
```
|
|
217
|
+
When prompted, enter your calendar URLs (comma-separated):
|
|
218
|
+
```
|
|
219
|
+
https://calendar.google.com/calendar/ical/example%40gmail.com/public/basic.ics,https://calendar.google.com/calendar/ical/another%40gmail.com/public/basic.ics
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Note:** URLs can be plain or `fernet://` encrypted. If using encrypted URLs, you'll also need to set `ENCRYPTION_KEY`.
|
|
223
|
+
|
|
224
|
+
5. **Set Encryption Key Secret (if using fernet:// encrypted URLs):**
|
|
225
|
+
```bash
|
|
226
|
+
wrangler secret put ENCRYPTION_KEY
|
|
227
|
+
```
|
|
228
|
+
When prompted, enter your Fernet encryption key (base64url-encoded 32-byte key).
|
|
229
|
+
|
|
230
|
+
Generate one with:
|
|
231
|
+
```bash
|
|
232
|
+
node -e "console.log(require('crypto').randomBytes(32).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, ''))"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
6. **Deploy the Worker:**
|
|
236
|
+
```bash
|
|
237
|
+
wrangler deploy
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
6. **Update your .env file:**
|
|
241
|
+
After deployment, update the `WORKER_URL` in your `.env` file with your worker URL:
|
|
242
|
+
```
|
|
243
|
+
WORKER_URL=https://your-worker.your-subdomain.workers.dev
|
|
244
|
+
# or if using custom domain:
|
|
245
|
+
WORKER_URL=https://your-domain.com
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
7. **Rebuild your project:**
|
|
249
|
+
```bash
|
|
250
|
+
pnpm run build
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## License
|
|
254
|
+
|
|
255
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
package/index.html.template
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
MIT License
|
|
5
|
+
Copyright (c) {{YEAR}} dytsou
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
9
|
+
-->
|
|
10
|
+
|
|
2
11
|
<html lang="zh-TW">
|
|
3
12
|
|
|
4
13
|
<head>
|
|
5
14
|
<meta charset="UTF-8">
|
|
6
15
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
16
|
+
<base href="/cal/">
|
|
7
17
|
<title>My Calendar</title>
|
|
8
18
|
<link rel="icon" type="image/png" href="asset/favicon.png">
|
|
9
19
|
|
|
10
20
|
<style>
|
|
21
|
+
html, body {
|
|
22
|
+
height: 100%;
|
|
23
|
+
margin: 0;
|
|
24
|
+
padding: 0;
|
|
25
|
+
overflow: hidden;
|
|
26
|
+
}
|
|
27
|
+
|
|
11
28
|
body {
|
|
12
29
|
transition: opacity ease-in 0.2s;
|
|
13
30
|
}
|
|
@@ -22,75 +39,309 @@
|
|
|
22
39
|
iframe {
|
|
23
40
|
border: solid 1px #777;
|
|
24
41
|
width: 100%;
|
|
25
|
-
height: 100vh;
|
|
42
|
+
height: calc(100vh - 25px);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Hide error messages that might expose calendar URLs */
|
|
46
|
+
iframe {
|
|
47
|
+
/* Note: Cross-origin iframe content cannot be styled directly */
|
|
26
48
|
}
|
|
27
49
|
</style>
|
|
28
50
|
</head>
|
|
29
51
|
|
|
30
52
|
<body unresolved>
|
|
31
53
|
|
|
32
|
-
<iframe id="
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const calendarParams = new URLSearchParams({
|
|
38
|
-
height: 600,
|
|
39
|
-
wkst: 1,
|
|
40
|
-
ctz: "Asia/Taipei",
|
|
41
|
-
showPrint: 0,
|
|
42
|
-
showTitle: 0,
|
|
43
|
-
showDate: 0,
|
|
44
|
-
showNav: 0,
|
|
45
|
-
showCalendars: 0,
|
|
46
|
-
showTabs: 0,
|
|
47
|
-
mode: "WEEK"
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const calendars = {{CALENDAR_SOURCES}};
|
|
51
|
-
|
|
52
|
-
const defaultColor = "%23A1B3FF";
|
|
53
|
-
|
|
54
|
-
const parseDatesRange = (value) => {
|
|
55
|
-
if (!value) return null;
|
|
56
|
-
const match = /^(\d{8})\/(\d{8})$/.exec(value);
|
|
57
|
-
if (!match) return null;
|
|
58
|
-
const [, start, end] = match;
|
|
54
|
+
<iframe id="open-web-calendar"
|
|
55
|
+
style="background:url('https://raw.githubusercontent.com/niccokunzmann/open-web-calendar/master/static/img/loaders/circular-loader.gif') center center no-repeat;"
|
|
56
|
+
sandbox="allow-scripts allow-same-origin allow-downloads"
|
|
57
|
+
allowTransparency="true" scrolling="no"
|
|
58
|
+
frameborder="0" width="100%"></iframe>
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
if (start > end) return null;
|
|
62
|
-
|
|
63
|
-
return `${start}/${end}`;
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const datesFromQuery = parseDatesRange(searchParams.get("dates"));
|
|
67
|
-
if (datesFromQuery) {
|
|
68
|
-
calendarParams.set("dates", datesFromQuery);
|
|
69
|
-
}
|
|
60
|
+
<p style="position: absolute; bottom: 2px; left: 0; right: 0; text-align: center; font-size: 9px; color: #ffffff; background-color: #000000; margin: 0; padding: 4px; pointer-events: none;">Copyright (c) {{YEAR}} dytsou</p>
|
|
70
61
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
<script>
|
|
63
|
+
// Filter console logs to hide calendar URLs and sensitive information
|
|
64
|
+
(function() {
|
|
65
|
+
const originalError = console.error;
|
|
66
|
+
const originalLog = console.log;
|
|
67
|
+
const originalWarn = console.warn;
|
|
68
|
+
const originalInfo = console.info;
|
|
69
|
+
|
|
70
|
+
// Patterns to detect and sanitize calendar URLs in error messages
|
|
71
|
+
const urlPatterns = [
|
|
72
|
+
/https?:\/\/[^\s"']+\.ics/gi,
|
|
73
|
+
/https?:\/\/calendar\.google\.com\/calendar\/ical\/[^\s"']+/gi,
|
|
74
|
+
/%40[^\s"']+/gi, // URL-encoded @ symbols
|
|
75
|
+
/@[^\s"']+\.ics/gi, // @ symbols in URLs
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
function sanitizeMessage(message) {
|
|
79
|
+
if (typeof message !== 'string') {
|
|
80
|
+
return message;
|
|
81
|
+
}
|
|
82
|
+
let sanitized = message;
|
|
83
|
+
urlPatterns.forEach(pattern => {
|
|
84
|
+
sanitized = sanitized.replace(pattern, '[Calendar URL hidden]');
|
|
85
|
+
});
|
|
86
|
+
return sanitized;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function sanitizeArgs(args) {
|
|
90
|
+
return args.map(arg => {
|
|
91
|
+
if (typeof arg === 'string') {
|
|
92
|
+
return sanitizeMessage(arg);
|
|
93
|
+
} else if (arg && typeof arg === 'object') {
|
|
94
|
+
// Try to sanitize object string representations
|
|
95
|
+
try {
|
|
96
|
+
const str = JSON.stringify(arg);
|
|
97
|
+
const sanitized = sanitizeMessage(str);
|
|
98
|
+
if (sanitized !== str) {
|
|
99
|
+
return JSON.parse(sanitized);
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// If JSON parsing fails, return original
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return arg;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.error = function(...args) {
|
|
110
|
+
// Check if message contains calendar URL patterns
|
|
111
|
+
const message = args.join(' ');
|
|
112
|
+
if (urlPatterns.some(pattern => pattern.test(message))) {
|
|
113
|
+
// Replace with sanitized version
|
|
114
|
+
const sanitized = sanitizeArgs(args);
|
|
115
|
+
originalError.apply(console, sanitized);
|
|
116
|
+
} else {
|
|
117
|
+
originalError.apply(console, args);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Helper function to check if args contain calendar info
|
|
122
|
+
function containsCalendarInfo(args) {
|
|
123
|
+
// Check string messages
|
|
124
|
+
const message = args.map(arg => {
|
|
125
|
+
if (typeof arg === 'string') {
|
|
126
|
+
return arg;
|
|
127
|
+
} else if (arg && typeof arg === 'object') {
|
|
128
|
+
try {
|
|
129
|
+
return JSON.stringify(arg);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
return String(arg);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return String(arg);
|
|
135
|
+
}).join(' ');
|
|
136
|
+
|
|
137
|
+
// Check for calendar info patterns
|
|
138
|
+
if (message.includes('Calendar Info:') ||
|
|
139
|
+
message.includes('calendars:') ||
|
|
140
|
+
message.includes('calendar_index:') ||
|
|
141
|
+
message.includes('url_index:') ||
|
|
142
|
+
message.includes('HTTPError') ||
|
|
143
|
+
message.includes('Error in ConvertToCalendars')) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if any arg is an object with calendar-related properties
|
|
148
|
+
for (const arg of args) {
|
|
149
|
+
if (arg && typeof arg === 'object') {
|
|
150
|
+
const keys = Object.keys(arg);
|
|
151
|
+
if (keys.includes('calendars') ||
|
|
152
|
+
keys.includes('calendar_index') ||
|
|
153
|
+
keys.includes('url_index') ||
|
|
154
|
+
(arg.calendars && Array.isArray(arg.calendars))) {
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log = function(...args) {
|
|
164
|
+
// Filter out calendar-related logs completely
|
|
165
|
+
if (containsCalendarInfo(args) ||
|
|
166
|
+
urlPatterns.some(pattern => {
|
|
167
|
+
const message = args.map(a => String(a)).join(' ');
|
|
168
|
+
return pattern.test(message);
|
|
169
|
+
})) {
|
|
170
|
+
// Don't log calendar info at all
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
originalLog.apply(console, args);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
console.warn = function(...args) {
|
|
177
|
+
const message = args.map(a => String(a)).join(' ');
|
|
178
|
+
if (urlPatterns.some(pattern => pattern.test(message))) {
|
|
179
|
+
const sanitized = sanitizeArgs(args);
|
|
180
|
+
originalWarn.apply(console, sanitized);
|
|
181
|
+
} else {
|
|
182
|
+
originalWarn.apply(console, args);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
console.info = function(...args) {
|
|
187
|
+
// Filter out calendar-related info completely
|
|
188
|
+
if (containsCalendarInfo(args) ||
|
|
189
|
+
urlPatterns.some(pattern => {
|
|
190
|
+
const message = args.map(a => String(a)).join(' ');
|
|
191
|
+
return pattern.test(message);
|
|
192
|
+
})) {
|
|
193
|
+
// Don't log calendar info at all
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
originalInfo.apply(console, args);
|
|
197
|
+
};
|
|
198
|
+
})();
|
|
199
|
+
|
|
200
|
+
// Get mode from URL parameter (e.g., ?mode=month, ?mode=week, ?mode=day)
|
|
201
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
202
|
+
const requestedMode = urlParams.get('mode');
|
|
203
|
+
const requestedDate = urlParams.get('date');
|
|
204
|
+
const requestedTheme = urlParams.get('theme');
|
|
205
|
+
|
|
206
|
+
// Valid modes and their mapping to open-web-calendar tab values
|
|
207
|
+
const validModes = ['month', 'week', 'day'];
|
|
208
|
+
const defaultMode = 'week';
|
|
209
|
+
|
|
210
|
+
// Determine the tab to use
|
|
211
|
+
let selectedTab = defaultMode;
|
|
74
212
|
if (requestedMode) {
|
|
75
|
-
const normalizedMode = requestedMode.
|
|
76
|
-
if (
|
|
77
|
-
|
|
213
|
+
const normalizedMode = requestedMode.toLowerCase();
|
|
214
|
+
if (validModes.includes(normalizedMode)) {
|
|
215
|
+
selectedTab = normalizedMode;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Determine the theme: URL parameter takes precedence, then browser preference
|
|
220
|
+
const validThemes = ['dark', 'light'];
|
|
221
|
+
let selectedTheme;
|
|
222
|
+
if (requestedTheme) {
|
|
223
|
+
const normalizedTheme = requestedTheme.toLowerCase();
|
|
224
|
+
if (validThemes.includes(normalizedTheme)) {
|
|
225
|
+
selectedTheme = normalizedTheme;
|
|
78
226
|
} else {
|
|
79
|
-
|
|
227
|
+
// Invalid theme parameter, fall back to browser preference
|
|
228
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
229
|
+
selectedTheme = prefersDark ? 'dark' : 'light';
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// No theme parameter, use browser preference
|
|
233
|
+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
234
|
+
selectedTheme = prefersDark ? 'dark' : 'light';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Parse and validate date parameter (YYYYMMDD format)
|
|
238
|
+
let selectedDate = null;
|
|
239
|
+
if (requestedDate) {
|
|
240
|
+
// Validate YYYYMMDD format
|
|
241
|
+
const dateMatch = /^(\d{4})(\d{2})(\d{2})$/.exec(requestedDate);
|
|
242
|
+
if (dateMatch) {
|
|
243
|
+
const [, year, month, day] = dateMatch;
|
|
244
|
+
// Validate the date is actually valid
|
|
245
|
+
const dateObj = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|
246
|
+
if (dateObj.getFullYear() == year &&
|
|
247
|
+
dateObj.getMonth() == parseInt(month) - 1 &&
|
|
248
|
+
dateObj.getDate() == day) {
|
|
249
|
+
// Convert YYYYMMDD to YYYY-MM-DD format for open-web-calendar
|
|
250
|
+
selectedDate = `${year}-${month}-${day}`;
|
|
251
|
+
}
|
|
80
252
|
}
|
|
81
253
|
}
|
|
254
|
+
|
|
255
|
+
// Calendar configuration
|
|
256
|
+
// Use Cloudflare Worker to decrypt fernet:// URLs server-side
|
|
257
|
+
// IMPORTANT: Must use /calendar.html so document.location.pathname is correct
|
|
258
|
+
// The calendar.js uses pathname.replace(/.html$/, ".json") for API calls
|
|
259
|
+
const calendarConfig = {
|
|
260
|
+
baseUrl: '{{WORKER_URL}}/calendar.html',
|
|
261
|
+
params: {
|
|
262
|
+
controls: '',
|
|
263
|
+
menu: 'false',
|
|
264
|
+
css: selectedTheme === 'dark'
|
|
265
|
+
? '.dhx_scale_holder_now, .dhx_now .dhx_month_head, .dhx_now .dhx_month_body { background-color: #000000;}.dhx_month_body, .dhx_month_head, .dhx_cal_container { background-color: #000000; }.dhx_cal_navline, .dhx_cal_navline div, button[aria-label*="menu"], button[aria-label*="Menu"], .menu-button, [class*="menu"], [class*="hamburger"] { display: none !important; }'
|
|
266
|
+
: '.dhx_cal_navline, .dhx_cal_navline div, button[aria-label*="menu"], button[aria-label*="Menu"], .menu-button, [class*="menu"], [class*="hamburger"] { display: none !important; }',
|
|
267
|
+
event_url_geo: 'https://www.google.com/maps/@{lat},{lon},{zoom}z',
|
|
268
|
+
event_url_location: 'https://www.google.com/maps/search/{location}',
|
|
269
|
+
menu_shows_calendar_names: 'false',
|
|
270
|
+
menu_shows_description: 'false',
|
|
271
|
+
menu_shows_title: 'false',
|
|
272
|
+
skin: selectedTheme,
|
|
273
|
+
start_of_week: 'su',
|
|
274
|
+
'style-event-status-cancelled': 'true',
|
|
275
|
+
'style-event-status-confirmed': 'true',
|
|
276
|
+
'style-event-status-tentative': 'true',
|
|
277
|
+
tab: selectedTab,
|
|
278
|
+
tabs: '',
|
|
279
|
+
target: '_self',
|
|
280
|
+
title: 'My Calendar'
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Add date parameter if provided
|
|
285
|
+
if (selectedDate) {
|
|
286
|
+
calendarConfig.params.date = selectedDate;
|
|
287
|
+
}
|
|
82
288
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
289
|
+
// Build calendar URL
|
|
290
|
+
// Note: Calendar URLs are stored in Cloudflare Worker secrets
|
|
291
|
+
// The worker automatically adds them to all requests
|
|
292
|
+
const buildCalendarUrl = (config) => {
|
|
293
|
+
const url = new URL(config.baseUrl);
|
|
294
|
+
|
|
295
|
+
// Add regular parameters
|
|
296
|
+
Object.entries(config.params).forEach(([key, value]) => {
|
|
297
|
+
if (value !== '' && value !== null) {
|
|
298
|
+
url.searchParams.append(key, value);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Calendar URLs are handled by the Cloudflare Worker from secrets
|
|
303
|
+
// No need to pass them in the URL
|
|
304
|
+
|
|
305
|
+
return url.toString();
|
|
306
|
+
};
|
|
87
307
|
|
|
88
|
-
|
|
308
|
+
// Set iframe source
|
|
309
|
+
const iframe = document.getElementById('open-web-calendar');
|
|
310
|
+
iframe.src = buildCalendarUrl(calendarConfig);
|
|
311
|
+
|
|
312
|
+
// Intercept and filter error messages from iframe
|
|
313
|
+
// Note: This only works if iframe is same-origin
|
|
314
|
+
iframe.addEventListener('load', function() {
|
|
315
|
+
try {
|
|
316
|
+
// Try to access iframe content (only works if same-origin)
|
|
317
|
+
const iframeWindow = iframe.contentWindow;
|
|
318
|
+
if (iframeWindow) {
|
|
319
|
+
// Override console methods in iframe
|
|
320
|
+
const originalIframeError = iframeWindow.console.error;
|
|
321
|
+
const urlPatterns = [
|
|
322
|
+
/https?:\/\/[^\s"']+\.ics/gi,
|
|
323
|
+
/calendar\.google\.com\/calendar\/ical\/[^\s"']+/gi,
|
|
324
|
+
/%40[^\s"']+/gi,
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
iframeWindow.console.error = function(...args) {
|
|
328
|
+
const message = args.join(' ');
|
|
329
|
+
if (urlPatterns.some(pattern => pattern.test(message))) {
|
|
330
|
+
// Don't log errors with calendar URLs
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
originalIframeError.apply(iframeWindow.console, args);
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
} catch (e) {
|
|
337
|
+
// Cross-origin restriction - can't access iframe content
|
|
338
|
+
// Console filtering on parent page will still work
|
|
339
|
+
}
|
|
340
|
+
});
|
|
89
341
|
|
|
90
342
|
window.onload = () => document.body.removeAttribute('unresolved');
|
|
91
343
|
</script>
|
|
92
344
|
|
|
93
345
|
</body>
|
|
94
346
|
|
|
95
|
-
</html>
|
|
96
|
-
|
|
347
|
+
</html>
|