rest_framework 0.9.4 → 0.9.6

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.
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>