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 " />
6
+ < title > Schema Doctor: Reducer Edition</ title >
7
+ < link rel ="stylesheet " href ="style.css " />
8
+ < link rel ="preconnect " href ="https://cdnjs.cloudflare.com " crossorigin >
9
+ < link rel ="preconnect " href ="https://cdn.jsdelivr.net " crossorigin >
10
+ </ head >
11
+ < body class ="theme-dark ">
12
+ < header class ="topbar ">
13
+ < div class ="brand "> 🩺 Schema Doctor: < span class ="muted "> Reducer Edition</ span > </ div >
14
+ < div class ="actions ">
15
+ < button id ="themeToggle " class ="btn " title ="Toggle theme "> 🌗 Theme</ button >
16
+ < button id ="copyBtn " class ="btn " title ="Copy reduced JSON "> 📋 Copy</ button >
17
+ < button id ="downloadBtn " class ="btn primary " title ="Download reduced.json "> ⬇️ Download</ button >
18
+ </ div >
19
+ </ header >
20
+
21
+ < main class ="layout ">
22
+ < section class ="pane inputs ">
23
+ < h2 > Input</ h2 >
24
+
25
+ < div class ="input-row ">
26
+ < label for ="schemaUrl "> Fetch by URL</ label >
27
+ < div class ="urlbar ">
28
+ < input id ="schemaUrl " type ="url " placeholder ="https://example.com/openapi.yaml or .json " />
29
+ < button id ="fetchBtn " class ="btn "> Fetch</ button >
30
+ </ div >
31
+ </ div >
32
+
33
+ < div class ="input-row ">
34
+ < label > Upload file (JSON or YAML)</ label >
35
+ < div id ="dropZone " class ="dropzone " tabindex ="0 " aria-label ="Drop file here or click to choose ">
36
+ < input id ="fileInput " type ="file " accept =".json,.yaml,.yml,application/json,text/yaml " hidden />
37
+ < div class ="dz-hint "> Drag & drop here or < span class ="linkish "> browse</ span > </ div >
38
+ </ div >
39
+ </ div >
40
+
41
+ < div class ="input-row ">
42
+ < label for ="pasteArea "> Paste JSON/YAML</ label >
43
+ < textarea id ="pasteArea " placeholder ="Paste your OpenAPI schema here… " rows ="6 "> </ textarea >
44
+ < div class ="row-right ">
45
+ < button id ="loadPasteBtn " class ="btn "> Load Paste</ button >
46
+ </ div >
47
+ </ div >
48
+
49
+ < div class ="input-row ">
50
+ < button id ="reduceBtn " class ="btn primary "> Generate Reduced Schema</ button >
51
+ < span id ="status " class ="status " aria-live ="polite "> </ span >
52
+ </ div >
53
+
54
+ <!-- Optional Endpoint Filter (accordion, collapsed by default) -->
55
+ < details id ="filter-panel " class ="filter-accordion locked " aria-disabled ="true ">
56
+ < summary class ="filter-summary-row ">
57
+ < span class ="chev " aria-hidden ="true "> </ span >
58
+ < span > Endpoint Filter</ span >
59
+ < small id ="filter-summary " class ="summary-pill "> </ small >
60
+ </ summary >
61
+
62
+ < div class ="filter-grid " style ="display:grid; gap:.5rem; grid-template-columns: 1fr; ">
63
+ < div class ="filter-toggle ">
64
+ < label class ="switch ">
65
+ < input type ="checkbox " id ="filter-enabled " />
66
+ < span class ="slider " aria-hidden ="true "> </ span >
67
+ < span class ="switch-label "> Enable endpoint filtering</ span >
68
+ </ label >
69
+ < span class ="toggle-hint "> Only matching endpoints will be kept in the reduced schema.</ span >
70
+ </ div >
71
+
72
+ < div class ="filter-row " style ="display:flex; gap:1rem; flex-wrap:wrap; ">
73
+ < fieldset style ="border:1px solid var(--border, rgba(255,255,255,.1)); border-radius:10px; padding:.5rem .75rem; ">
74
+ < legend > Methods</ legend >
75
+ < label > < input type ="checkbox " class ="filter-method " value ="get " /> GET</ label >
76
+ < label > < input type ="checkbox " class ="filter-method " value ="post " /> POST</ label >
77
+ < label > < input type ="checkbox " class ="filter-method " value ="put " /> PUT</ label >
78
+ < label > < input type ="checkbox " class ="filter-method " value ="patch " /> PATCH</ label >
79
+ < label > < input type ="checkbox " class ="filter-method " value ="delete " /> DELETE</ label >
80
+ < label > < input type ="checkbox " class ="filter-method " value ="head " /> HEAD</ label >
81
+ < label > < input type ="checkbox " class ="filter-method " value ="options "/> OPTIONS</ label >
82
+ < label > < input type ="checkbox " class ="filter-method " value ="trace " /> TRACE</ label >
83
+ </ fieldset >
84
+
85
+ < fieldset style ="flex:1; min-width:280px; border:1px solid var(--border, rgba(255,255,255,.1)); border-radius:10px; padding:.5rem .75rem; ">
86
+ < legend > Path patterns (comma or newline)</ legend >
87
+ < textarea id ="filter-paths " rows ="3 " placeholder ="/zones, /dns_records or /accounts
88
+ Use /regex/ to treat a token as RegExp "
89
+ style ="width:100%; resize:vertical; "> </ textarea >
90
+ </ fieldset >
91
+ </ div >
92
+
93
+ < fieldset style ="border:1px solid var(--border, rgba(255,255,255,.1)); border-radius:10px; padding:.5rem .75rem; ">
94
+ < legend > Tags / Categories</ legend >
95
+
96
+ < div id ="filter-tags-toolbar " class ="filter-toolbar ">
97
+ < input id ="filter-tag-search " ... >
98
+ < button id ="tag-all " class ="btn "> All</ button >
99
+ < button id ="tag-none " class ="btn "> None</ button >
100
+ </ div >
101
+ < div id ="filter-tags " class ="filter-tags ">
102
+ < em style ="opacity:.7; "> Open this panel after loading a schema to see tags…</ em >
103
+ </ div >
104
+ </ fieldset >
105
+
106
+ < div style ="display:flex; gap:.5rem; align-items:center; ">
107
+ < button id ="filter-clear " class ="btn "> Clear filter</ button >
108
+ < span id ="filter-stats " style ="opacity:.8; "> </ span >
109
+ </ div >
110
+ </ div >
111
+ <!-- Pending banner (appears when filter changed but not applied) -->
112
+ < div id ="filter-pending " class ="callout info hidden ">
113
+ Filtering changes are pending. Press < strong > Generate</ strong > or
114
+ < button id ="filter-apply-inline " class ="btn small " type ="button "> Apply now</ button > .
115
+ </ div >
116
+
117
+ <!-- Persistent metrics -->
118
+ < div id ="metrics " aria-live ="polite ">
119
+ < span class ="metric-label "> Metrics</ span >
120
+ < span id ="metric-lines " class ="metric-chip "> Lines: —</ span >
121
+ < span id ="metric-actions " class ="metric-chip "> Actions: —</ span >
122
+ </ div >
123
+ < details class ="hint ">
124
+ < summary > Having CORS trouble fetching a URL?</ summary >
125
+ Try downloading the schema and using the upload/paste options. Some servers block cross‑origin fetches.
126
+ </ details >
127
+ </ section >
128
+
129
+ < section class ="pane editors ">
130
+ < div class ="editor-group ">
131
+ < h2 > Original (read‑only)</ h2 >
132
+ < div id ="originalEditor " class ="editor "> </ div >
133
+ </ div >
134
+ < div class ="editor-group ">
135
+ < h2 > Reduced (JSON)</ h2 >
136
+ < div id ="reducedEditor " class ="editor "> </ div >
137
+ </ div >
138
+ </ section >
139
+ </ main >
140
+
141
+ < footer id ="app-footer " class ="app-footer ">
142
+ Disclaimer: This app is a community-driven project built by Ben Climer and is not officially supported or endorsed by Rewst.
143
+ If you have suggestions or improvements, please submit a pull request.
144
+ </ footer >
145
+
146
+ <!-- Libraries (CDNJS, no SRI to avoid mismatches) -->
147
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/4.1.0/js-yaml.min.js " defer > </ script >
148
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/ajv/8.17.1/ajv7.min.js " defer > </ script >
149
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js " defer > </ script >
150
+
151
+ <!-- Monaco loader -->
152
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs/loader.min.js "> </ script >
153
+ < script src ="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.0/min/vs/loader.min.js " crossorigin ="anonymous "> </ script >
154
+
155
+ < script >
156
+ // Monaco worker config for CDN usage
157
+ window . MonacoEnvironment = {
158
+ getWorkerUrl : function ( workerId , label ) {
159
+ const base = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/' ;
160
+ const code = `self.MonacoEnvironment={baseUrl:'${ base } '};importScripts('${ base } vs/base/worker/workerMain.js');` ;
161
+ return URL . createObjectURL ( new Blob ( [ code ] , { type : 'text/javascript' } ) ) ;
162
+ }
163
+ } ;
164
+ require . config ( { paths : { 'vs' : 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.52.2/min/vs' } } ) ;
165
+ </ script >
166
+
167
+ <!-- App -->
168
+ < script src ="app.js "> </ script >
169
+ </ body >
170
+ </ html >
0 commit comments