apidocly 1.0.3

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.
@@ -0,0 +1,243 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="theme-color" content="#09090b">
7
+ <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
8
+ <meta http-equiv="Pragma" content="no-cache">
9
+ <meta http-equiv="Expires" content="0">
10
+ <base href="./">
11
+ <title>{{PROJECT_TITLE}}</title>
12
+ <link rel="stylesheet" href="css/style.css?v={{CACHE_VERSION}}">
13
+ </head>
14
+ <body>
15
+ <div id="login-container"></div>
16
+
17
+ <div id="app" class="app hidden">
18
+ <!-- Noise Overlay -->
19
+ <div class="noise-overlay"></div>
20
+
21
+ <!-- Header -->
22
+ <header class="app-header">
23
+ <div class="header-left">
24
+ <a href="#" class="logo-link" id="logo-link">
25
+ <div class="logo-icon">{}</div>
26
+ <h1 class="logo">{{PROJECT_NAME}}</h1>
27
+ </a>
28
+ <span class="version-badge">v{{PROJECT_VERSION}}</span>
29
+ </div>
30
+ <div class="header-right">
31
+ <div class="version-selector-wrapper hidden" id="version-selector-wrapper">
32
+ <select class="version-selector" id="version-selector" title="Select API version">
33
+ </select>
34
+ <button class="version-compare-toggle" id="version-compare-toggle" title="Compare versions">
35
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
36
+ <path d="M16 3h5v5"></path>
37
+ <path d="M8 3H3v5"></path>
38
+ <path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3"></path>
39
+ <path d="m15 9 6-6"></path>
40
+ </svg>
41
+ </button>
42
+ </div>
43
+ <div class="env-selector-wrapper hidden" id="env-selector-wrapper">
44
+ <select class="env-selector" id="env-selector" title="Select environment">
45
+ </select>
46
+ </div>
47
+ <div class="search-wrapper">
48
+ <svg class="search-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
49
+ <circle cx="11" cy="11" r="8"></circle>
50
+ <path d="m21 21-4.35-4.35"></path>
51
+ </svg>
52
+ <input type="text" class="search-input" placeholder="Search endpoints..." id="search-input">
53
+ <button class="search-clear hidden" id="search-clear">×</button>
54
+ </div>
55
+ <div class="settings-wrapper">
56
+ <button class="settings-btn" id="settings-btn" title="Settings">
57
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
58
+ <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path>
59
+ <circle cx="12" cy="12" r="3"></circle>
60
+ </svg>
61
+ </button>
62
+ <div class="settings-panel hidden" id="settings-panel">
63
+ <div class="settings-header">
64
+ <span>Settings</span>
65
+ <button class="settings-close" id="settings-close">×</button>
66
+ </div>
67
+ <div class="settings-content">
68
+ <div class="settings-group">
69
+ <div class="settings-group-title">Default States</div>
70
+ <label class="settings-item">
71
+ <span class="settings-label">Expand examples by default</span>
72
+ <input type="checkbox" class="settings-toggle" id="setting-expand-examples">
73
+ </label>
74
+ <label class="settings-item">
75
+ <span class="settings-label">Expand endpoints by default</span>
76
+ <input type="checkbox" class="settings-toggle" id="setting-expand-endpoints">
77
+ </label>
78
+ </div>
79
+ <div class="settings-group">
80
+ <div class="settings-group-title">Behavior</div>
81
+ <label class="settings-item">
82
+ <span class="settings-label">Auto-collapse other endpoints</span>
83
+ <input type="checkbox" class="settings-toggle" id="setting-auto-collapse" checked>
84
+ </label>
85
+ <label class="settings-item">
86
+ <span class="settings-label">Show "Try it out" section</span>
87
+ <input type="checkbox" class="settings-toggle" id="setting-show-tryit" checked>
88
+ </label>
89
+ <label class="settings-item">
90
+ <span class="settings-label">Show code samples</span>
91
+ <input type="checkbox" class="settings-toggle" id="setting-show-codesamples" checked>
92
+ </label>
93
+ </div>
94
+ <div class="settings-group">
95
+ <div class="settings-group-title">Appearance</div>
96
+ <label class="settings-item">
97
+ <span class="settings-label">Compact mode</span>
98
+ <input type="checkbox" class="settings-toggle" id="setting-compact-mode">
99
+ </label>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ <button class="logout-btn hidden" id="logout-btn" title="Logout">
105
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
106
+ <path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
107
+ <polyline points="16 17 21 12 16 7"></polyline>
108
+ <line x1="21" y1="12" x2="9" y2="12"></line>
109
+ </svg>
110
+ </button>
111
+ </div>
112
+ </header>
113
+
114
+ <!-- Main Content -->
115
+ <main class="main-content">
116
+ <!-- Stats Cards -->
117
+ <div class="stats-grid" id="stats-grid">
118
+ <div class="stat-card">
119
+ <div class="stat-value" id="stat-endpoints">0</div>
120
+ <div class="stat-label">Endpoints</div>
121
+ </div>
122
+ <div class="stat-card">
123
+ <div class="stat-value" id="stat-groups">0</div>
124
+ <div class="stat-label">Groups</div>
125
+ </div>
126
+ <div class="stat-card">
127
+ <div class="stat-value" id="stat-private">0</div>
128
+ <div class="stat-label">Private</div>
129
+ </div>
130
+ <div class="stat-card">
131
+ <div class="stat-value" id="stat-version">-</div>
132
+ <div class="stat-label">Version</div>
133
+ </div>
134
+ </div>
135
+
136
+ <!-- Version Comparison Panel -->
137
+ <div class="version-compare-panel hidden" id="version-compare-panel">
138
+ <div class="version-compare-header">
139
+ <h3>Compare Versions</h3>
140
+ <button class="version-compare-close" id="version-compare-close">×</button>
141
+ </div>
142
+ <div class="version-compare-selectors">
143
+ <div class="version-select-group">
144
+ <label>From version:</label>
145
+ <select id="version-from" class="version-select"></select>
146
+ </div>
147
+ <div class="version-arrow">→</div>
148
+ <div class="version-select-group">
149
+ <label>To version:</label>
150
+ <select id="version-to" class="version-select"></select>
151
+ </div>
152
+ <button class="version-compare-btn" id="version-compare-btn">Compare</button>
153
+ </div>
154
+ <div class="version-compare-results hidden" id="version-compare-results">
155
+ <div class="version-diff-summary" id="version-diff-summary"></div>
156
+ <div class="version-diff-details" id="version-diff-details"></div>
157
+ </div>
158
+ </div>
159
+
160
+ <!-- Content Container -->
161
+ <div class="content-container" id="content-container">
162
+ <!-- Groups View (default) -->
163
+ <div class="groups-view" id="groups-view">
164
+ <div class="section-header">
165
+ <h2>API Groups</h2>
166
+ <div class="export-dropdown-wrapper">
167
+ <button class="export-btn export-dropdown-toggle" id="export-dropdown-toggle">
168
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
169
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
170
+ <polyline points="7 10 12 15 17 10"></polyline>
171
+ <line x1="12" y1="15" x2="12" y2="3"></line>
172
+ </svg>
173
+ <span>Export</span>
174
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="dropdown-arrow">
175
+ <path d="m6 9 6 6 6-6"></path>
176
+ </svg>
177
+ </button>
178
+ <div class="export-dropdown-menu hidden" id="export-dropdown-menu">
179
+ <button class="export-dropdown-item" onclick="downloadOpenAPI('__all__')">
180
+ <span class="export-icon">{ }</span> OpenAPI JSON
181
+ </button>
182
+ <button class="export-dropdown-item" onclick="downloadPostmanCollection('__all__')">
183
+ <span class="export-icon">PM</span> Postman Collection
184
+ </button>
185
+ <button class="export-dropdown-item" onclick="downloadInsomniaCollection('__all__')">
186
+ <span class="export-icon">IN</span> Insomnia Collection
187
+ </button>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ <div class="groups-list" id="groups-list">
192
+ <!-- Generated by JS -->
193
+ </div>
194
+ </div>
195
+
196
+ <!-- Endpoints View (when group selected) -->
197
+ <div class="endpoints-view hidden" id="endpoints-view">
198
+ <div class="section-header">
199
+ <button class="back-btn" id="back-to-groups">
200
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
201
+ <path d="m15 18-6-6 6-6"></path>
202
+ </svg>
203
+ Back to groups
204
+ </button>
205
+ <div class="current-group" id="current-group">
206
+ <span class="group-color-bar"></span>
207
+ <span class="group-name"></span>
208
+ <span class="group-count"></span>
209
+ </div>
210
+ <button class="group-export-btn" id="group-export-btn" title="Download as OpenAPI JSON">
211
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
212
+ <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
213
+ <polyline points="7 10 12 15 17 10"></polyline>
214
+ <line x1="12" y1="15" x2="12" y2="3"></line>
215
+ </svg>
216
+ <span>OpenAPI</span>
217
+ </button>
218
+ </div>
219
+ <div class="endpoints-list" id="endpoints-list">
220
+ <!-- Generated by JS -->
221
+ </div>
222
+ </div>
223
+
224
+ <!-- Search Results View -->
225
+ <div class="search-view hidden" id="search-view">
226
+ <div class="section-header">
227
+ <h2>Search results for "<span id="search-query"></span>"</h2>
228
+ <button class="clear-search-btn" id="clear-search">Clear ×</button>
229
+ </div>
230
+ <div class="search-results" id="search-results">
231
+ <!-- Generated by JS -->
232
+ </div>
233
+ </div>
234
+ </div>
235
+ </main>
236
+ </div>
237
+
238
+ <script src="api_data.js?v={{CACHE_VERSION}}"></script>
239
+ <script src="versions.js?v={{CACHE_VERSION}}" onerror="window.apiVersions=[];window.apiVersionsConfig={withCompare:false};window.apiVersionsData={}"></script>
240
+ <script src="js/auth.js?v={{CACHE_VERSION}}"></script>
241
+ <script src="js/main.js?v={{CACHE_VERSION}}"></script>
242
+ </body>
243
+ </html>
@@ -0,0 +1,281 @@
1
+ (function() {
2
+ 'use strict';
3
+
4
+ const AUTH_STORAGE_KEY = 'apidocly-auth';
5
+ const AUTH_PWD_KEY = 'apidocly-pwd'; // Encrypted password storage for session
6
+
7
+ window.apiDoclyAuth = {
8
+ passwordHash: null,
9
+ passwordMessage: 'Enter password to view API documentation',
10
+ authData: null,
11
+ onAuthenticatedCallback: null,
12
+ isReady: false,
13
+
14
+ init: function(authData) {
15
+ this.authData = authData;
16
+
17
+ if (!authData) {
18
+ // No auth required
19
+ this.showApp();
20
+ return;
21
+ }
22
+
23
+ this.passwordHash = authData.hash;
24
+ if (authData.message) {
25
+ this.passwordMessage = authData.message;
26
+ }
27
+
28
+ if (this.isAuthenticated()) {
29
+ // Already authenticated, try to decrypt if needed
30
+ this.handleDecryptionAndShow();
31
+ } else {
32
+ this.showLoginForm();
33
+ }
34
+ },
35
+
36
+ isAuthenticated: function() {
37
+ const stored = localStorage.getItem(AUTH_STORAGE_KEY);
38
+ return stored === this.passwordHash;
39
+ },
40
+
41
+ handleDecryptionAndShow: async function() {
42
+ if (this.authData && this.authData.encrypted && typeof apiDataEncrypted !== 'undefined' && apiDataEncrypted) {
43
+ // Need to decrypt - get stored password
44
+ const storedPwd = sessionStorage.getItem(AUTH_PWD_KEY);
45
+ if (storedPwd) {
46
+ try {
47
+ const decrypted = await this.decryptData(apiDataEncrypted, storedPwd);
48
+ window.apiData = decrypted;
49
+ this.showApp();
50
+ } catch (e) {
51
+ // Decryption failed, need to re-login
52
+ console.error('Decryption failed:', e);
53
+ localStorage.removeItem(AUTH_STORAGE_KEY);
54
+ sessionStorage.removeItem(AUTH_PWD_KEY);
55
+ this.showLoginForm();
56
+ }
57
+ } else {
58
+ // No stored password, need to re-login
59
+ localStorage.removeItem(AUTH_STORAGE_KEY);
60
+ this.showLoginForm();
61
+ }
62
+ } else {
63
+ // Not encrypted or no encryption needed
64
+ this.showApp();
65
+ }
66
+ },
67
+
68
+ showLoginForm: function() {
69
+ const container = document.getElementById('login-container');
70
+
71
+ container.innerHTML = `
72
+ <div class="login-page">
73
+ <div class="login-bg-pattern"></div>
74
+ <div class="login-card">
75
+ <div class="login-icon">
76
+ <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
77
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
78
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
79
+ </svg>
80
+ </div>
81
+ <h1 class="login-title">Protected Documentation</h1>
82
+ <p class="login-subtitle">${this.escapeHtml(this.passwordMessage)}</p>
83
+ <form class="login-form" id="login-form">
84
+ <div class="login-input-wrapper">
85
+ <svg class="login-input-icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
86
+ <rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
87
+ <path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
88
+ </svg>
89
+ <input type="password" class="login-input" id="password-input" placeholder="Enter password" autofocus>
90
+ </div>
91
+ <button type="submit" class="login-btn">
92
+ <span>Unlock Documentation</span>
93
+ <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
94
+ <path d="M5 12h14M12 5l7 7-7 7"></path>
95
+ </svg>
96
+ </button>
97
+ <div class="login-error hidden" id="login-error">
98
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
99
+ <circle cx="12" cy="12" r="10"></circle>
100
+ <line x1="15" y1="9" x2="9" y2="15"></line>
101
+ <line x1="9" y1="9" x2="15" y2="15"></line>
102
+ </svg>
103
+ <span>Invalid password. Please try again.</span>
104
+ </div>
105
+ </form>
106
+ </div>
107
+ </div>
108
+ `;
109
+
110
+ document.getElementById('login-form').addEventListener('submit', (e) => {
111
+ e.preventDefault();
112
+ this.handleLogin();
113
+ });
114
+ },
115
+
116
+ handleLogin: async function() {
117
+ const passwordInput = document.getElementById('password-input');
118
+ const errorDiv = document.getElementById('login-error');
119
+ const loginBtn = document.querySelector('.login-btn');
120
+ const password = passwordInput.value;
121
+
122
+ // Show loading state
123
+ if (loginBtn) {
124
+ loginBtn.disabled = true;
125
+ loginBtn.innerHTML = '<span>Decrypting...</span>';
126
+ }
127
+
128
+ const hash = await this.hashPassword(password);
129
+
130
+ if (hash === this.passwordHash) {
131
+ // Password correct, now decrypt if needed
132
+ if (this.authData && this.authData.encrypted && typeof apiDataEncrypted !== 'undefined' && apiDataEncrypted) {
133
+ try {
134
+ const decrypted = await this.decryptData(apiDataEncrypted, password);
135
+ window.apiData = decrypted;
136
+ // Store password in sessionStorage for page refreshes
137
+ sessionStorage.setItem(AUTH_PWD_KEY, password);
138
+ localStorage.setItem(AUTH_STORAGE_KEY, hash);
139
+ this.showApp();
140
+ } catch (e) {
141
+ console.error('Decryption failed:', e);
142
+ errorDiv.classList.remove('hidden');
143
+ passwordInput.value = '';
144
+ passwordInput.focus();
145
+ if (loginBtn) {
146
+ loginBtn.disabled = false;
147
+ loginBtn.innerHTML = '<span>Unlock Documentation</span><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"></path></svg>';
148
+ }
149
+ setTimeout(() => {
150
+ errorDiv.classList.add('hidden');
151
+ }, 3000);
152
+ }
153
+ } else {
154
+ // No encryption, just authenticate
155
+ localStorage.setItem(AUTH_STORAGE_KEY, hash);
156
+ this.showApp();
157
+ }
158
+ } else {
159
+ errorDiv.classList.remove('hidden');
160
+ passwordInput.value = '';
161
+ passwordInput.focus();
162
+ if (loginBtn) {
163
+ loginBtn.disabled = false;
164
+ loginBtn.innerHTML = '<span>Unlock Documentation</span><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"></path></svg>';
165
+ }
166
+ setTimeout(() => {
167
+ errorDiv.classList.add('hidden');
168
+ }, 3000);
169
+ }
170
+ },
171
+
172
+ decryptData: async function(encryptedBase64, password) {
173
+ const auth = this.authData;
174
+
175
+ // Decode base64
176
+ const combined = Uint8Array.from(atob(encryptedBase64), c => c.charCodeAt(0));
177
+
178
+ // Extract parts
179
+ const salt = combined.slice(0, auth.saltLength);
180
+ const iv = combined.slice(auth.saltLength, auth.saltLength + auth.ivLength);
181
+ const authTag = combined.slice(auth.saltLength + auth.ivLength, auth.saltLength + auth.ivLength + auth.authTagLength);
182
+ const encrypted = combined.slice(auth.saltLength + auth.ivLength + auth.authTagLength);
183
+
184
+ // Combine encrypted data with auth tag (Web Crypto expects them together)
185
+ const encryptedWithTag = new Uint8Array(encrypted.length + authTag.length);
186
+ encryptedWithTag.set(encrypted);
187
+ encryptedWithTag.set(authTag, encrypted.length);
188
+
189
+ // Derive key using PBKDF2
190
+ const encoder = new TextEncoder();
191
+ const passwordKey = await crypto.subtle.importKey(
192
+ 'raw',
193
+ encoder.encode(password),
194
+ 'PBKDF2',
195
+ false,
196
+ ['deriveKey']
197
+ );
198
+
199
+ const aesKey = await crypto.subtle.deriveKey(
200
+ {
201
+ name: 'PBKDF2',
202
+ salt: salt,
203
+ iterations: auth.pbkdf2Iterations,
204
+ hash: 'SHA-256'
205
+ },
206
+ passwordKey,
207
+ { name: 'AES-GCM', length: 256 },
208
+ false,
209
+ ['decrypt']
210
+ );
211
+
212
+ // Decrypt
213
+ const decrypted = await crypto.subtle.decrypt(
214
+ { name: 'AES-GCM', iv: iv },
215
+ aesKey,
216
+ encryptedWithTag
217
+ );
218
+
219
+ // Parse JSON
220
+ const decoder = new TextDecoder();
221
+ return JSON.parse(decoder.decode(decrypted));
222
+ },
223
+
224
+ showApp: function() {
225
+ const container = document.getElementById('login-container');
226
+ container.innerHTML = '';
227
+
228
+ const app = document.getElementById('app');
229
+ app.classList.remove('hidden');
230
+
231
+ if (this.passwordHash) {
232
+ const logoutBtn = document.getElementById('logout-btn');
233
+ logoutBtn.classList.remove('hidden');
234
+ logoutBtn.addEventListener('click', () => this.logout());
235
+ }
236
+
237
+ this.isReady = true;
238
+
239
+ if (this.onAuthenticatedCallback) {
240
+ this.onAuthenticatedCallback();
241
+ }
242
+ },
243
+
244
+ logout: function() {
245
+ localStorage.removeItem(AUTH_STORAGE_KEY);
246
+ sessionStorage.removeItem(AUTH_PWD_KEY);
247
+ window.location.reload();
248
+ },
249
+
250
+ onAuthenticated: function(callback) {
251
+ this.onAuthenticatedCallback = callback;
252
+ },
253
+
254
+ hashPassword: async function(password) {
255
+ const encoder = new TextEncoder();
256
+ const data = encoder.encode(password);
257
+ const hashBuffer = await crypto.subtle.digest('SHA-256', data);
258
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
259
+ return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
260
+ },
261
+
262
+ escapeHtml: function(str) {
263
+ if (!str) return '';
264
+ return String(str)
265
+ .replace(/&/g, '&amp;')
266
+ .replace(/</g, '&lt;')
267
+ .replace(/>/g, '&gt;')
268
+ .replace(/"/g, '&quot;')
269
+ .replace(/'/g, '&#39;');
270
+ }
271
+ };
272
+
273
+ document.addEventListener('DOMContentLoaded', function() {
274
+ // Use apiDataAuth for auth info (separate from potentially encrypted apiData)
275
+ if (typeof apiDataAuth !== 'undefined' && apiDataAuth) {
276
+ window.apiDoclyAuth.init(apiDataAuth);
277
+ } else {
278
+ window.apiDoclyAuth.init(null);
279
+ }
280
+ });
281
+ })();