rest_framework 0.9.4 → 0.9.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +130 -0
  4. data/VERSION +1 -1
  5. data/app/views/layouts/rest_framework.html.erb +9 -183
  6. data/app/views/rest_framework/_breadcrumbs.html.erb +27 -0
  7. data/app/views/rest_framework/_head.html.erb +348 -190
  8. data/app/views/rest_framework/_header.html.erb +15 -0
  9. data/app/views/rest_framework/_heading.html.erb +10 -0
  10. data/app/views/rest_framework/_payloads.html.erb +36 -0
  11. data/app/views/rest_framework/_request_metadata.html.erb +16 -0
  12. data/app/views/rest_framework/_routes_and_forms.html.erb +52 -0
  13. data/app/views/rest_framework/head/_external.html.erb +7 -2
  14. data/app/views/rest_framework/header/_mode.html.erb +14 -0
  15. data/app/views/rest_framework/heading/_actions.html.erb +9 -0
  16. data/app/views/rest_framework/{_routes.html.erb → routes_and_forms/_routes.html.erb} +2 -2
  17. data/lib/rest_framework/controller_mixins/base.rb +11 -12
  18. data/lib/rest_framework/engine.rb +5 -3
  19. data/lib/rest_framework/filters/ransack.rb +6 -6
  20. data/lib/rest_framework/version.rb +0 -6
  21. data/lib/rest_framework.rb +25 -13
  22. data/vendor/assets/javascripts/rest_framework/external.min.js +1256 -0
  23. data/vendor/assets/stylesheets/rest_framework/{bootstrap-icons.css → external.min.css} +415 -1
  24. data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-dark.css → highlight-a11y-dark.min.css} +1 -1
  25. data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-light.css → highlight-a11y-light.min.css} +1 -1
  26. metadata +18 -35
  27. data/README.md +0 -1
  28. data/app/views/rest_framework/head/_shared.html +0 -164
  29. data/docs/CNAME +0 -1
  30. data/docs/Gemfile +0 -5
  31. data/docs/Gemfile.lock +0 -264
  32. data/docs/_config.yml +0 -19
  33. data/docs/_guide/1_routers.md +0 -110
  34. data/docs/_guide/2_controllers.md +0 -342
  35. data/docs/_guide/3_serializers.md +0 -60
  36. data/docs/_guide/4_filtering_and_ordering.md +0 -41
  37. data/docs/_guide/5_pagination.md +0 -21
  38. data/docs/_includes/anchor_headings.html +0 -144
  39. data/docs/_includes/external.html +0 -9
  40. data/docs/_includes/head.html +0 -155
  41. data/docs/_includes/header.html +0 -58
  42. data/docs/_includes/shared.html +0 -164
  43. data/docs/_layouts/default.html +0 -11
  44. data/docs/assets/images/favicon.ico +0 -0
  45. data/docs/index.md +0 -133
  46. data/vendor/assets/javascripts/rest_framework/bootstrap.js +0 -7
  47. data/vendor/assets/javascripts/rest_framework/highlight-json.js +0 -7
  48. data/vendor/assets/javascripts/rest_framework/highlight-xml.js +0 -29
  49. data/vendor/assets/javascripts/rest_framework/highlight.js +0 -1202
  50. data/vendor/assets/javascripts/rest_framework/neatjson.js +0 -8
  51. data/vendor/assets/javascripts/rest_framework/trix.js +0 -6
  52. data/vendor/assets/stylesheets/rest_framework/bootstrap.css +0 -6
  53. data/vendor/assets/stylesheets/rest_framework/trix.css +0 -410
  54. /data/app/views/rest_framework/{_html_form.html.erb → routes_and_forms/_html_form.html.erb} +0 -0
  55. /data/app/views/rest_framework/{_raw_form.html.erb → routes_and_forms/_raw_form.html.erb} +0 -0
  56. /data/app/views/rest_framework/{_route.html.erb → routes_and_forms/routes/_route.html.erb} +0 -0
