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.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +130 -0
- data/VERSION +1 -1
- data/app/views/layouts/rest_framework.html.erb +9 -183
- data/app/views/rest_framework/_breadcrumbs.html.erb +27 -0
- data/app/views/rest_framework/_head.html.erb +348 -190
- data/app/views/rest_framework/_header.html.erb +15 -0
- data/app/views/rest_framework/_heading.html.erb +10 -0
- data/app/views/rest_framework/_payloads.html.erb +36 -0
- data/app/views/rest_framework/_request_metadata.html.erb +16 -0
- data/app/views/rest_framework/_routes_and_forms.html.erb +52 -0
- data/app/views/rest_framework/head/_external.html.erb +7 -2
- data/app/views/rest_framework/header/_mode.html.erb +14 -0
- data/app/views/rest_framework/heading/_actions.html.erb +9 -0
- data/app/views/rest_framework/{_routes.html.erb → routes_and_forms/_routes.html.erb} +2 -2
- data/lib/rest_framework/controller_mixins/base.rb +11 -12
- data/lib/rest_framework/engine.rb +5 -3
- data/lib/rest_framework/filters/ransack.rb +6 -6
- data/lib/rest_framework/version.rb +0 -6
- data/lib/rest_framework.rb +25 -13
- data/vendor/assets/javascripts/rest_framework/external.min.js +1256 -0
- data/vendor/assets/stylesheets/rest_framework/{bootstrap-icons.css → external.min.css} +415 -1
- data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-dark.css → highlight-a11y-dark.min.css} +1 -1
- data/vendor/assets/stylesheets/rest_framework/{highlight-a11y-light.css → highlight-a11y-light.min.css} +1 -1
- metadata +18 -35
- data/README.md +0 -1
- data/app/views/rest_framework/head/_shared.html +0 -164
- data/docs/CNAME +0 -1
- data/docs/Gemfile +0 -5
- data/docs/Gemfile.lock +0 -264
- data/docs/_config.yml +0 -19
- data/docs/_guide/1_routers.md +0 -110
- data/docs/_guide/2_controllers.md +0 -342
- data/docs/_guide/3_serializers.md +0 -60
- data/docs/_guide/4_filtering_and_ordering.md +0 -41
- data/docs/_guide/5_pagination.md +0 -21
- data/docs/_includes/anchor_headings.html +0 -144
- data/docs/_includes/external.html +0 -9
- data/docs/_includes/head.html +0 -155
- data/docs/_includes/header.html +0 -58
- data/docs/_includes/shared.html +0 -164
- data/docs/_layouts/default.html +0 -11
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/index.md +0 -133
- data/vendor/assets/javascripts/rest_framework/bootstrap.js +0 -7
- data/vendor/assets/javascripts/rest_framework/highlight-json.js +0 -7
- data/vendor/assets/javascripts/rest_framework/highlight-xml.js +0 -29
- data/vendor/assets/javascripts/rest_framework/highlight.js +0 -1202
- data/vendor/assets/javascripts/rest_framework/neatjson.js +0 -8
- data/vendor/assets/javascripts/rest_framework/trix.js +0 -6
- data/vendor/assets/stylesheets/rest_framework/bootstrap.css +0 -6
- data/vendor/assets/stylesheets/rest_framework/trix.css +0 -410
- /data/app/views/rest_framework/{_html_form.html.erb → routes_and_forms/_html_form.html.erb} +0 -0
- /data/app/views/rest_framework/{_raw_form.html.erb → routes_and_forms/_raw_form.html.erb} +0 -0
- /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
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
}
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
}
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
-
|
40
|
-
|
41
|
-
|
42
|
-
}
|
43
|
-
|
44
|
-
/*
|
45
|
-
.
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
.
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
.replaceAll("<", "<")
|
73
|
-
.replaceAll(">", ">")
|
74
|
-
.replaceAll('"', """)
|
75
|
-
.replaceAll("'", "'")
|
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
|
-
|
79
|
-
|
80
|
-
|
135
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
136
|
+
return "dark"
|
137
|
+
}
|
81
138
|
|
82
|
-
|
83
|
-
|
84
|
-
el.innerHTML = rrfLinkify(el.innerHTML)
|
85
|
-
})
|
139
|
+
return "light"
|
140
|
+
}
|
86
141
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
"
|
91
|
-
|
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
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
}
|
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
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
192
|
+
console.log(`RRF: Unknown mode: ${mode}`)
|
126
193
|
}
|
127
194
|
}
|
128
195
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
//
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
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("&", "&")
|
230
|
+
.replaceAll("<", "<")
|
231
|
+
.replaceAll(">", ">")
|
232
|
+
.replaceAll('"', """)
|
233
|
+
.replaceAll("'", "'")
|
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
|
-
//
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
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
|
-
|
199
|
-
|
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
|
-
//
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
//
|
212
|
-
|
213
|
-
|
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
|
-
|
217
|
-
|
218
|
-
.
|
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>
|