@@ -2,219 +2,377 @@
2
2
  <meta name="viewport" content="width=device-width, initial-scale=1">
3
3
 
4
4
  <%= render partial: "rest_framework/head/external" %>
5
- <%= render partial: "rest_framework/head/shared" %>
6
5
 
7
6
  <style>
8
- /* Header adjustments. */
9
- h1,h2,h3,h4,h5,h6 { display: inline-block; font-weight: normal; margin-bottom: 0; }
10
-
11
- /* Reduce label font size. */
12
- label.form-label {
13
- font-size: .8em;
14
- }
15
-
16
- /* Make Trix buttons visible even in dark mode. */
17
- trix-toolbar .trix-button-group {
18
- background-color: #eee;
19
- }
20
-
21
- /* Make Trix dialog URL input visible in dark mode. */
22
- input.trix-input--dialog {
23
- color: black;
24
- }
25
-
26
- /* Make route group expansion obvious to the user. */
27
- .rrf-routes .rrf-route-group-header:hover {
28
- background-color: #ddd;
29
- }
30
- html[data-bs-theme="dark"] .rrf-routes .rrf-route-group-header:hover {
31
- background-color: #333;
32
- }
33
- .rrf-routes .rrf-route-group-header td {
34
- cursor: pointer;
35
- }
36
-
37
- /* Disable bootstrap's collapsing animation because in tables it causes delayed jerkiness. */
38
- .rrf-routes .collapsing {
39
- -webkit-transition: none;
40
- transition: none;
41
- display: none;
42
- }
43
-
44
- /* Copy-to-clipboard styles. */
45
- .rrf-copy {
46
- position: relative;
47
- }
48
- .rrf-copy .rrf-copy-link {
49
- position: absolute;
50
- top: .25em;
51
- right: .4em;
52
- transition: 0.3s ease;
53
- font-size: 1.5em;
54
- }
55
- .rrf-copy .rrf-copy-link.rrf-clicked {
56
- color: green;
57
- }
58
- .rrf-copy .rrf-copy-link.rrf-clicked:hover {
59
- color: green;
60
- }
7
+ :root {
8
+ --rrf-red: #900;
9
+ --rrf-red-hover: #5f0c0c;
10
+ --rrf-light-red: #db2525;
11
+ --rrf-light-red-hover: #b80404;
12
+ }
13
+ #rrfAccentBar {
14
+ background-color: var(--rrf-red);
15
+ height: .3em;
16
+ width: 100%;
17
+ margin: 0;
18
+ padding: 0;
19
+ }
20
+ header nav { background-color: black; }
21
+
22
+ /* Header adjustments. */
23
+ h1 { font-size: 2rem; }
24
+ h2 { font-size: 1.7rem; }
25
+ h3 { font-size: 1.5rem; }
26
+ h4 { font-size: 1.3rem; }
27
+ h5 { font-size: 1.1rem; }
28
+ h6 { font-size: 1rem; }
29
+ h1, h2, h3, h4, h5, h6 {
30
+ color: var(--rrf-red);
31
+ font-weight: normal;
32
+ margin-bottom: 0;
33
+ }
34
+ html[data-bs-theme="dark"] h1,
35
+ html[data-bs-theme="dark"] h2,
36
+ html[data-bs-theme="dark"] h3,
37
+ html[data-bs-theme="dark"] h4,
38
+ html[data-bs-theme="dark"] h5,
39
+ html[data-bs-theme="dark"] h6 {
40
+ color: var(--rrf-light-red);
41
+ }
42
+
43
+ /* Improve code and code blocks. */
44
+ pre code, .trix-content pre {
45
+ display: block;
46
+ overflow-x: auto;
47
+ padding: .5em !important;
48
+ }
49
+ code, .trix-content pre {
50
+ --bs-code-color: black;
51
+ background-color: #eee !important;
52
+ border: 1px solid #aaa;
53
+ border-radius: 3px;
54
+ padding: .1em .3em;
55
+ }
56
+ html[data-bs-theme="dark"] code, html[data-bs-theme="dark"] .trix-content pre {
57
+ --bs-code-color: white;
58
+ background-color: #2b2b2b !important;
59
+ }
60
+
61
+ /* Anchors */
62
+ a:not(.nav-link) {
63
+ text-decoration: none;
64
+ color: var(--rrf-red);
65
+ }
66
+ a:hover:not(.nav-link) {
67
+ text-decoration: underline;
68
+ color: var(--rrf-red-hover);
69
+ }
70
+ html[data-bs-theme="dark"] a:not(.nav-link) { color: var(--rrf-light-red); }
71
+ html[data-bs-theme="dark"] a:hover:not(.nav-link) { color: var(--rrf-light-red-hover); }
72
+
73
+ /* Reduce label font size. */
74
+ label.form-label {
75
+ font-size: .8em;
76
+ }
77
+
78
+ /* Make Trix buttons visible even in dark mode. */
79
+ trix-toolbar .trix-button-group {
80
+ background-color: #eee;
81
+ }
82
+
83
+ /* Make Trix dialog URL input visible in dark mode. */
84
+ input.trix-input--dialog {
85
+ color: black;
86
+ }
87
+
88
+ /* Make route group expansion obvious to the user. */
89
+ .rrf-routes .rrf-route-group-header:hover {
90
+ background-color: #ddd;
91
+ }
92
+ html[data-bs-theme="dark"] .rrf-routes .rrf-route-group-header:hover {
93
+ background-color: #333;
94
+ }
95
+ .rrf-routes .rrf-route-group-header td {
96
+ cursor: pointer;
97
+ }
98
+
99
+ /* Disable bootstrap's collapsing animation because in tables it causes delayed jerkiness. */
100
+ .rrf-routes .collapsing {
101
+ -webkit-transition: none;
102
+ transition: none;
103
+ display: none;
104
+ }
105
+
106
+ /* Copy-to-clipboard styles. */
107
+ .rrf-copy {
108
+ position: relative;
109
+ }
110
+ .rrf-copy .rrf-copy-link {
111
+ position: absolute;
112
+ top: .25em;
113
+ right: .4em;
114
+ transition: 0.3s ease;
115
+ font-size: 1.5em;
116
+ }
117
+ .rrf-copy .rrf-copy-link.rrf-clicked {
118
+ color: green;
119
+ }
120
+ .rrf-copy .rrf-copy-link.rrf-clicked:hover {
121
+ color: green;
122
+ }
61
123
  </style>
62
124
 
63
125
  <script>
64
- document.addEventListener("DOMContentLoaded", (event) => {
65
- // Pretty-print JSON.
66
- document.querySelectorAll(".language-json").forEach((el) => {
67
- el.innerHTML = neatJSON(JSON.parse(el.innerText), {
68
- wrap: 80,
69
- afterComma: 1,
70
- afterColon: 1,
71
- }).replaceAll("&", "&amp;")
72
- .replaceAll("<", "&lt;")
73
- .replaceAll(">", "&gt;")
74
- .replaceAll('"', "&quot;")
75
- .replaceAll("'", "&#039;")
76
- })
126
+ // Javascript for dark/light mode.
127
+ ;(() => {
128
+ // Get the real mode from a selected mode. Anything other than "light" or "dark" is treated as
129
+ // "system" mode.
130
+ const rrfGetRealMode = (selectedMode) => {
131
+ if (selectedMode === "light" || selectedMode === "dark") {
132
+ return selectedMode
133
+ }
77
134
 
78
- // Then highlight it.
79
- hljs.configure({cssSelector: "pre code.auto-hljs"})
80
- hljs.highlightAll()
135
+ if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
136
+ return "dark"
137
+ }
81
138
 
82
- // Replace text node links with anchor tag links.
83
- document.querySelectorAll(".rrf-copy code").forEach((el) => {
84
- el.innerHTML = rrfLinkify(el.innerHTML)
85
- })
139
+ return "light"
140
+ }
86
141
 
87
- // Insert copy links.
88
- document.querySelectorAll(".rrf-copy").forEach((el) => {
89
- el.insertAdjacentHTML(
90
- "afterbegin",
91
- '<a class="rrf-copy-link" title="Copy to Clipboard" href="#"><i class="bi bi-clipboard-fill"></i></a>',
92
- )
93
- })
142
+ // Set the mode, given a "selected" mode.
143
+ const rrfSetSelectedMode = (selectedMode) => {
144
+ // Anything except "light" or "dark" is casted to "system".
145
+ if (selectedMode !== "light" && selectedMode !== "dark") {
146
+ selectedMode = "system"
147
+ }
94
148
 
95
- // Copy link implementation.
96
- document.querySelectorAll(".rrf-copy-link").forEach((el) => {
97
- el.addEventListener("click", (event) => {
98
- const range = document.createRange()
99
- range.selectNode(el.nextSibling)
100
- window.getSelection().removeAllRanges()
101
- window.getSelection().addRange(range)
102
- if (document.execCommand("copy")) {
103
- // Trigger clicked animation.
104
- el.classList.add("rrf-clicked")
105
- el.innerHTML = '<i class="bi bi-clipboard-check-fill">'
106
- setTimeout(() => {
107
- el.classList.remove("rrf-clicked")
108
- el.innerHTML = '<i class="bi bi-clipboard-fill">'
109
- }, 1000)
149
+ // Store selected mode in `localStorage`.
150
+ localStorage.setItem("rrfMode", selectedMode)
151
+
152
+ // Set the mode selector to the selected mode.
153
+ const modeComponent = document.getElementById("rrfModeComponent")
154
+ if (modeComponent) {
155
+ let labelHTML
156
+ modeComponent.querySelectorAll("button[data-rrf-mode-value]").forEach((el) => {
157
+ if (el.getAttribute("data-rrf-mode-value") === selectedMode) {
158
+ el.classList.add("active")
159
+ labelHTML = el.querySelector("i").outerHTML.replace("ms-2", "me-1")
160
+ } else {
161
+ el.classList.remove("active")
162
+ }
163
+ })
164
+ modeComponent.querySelector("button[data-bs-toggle]").innerHTML = labelHTML
110
165
  }
111
- event.preventDefault()
112
- })
113
- })
114
166
 
115
- // Check if `rawFilesFormWrapper` should be displayed when media type is changed.
116
- const rawFormRouteSelect = document.getElementById("rawFormRoute")
117
- const rawFormMediaTypeSelect = document.getElementById("rawFormMediaType")
118
- const rawFilesFormWrapper = document.getElementById("rawFilesFormWrapper")
119
- if (rawFilesFormWrapper) {
120
- const rawFormFilesHandler = () => {
121
- const selectedRouteOption = rawFormRouteSelect.options[rawFormRouteSelect.selectedIndex]
122
- if (rawFormMediaTypeSelect.value === "multipart/form-data" && selectedRouteOption.dataset.supportsFiles) {
123
- rawFilesFormWrapper.style.display = "block"
167
+ // Get the real mode to use.
168
+ realMode = rrfGetRealMode(selectedMode)
169
+
170
+ // Set the `realMode` effects.
171
+ if (realMode === "light") {
172
+ document.querySelectorAll(".rrf-light-mode").forEach((el) => {
173
+ el.disabled = false
174
+ })
175
+ document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
176
+ el.disabled = true
177
+ })
178
+ document.querySelectorAll(".rrf-mode").forEach((el) => {
179
+ el.setAttribute("data-bs-theme", "light")
180
+ })
181
+ } else if (realMode === "dark") {
182
+ document.querySelectorAll(".rrf-light-mode").forEach((el) => {
183
+ el.disabled = true
184
+ })
185
+ document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
186
+ el.disabled = false
187
+ })
188
+ document.querySelectorAll(".rrf-mode").forEach((el) => {
189
+ el.setAttribute("data-bs-theme", "dark")
190
+ })
124
191
  } else {
125
- rawFilesFormWrapper.style.display = "none"
192
+ console.log(`RRF: Unknown mode: ${mode}`)
126
193
  }
127
194
  }
128
195
 
129
- rawFormRouteSelect.addEventListener("change", rawFormFilesHandler)
130
- rawFormMediaTypeSelect.addEventListener("change", rawFormFilesHandler)
131
- }
132
- })
133
-
134
- // Convert plain-text links to anchor tag links.
135
- function rrfLinkify(text) {
136
- return text.replace(/(https?:\/\/[^\s<>"]+)/g, "<a href=\"$1\" target=\"_blank\">$1</a>")
137
- }
138
-
139
- // Replace the document when doing form submission (mainly to support PUT/PATCH/DELETE).
140
- function rrfReplaceDocument(content) {
141
- // Replace the document with provided content.
142
- document.open()
143
- document.write(content)
144
- document.close()
145
-
146
- // Trigger `DOMContentLoaded` manually so our custom JavaScript works.
147
- document.dispatchEvent(new Event("DOMContentLoaded", {bubbles: true, cancelable: true}))
148
- }
149
-
150
- // Refresh the window as a `GET` request.
151
- function rrfGet(button) {
152
- button.disabled = true
153
- window.location.replace(window.location.href)
154
- }
155
-
156
- // Call `DELETE` on the current path.
157
- function rrfDelete(button) {
158
- button.disabled = true
159
- rrfAPICall(window.location.pathname, "DELETE")
160
- }
161
-
162
- // Call `OPTIONS` on the current path.
163
- function rrfOptions(button) {
164
- button.disabled = true
165
- rrfAPICall(window.location.pathname, "OPTIONS")
166
- }
167
-
168
- // Submit the raw form.
169
- function rrfSubmitRawForm(button) {
170
- button.disabled = true
171
-
172
- // Grab the selected route/method, media type, and the body.
173
- const [method, path] = document.getElementById("rawFormRoute").value.split(":")
174
- const mediaType = document.getElementById("rawFormMediaType").value
175
- let body = document.getElementById("rawFormContent").value
176
-
177
- // If the media type is `multipart/form-data`, then we need to build a FormData object.
178
- if (mediaType == "multipart/form-data") {
179
- let formData = new FormData()
180
-
181
- // Add body to `formData`.
182
- const bodySearchParams = new URLSearchParams(body)
183
- bodySearchParams.forEach((value, key) => {
184
- formData.append(key, value)
196
+ // Initialize dark/light mode before page fully loads to prevent flash.
197
+ rrfSetSelectedMode(localStorage.getItem("rrfMode"))
198
+
199
+ // Initialize dark/light mode after page load (mostly so mode component is updated).
200
+ document.addEventListener("DOMContentLoaded", (event) => {
201
+ rrfSetSelectedMode(localStorage.getItem("rrfMode"))
202
+
203
+ // Also set up mode selector.
204
+ document.querySelectorAll("#rrfModeComponent button[data-rrf-mode-value]").forEach((el) => {
205
+ el.addEventListener("click", (event) => {
206
+ rrfSetSelectedMode(event.target.getAttribute("data-rrf-mode-value"))
207
+ })
208
+ })
209
+ })
210
+
211
+ // Handle case where user changes system theme.
212
+ if (window.matchMedia) {
213
+ window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
214
+ const selectedMode = localStorage.getItem("rrfMode")
215
+ if (selectedMode !== "light" && selectedMode !== "dark") {
216
+ rrfSetSelectedMode("system")
217
+ }
218
+ })
219
+ }
220
+ })();
221
+
222
+ document.addEventListener("DOMContentLoaded", (event) => {
223
+ // Pretty-print JSON.
224
+ document.querySelectorAll(".language-json").forEach((el) => {
225
+ el.innerHTML = neatJSON(JSON.parse(el.innerText), {
226
+ wrap: 80,
227
+ afterComma: 1,
228
+ afterColon: 1,
229
+ }).replaceAll("&", "&amp;")
230
+ .replaceAll("<", "&lt;")
231
+ .replaceAll(">", "&gt;")
232
+ .replaceAll('"', "&quot;")
233
+ .replaceAll("'", "&#039;")
234
+ })
235
+
236
+ // Then highlight it.
237
+ hljs.configure({cssSelector: "pre code"})
238
+ hljs.highlightAll()
239
+
240
+ // Replace text node links with anchor tag links.
241
+ document.querySelectorAll(".rrf-copy code").forEach((el) => {
242
+ el.innerHTML = rrfLinkify(el.innerHTML)
185
243
  })
186
244
 
187
- // Add file(s) to `formData`.
188
- const rawFilesForm = document.getElementById("rawFilesForm")
189
- if (rawFilesForm) {
190
- rawFilesForm.querySelectorAll("input[type=file]").forEach((el) => {
191
- const files = el.files
192
- for (let i = 0; i < files.length; i++) {
193
- formData.append(el.name, files[i])
245
+ // Insert copy links.
246
+ document.querySelectorAll(".rrf-copy").forEach((el) => {
247
+ el.insertAdjacentHTML(
248
+ "afterbegin",
249
+ '<a class="rrf-copy-link" title="Copy to Clipboard" href="#"><i class="bi bi-clipboard-fill"></i></a>',
250
+ )
251
+ })
252
+
253
+ // Copy link implementation.
254
+ document.querySelectorAll(".rrf-copy-link").forEach((el) => {
255
+ el.addEventListener("click", (event) => {
256
+ const range = document.createRange()
257
+ range.selectNode(el.nextSibling)
258
+ window.getSelection().removeAllRanges()
259
+ window.getSelection().addRange(range)
260
+ if (document.execCommand("copy")) {
261
+ // Trigger clicked animation.
262
+ el.classList.add("rrf-clicked")
263
+ el.innerHTML = '<i class="bi bi-clipboard-check-fill">'
264
+ setTimeout(() => {
265
+ el.classList.remove("rrf-clicked")
266
+ el.innerHTML = '<i class="bi bi-clipboard-fill">'
267
+ }, 1000)
194
268
  }
269
+ event.preventDefault()
195
270
  })
271
+ })
272
+
273
+ // Check if `rawFilesFormWrapper` should be displayed when media type is changed.
274
+ const rawFormRouteSelect = document.getElementById("rawFormRoute")
275
+ const rawFormMediaTypeSelect = document.getElementById("rawFormMediaType")
276
+ const rawFilesFormWrapper = document.getElementById("rawFilesFormWrapper")
277
+ if (rawFilesFormWrapper) {
278
+ const rawFormFilesHandler = () => {
279
+ const selectedRouteOption = rawFormRouteSelect.options[rawFormRouteSelect.selectedIndex]
280
+ if (rawFormMediaTypeSelect.value === "multipart/form-data" && selectedRouteOption.dataset.supportsFiles) {
281
+ rawFilesFormWrapper.style.display = "block"
282
+ } else {
283
+ rawFilesFormWrapper.style.display = "none"
284
+ }
285
+ }
286
+
287
+ rawFormRouteSelect.addEventListener("change", rawFormFilesHandler)
288
+ rawFormMediaTypeSelect.addEventListener("change", rawFormFilesHandler)
196
289
  }
290
+ })
197
291
 
198
- // Set body to be the form data.
199
- body = formData
292
+ // Convert plain-text links to anchor tag links.
293
+ function rrfLinkify(text) {
294
+ return text.replace(/(https?:\/\/[^\s<>"]+)/g, "<a href=\"$1\" target=\"_blank\">$1</a>")
200
295
  }
201
296
 
202
- // Perform the API call.
203
- rrfAPICall(path, method, {
204
- body,
205
- // If the media type is `multipart/form-data`, then we don't want to set the content type
206
- // because it must be set by `fetch` to include boundary.
207
- headers: mediaType == "multipart/form-data" ? {} : {"Content-Type": mediaType},
208
- })
209
- }
297
+ // Replace the document when doing form submission (mainly to support PUT/PATCH/DELETE).
298
+ function rrfReplaceDocument(content) {
299
+ // Replace the document with provided content.
300
+ document.open()
301
+ document.write(content)
302
+ document.close()
210
303
 
211
- // Make an HTML API call and replace the document with the response.
212
- function rrfAPICall(path, method, kwargs={}) {
213
- const headers = kwargs.headers || {}
214
- delete kwargs.headers
304
+ // Trigger `DOMContentLoaded` manually so our custom JavaScript works.
305
+ document.dispatchEvent(new Event("DOMContentLoaded", {bubbles: true, cancelable: true}))
306
+ }
215
307
 
216
- fetch(path, {method, headers: {"Accept": "text/html", ...headers}, ...kwargs})
217
- .then((response) => response.text())
218
- .then((body) => { rrfReplaceDocument(body) })
219
- }
308
+ // Refresh the window as a `GET` request.
309
+ function rrfGet(button) {
310
+ button.disabled = true
311
+ window.location.replace(window.location.href)
312
+ }
313
+
314
+ // Call `DELETE` on the current path.
315
+ function rrfDelete(button) {
316
+ button.disabled = true
317
+ rrfAPICall(window.location.pathname, "DELETE")
318
+ }
319
+
320
+ // Call `OPTIONS` on the current path.
321
+ function rrfOptions(button) {
322
+ button.disabled = true
323
+ rrfAPICall(window.location.pathname, "OPTIONS")
324
+ }
325
+
326
+ // Submit the raw form.
327
+ function rrfSubmitRawForm(button) {
328
+ button.disabled = true
329
+
330
+ // Grab the selected route/method, media type, and the body.
331
+ const [method, path] = document.getElementById("rawFormRoute").value.split(":")
332
+ const mediaType = document.getElementById("rawFormMediaType").value
333
+ let body = document.getElementById("rawFormContent").value
334
+
335
+ // If the media type is `multipart/form-data`, then we need to build a FormData object.
336
+ if (mediaType == "multipart/form-data") {
337
+ let formData = new FormData()
338
+
339
+ // Add body to `formData`.
340
+ const bodySearchParams = new URLSearchParams(body)
341
+ bodySearchParams.forEach((value, key) => {
342
+ formData.append(key, value)
343
+ })
344
+
345
+ // Add file(s) to `formData`.
346
+ const rawFilesForm = document.getElementById("rawFilesForm")
347
+ if (rawFilesForm) {
348
+ rawFilesForm.querySelectorAll("input[type=file]").forEach((el) => {
349
+ const files = el.files
350
+ for (let i = 0; i < files.length; i++) {
351
+ formData.append(el.name, files[i])
352
+ }
353
+ })
354
+ }
355
+
356
+ // Set body to be the form data.
357
+ body = formData
358
+ }
359
+
360
+ // Perform the API call.
361
+ rrfAPICall(path, method, {
362
+ body,
363
+ // If the media type is `multipart/form-data`, then we don't want to set the content type
364
+ // because it must be set by `fetch` to include boundary.
365
+ headers: mediaType == "multipart/form-data" ? {} : {"Content-Type": mediaType},
366
+ })
367
+ }
368
+
369
+ // Make an HTML API call and replace the document with the response.
370
+ function rrfAPICall(path, method, kwargs={}) {
371
+ const headers = kwargs.headers || {}
372
+ delete kwargs.headers
373
+
374
+ fetch(path, {method, headers: {"Accept": "text/html", ...headers}, ...kwargs})
375
+ .then((response) => response.text())
376
+ .then((body) => { rrfReplaceDocument(body) })
377
+ }
220
378
  </script>
@@ -0,0 +1,15 @@
1
+ <div class="w-100 m-0 p-0" id="rrfAccentBar"></div>
2
+ <nav class="navbar navbar-expand-md py-0" data-bs-theme="dark">
3
+ <div class="container">
4
+ <span class="navbar-brand p-0">
5
+ <h1 title="RRF v<%= RESTFramework::VERSION %>" class="text-light font-weight-light m-0 p-0" style="font-size: 1em">
6
+ <%= @template_logo_text || @header_title || "Rails REST Framework" %>
7
+ </h1>
8
+ </span>
9
+ <% if content_for? :header_menu %>
10
+ <%= yield :header_menu %>
11
+ <% else %>
12
+ <%= render partial: "rest_framework/header/mode" %>
13
+ <% end %>
14
+ </div>
15
+ </nav>
@@ -0,0 +1,10 @@
1
+ <div class="row">
2
+ <div>
3
+ <%= render partial: "rest_framework/heading/actions" if @route_groups.present? %>
4
+ <h1 class="m-0"><%= @heading_title || @title %></h1>
5
+ <% if @description.present? %>
6
+ <br><br><p style="display: inline-block; margin-bottom: 0"><%= @description %></p>
7
+ <% end %>
8
+ </div>
9
+ </div>
10
+ <hr/>
@@ -0,0 +1,36 @@
1
+ <div class="row">
2
+ <div>
3
+ <ul class="nav nav-tabs">
4
+ <% if @json_payload.present? %>
5
+ <li class="nav-item">
6
+ <a class="nav-link active" href="#tab-json" data-bs-toggle="tab" role="tab">
7
+ .json
8
+ </a>
9
+ </li>
10
+ <% end %>
11
+ <% if @xml_payload.present? %>
12
+ <li class="nav-item">
13
+ <a class="nav-link" href="#tab-xml" data-bs-toggle="tab" role="tab">
14
+ .xml
15
+ </a>
16
+ </li>
17
+ <% end %>
18
+ </ul>
19
+ </div>
20
+ <div class="tab-content pt-2">
21
+ <div class="tab-pane fade show active" id="tab-json" role="tabpanel">
22
+ <% if @json_payload.present? %>
23
+ <div><pre class="rrf-copy"><code class="language-json"><%=
24
+ JSON.pretty_generate(JSON.parse(@json_payload)) unless @json_payload == ""
25
+ %></code></pre></div>
26
+ <% end %>
27
+ </div>
28
+ <div class="tab-pane fade" id="tab-xml" role="tabpanel">
29
+ <% if @xml_payload.present? %>
30
+ <div><pre class="rrf-copy"><code class="language-xml"><%=
31
+ CGI.unescapeHTML(@xml_payload)
32
+ %></code></pre></div>
33
+ <% end %>
34
+ </div>
35
+ </div>
36
+ </div>
@@ -0,0 +1,16 @@
1
+ <div class="row">
2
+ <div>
3
+ <pre><code><%
4
+ concat request.request_method
5
+ if request.method != request.request_method
6
+ concat " (via #{request.method})"
7
+ end
8
+ concat " #{request.path}"
9
+ %></code></pre>
10
+ <pre><code><%
11
+ concat "HTTP #{response.status} #{response.message}"
12
+ concat "\n"
13
+ concat "Content-Type: #{response.content_type}"
14
+ %></code></pre>
15
+ </div>
16
+ </div>