rest_framework 0.8.15 → 0.8.17
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/app/views/layouts/rest_framework.html.erb +149 -128
- data/app/views/rest_framework/_head.html.erb +249 -28
- data/app/views/rest_framework/_html_form.html.erb +55 -3
- data/app/views/rest_framework/_raw_form.html.erb +31 -3
- data/docs/CNAME +1 -0
- data/docs/Gemfile +4 -0
- data/docs/Gemfile.lock +264 -0
- data/docs/_config.yml +17 -0
- data/docs/_guide/1_routers.md +110 -0
- data/docs/_guide/2_controller_mixins.md +293 -0
- data/docs/_guide/3_serializers.md +60 -0
- data/docs/_guide/4_filtering_and_ordering.md +41 -0
- data/docs/_guide/5_pagination.md +21 -0
- data/docs/_includes/anchor_headings.html +144 -0
- data/docs/_includes/head.html +35 -0
- data/docs/_includes/header.html +58 -0
- data/docs/_layouts/default.html +11 -0
- data/docs/assets/css/rest_framework.css +159 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/js/rest_framework.js +132 -0
- data/docs/index.md +133 -0
- data/lib/rest_framework/controller_mixins/base.rb +10 -3
- data/lib/rest_framework/controller_mixins/models.rb +120 -44
- data/lib/rest_framework/filters.rb +7 -9
- data/lib/rest_framework/serializers.rb +35 -12
- data/lib/rest_framework/utils.rb +11 -2
- data/lib/rest_framework/version.rb +4 -1
- data/lib/rest_framework.rb +2 -8
- metadata +22 -6
- data/app/views/rest_framework/_form_routes.html.erb +0 -10
@@ -4,11 +4,13 @@
|
|
4
4
|
<%= csp_meta_tag rescue nil %>
|
5
5
|
|
6
6
|
<!-- Bootstrap -->
|
7
|
-
<link
|
8
|
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-aFq/bzH65dt+w6FI2ooMVUpc+21e0SRygnTpmBvdBgSdnuTN7QbdgL+OapgHtvPp" crossorigin="anonymous">
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha2/dist/js/bootstrap.bundle.min.js" integrity="sha384-qKXV1j0HvMUeCBQ+QVp7JcfGl760yU08IQ+GpUo5hlbpg51QRiuqHAJz8+BrxE/N" crossorigin="anonymous"></script>
|
9
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
|
9
10
|
|
10
11
|
<!-- Highlight.js -->
|
11
|
-
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/
|
12
|
+
<link rel="stylesheet" class="rrf-light-mode" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-light.min.css" integrity="sha512-WDk6RzwygsN9KecRHAfm9HTN87LQjqdygDmkHSJxVkVI7ErCZ8ZWxP6T8RvBujY1n2/E4Ac+bn2ChXnp5rnnHA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
13
|
+
<link rel="stylesheet" class="rrf-dark-mode" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/a11y-dark.min.css" integrity="sha512-Vj6gPCk8EZlqnoveEyuGyYaWZ1+jyjMPg8g4shwyyNlRQl6d3L9At02ZHQr5K6s5duZl/+YKMnM3/8pDhoUphg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
12
14
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js" integrity="sha512-bgHRAiTjGrzHzLyKOnpFvaEpGzJet3z4tZnXGjpsCcqOnAH6VGUx9frc5bcIhKTVLEiCO6vEhNAgx5jtLUYrfA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
13
15
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/json.min.js" integrity="sha512-0xYvyncS9OLE7GOpNBZFnwyh9+bq4HVgk4yVVYI678xRvE22ASicF1v6fZ1UiST+M6pn17MzFZdvVCI3jTHSyw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
14
16
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/xml.min.js" integrity="sha512-5zBcw+OKRkaNyvUEPlTSfYylVzgpi7KpncY36b0gRudfxIYIH0q0kl2j26uCUB3YBRM6ytQQEZSgRg+ZlBTmdA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
@@ -16,31 +18,105 @@
|
|
16
18
|
<!-- NeatJSON -->
|
17
19
|
<script src="https://cdn.jsdelivr.net/npm/neatjson@0.10.5/javascript/neatjson.min.js"></script>
|
18
20
|
|
21
|
+
<!-- Trix -->
|
22
|
+
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.0/dist/trix.css">
|
23
|
+
<script type="text/javascript" src="https://unpkg.com/trix@2.0.0/dist/trix.umd.min.js"></script>
|
24
|
+
|
19
25
|
<!-- Custom Style -->
|
20
26
|
<style>
|
21
|
-
|
22
|
-
|
27
|
+
/********************************
|
28
|
+
* START OF LIB/DOCS COMMON CSS *
|
29
|
+
********************************/
|
30
|
+
|
31
|
+
:root {
|
32
|
+
--rrf-red: #900;
|
33
|
+
--rrf-red-hover: #5f0c0c;
|
34
|
+
--rrf-light-red: #db2525;
|
35
|
+
--rrf-light-red-hover: #b80404;
|
36
|
+
}
|
37
|
+
#rrfAccentBar {
|
38
|
+
background-color: var(--rrf-red);
|
39
|
+
height: .3em;
|
40
|
+
}
|
41
|
+
header nav { background-color: black; }
|
42
|
+
|
43
|
+
/* Header adjustments. */
|
23
44
|
h1 { font-size: 2rem; }
|
24
45
|
h2 { font-size: 1.7rem; }
|
25
46
|
h3 { font-size: 1.5rem; }
|
26
47
|
h4 { font-size: 1.3rem; }
|
27
48
|
h5 { font-size: 1.1rem; }
|
28
49
|
h6 { font-size: 1rem; }
|
50
|
+
h1, h2, h3, h4, h5, h6 {
|
51
|
+
color: var(--rrf-red);
|
52
|
+
}
|
53
|
+
html[data-bs-theme="dark"] h1,
|
54
|
+
html[data-bs-theme="dark"] h2,
|
55
|
+
html[data-bs-theme="dark"] h3,
|
56
|
+
html[data-bs-theme="dark"] h4,
|
57
|
+
html[data-bs-theme="dark"] h5,
|
58
|
+
html[data-bs-theme="dark"] h6 {
|
59
|
+
color: var(--rrf-light-red);
|
60
|
+
}
|
29
61
|
|
30
|
-
/*
|
31
|
-
code {
|
62
|
+
/* Improve code and code blocks. */
|
63
|
+
pre code {
|
64
|
+
display: block;
|
65
|
+
overflow-x: auto;
|
66
|
+
}
|
67
|
+
code, .trix-content pre {
|
32
68
|
padding: .5em !important;
|
33
|
-
background-color: #
|
69
|
+
background-color: #eee !important;
|
34
70
|
border: 1px solid #aaa;
|
35
71
|
border-radius: 3px;
|
36
72
|
}
|
73
|
+
p code {
|
74
|
+
padding: .1em .3em !important;
|
75
|
+
}
|
76
|
+
html[data-bs-theme="dark"] code, html[data-bs-theme="dark"] .trix-content pre {
|
77
|
+
background-color: #2b2b2b !important;
|
78
|
+
}
|
37
79
|
|
38
|
-
/*
|
39
|
-
.
|
40
|
-
|
80
|
+
/* Anchors */
|
81
|
+
a:not(.nav-link) {
|
82
|
+
text-decoration: none;
|
83
|
+
color: var(--rrf-red);
|
84
|
+
}
|
85
|
+
a:hover:not(.nav-link) {
|
86
|
+
text-decoration: underline;
|
87
|
+
color: var(--rrf-red-hover);
|
88
|
+
}
|
89
|
+
html[data-bs-theme="dark"] a:not(.nav-link) { color: var(--rrf-light-red); }
|
90
|
+
html[data-bs-theme="dark"] a:hover:not(.nav-link) { color: var(--rrf-light-red-hover); }
|
91
|
+
|
92
|
+
/******************************
|
93
|
+
* END OF LIB/DOCS COMMON CSS *
|
94
|
+
******************************/
|
95
|
+
|
96
|
+
/* Header adjustments. */
|
97
|
+
h1,h2,h3,h4,h5,h6 { display: inline-block; font-weight: normal; margin-bottom: 0; }
|
98
|
+
|
99
|
+
/* Reduce label font size. */
|
100
|
+
label.form-label {
|
101
|
+
font-size: .8em;
|
102
|
+
}
|
103
|
+
|
104
|
+
/* Make Trix buttons visible even in dark mode. */
|
105
|
+
trix-toolbar .trix-button-group {
|
106
|
+
background-color: #eee;
|
107
|
+
}
|
108
|
+
|
109
|
+
/* Make Trix dialog URL input visible in dark mode. */
|
110
|
+
input.trix-input--dialog {
|
111
|
+
color: black;
|
41
112
|
}
|
113
|
+
|
114
|
+
/* Make route group expansion obvious to the user. */
|
42
115
|
.rrf-routes .rrf-route-group-header:hover {
|
43
|
-
background-color: #
|
116
|
+
background-color: #ddd;
|
117
|
+
}
|
118
|
+
html[data-bs-theme="dark"] .rrf-routes .rrf-route-group-header:hover {
|
119
|
+
background-color: #333;
|
44
120
|
}
|
45
121
|
.rrf-routes .rrf-route-group-header td {
|
46
122
|
cursor: pointer;
|
@@ -70,32 +146,134 @@ code {
|
|
70
146
|
|
71
147
|
<!-- Custom JavaScript -->
|
72
148
|
<script>
|
73
|
-
|
149
|
+
/*******************************
|
150
|
+
* START OF LIB/DOCS COMMON JS *
|
151
|
+
*******************************/
|
152
|
+
|
153
|
+
;(() => {
|
154
|
+
// Get the real mode from a selected mode. Anything other than "light" or "dark" is treated as
|
155
|
+
// "system" mode.
|
156
|
+
const rrfGetRealMode = (selectedMode) => {
|
157
|
+
if (selectedMode === "light" || selectedMode === "dark") {
|
158
|
+
return selectedMode
|
159
|
+
}
|
160
|
+
|
161
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
162
|
+
return "dark"
|
163
|
+
}
|
164
|
+
|
165
|
+
return "light"
|
166
|
+
}
|
167
|
+
|
168
|
+
// Set the mode, given a "selected" mode.
|
169
|
+
const rrfSetSelectedMode = (selectedMode) => {
|
170
|
+
const modeComponent = document.getElementById("rrfModeComponent")
|
171
|
+
|
172
|
+
// Anything except "light" or "dark" is casted to "system".
|
173
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
174
|
+
selectedMode = "system"
|
175
|
+
}
|
176
|
+
|
177
|
+
// Store selected mode in `localStorage`.
|
178
|
+
localStorage.setItem("rrfMode", selectedMode)
|
179
|
+
|
180
|
+
// Set the mode selector to the selected mode.
|
181
|
+
let labelHTML
|
182
|
+
modeComponent.querySelectorAll("button[data-rrf-mode-value]").forEach((el) => {
|
183
|
+
if (el.getAttribute("data-rrf-mode-value") === selectedMode) {
|
184
|
+
el.classList.add("active")
|
185
|
+
labelHTML = el.querySelector("i").outerHTML.replace("ms-2", "me-1")
|
186
|
+
} else {
|
187
|
+
el.classList.remove("active")
|
188
|
+
}
|
189
|
+
})
|
190
|
+
modeComponent.querySelector("button[data-bs-toggle]").innerHTML = labelHTML
|
191
|
+
|
192
|
+
// Get the real mode to use.
|
193
|
+
realMode = rrfGetRealMode(selectedMode)
|
194
|
+
|
195
|
+
// Set the `realMode` effects.
|
196
|
+
if (realMode === "light") {
|
197
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
198
|
+
el.disabled = false
|
199
|
+
})
|
200
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
201
|
+
el.disabled = true
|
202
|
+
})
|
203
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
204
|
+
el.setAttribute("data-bs-theme", "light")
|
205
|
+
})
|
206
|
+
} else if (realMode === "dark") {
|
207
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
208
|
+
el.disabled = true
|
209
|
+
})
|
210
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
211
|
+
el.disabled = false
|
212
|
+
})
|
213
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
214
|
+
el.setAttribute("data-bs-theme", "dark")
|
215
|
+
})
|
216
|
+
} else {
|
217
|
+
console.log(`RRF: Unknown mode: ${mode}`)
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
// Initialize dark/light mode.
|
222
|
+
document.addEventListener("DOMContentLoaded", (event) => {
|
223
|
+
const selectedMode = localStorage.getItem("rrfMode")
|
224
|
+
rrfSetSelectedMode(selectedMode)
|
225
|
+
document.querySelectorAll("#rrfModeComponent button[data-rrf-mode-value]").forEach((el) => {
|
226
|
+
el.addEventListener("click", (event) => {
|
227
|
+
rrfSetSelectedMode(event.target.getAttribute("data-rrf-mode-value"))
|
228
|
+
})
|
229
|
+
})
|
230
|
+
})
|
231
|
+
|
232
|
+
// Handle case where user changes system theme.
|
233
|
+
if (window.matchMedia) {
|
234
|
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
235
|
+
const selectedMode = localStorage.getItem("rrfMode")
|
236
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
237
|
+
rrfSetSelectedMode("system")
|
238
|
+
}
|
239
|
+
})
|
240
|
+
}
|
241
|
+
})()
|
242
|
+
|
243
|
+
/*****************************
|
244
|
+
* END OF LIB/DOCS COMMON JS *
|
245
|
+
*****************************/
|
246
|
+
|
74
247
|
document.addEventListener("DOMContentLoaded", (event) => {
|
75
248
|
// Pretty-print JSON.
|
76
|
-
|
249
|
+
document.querySelectorAll(".language-json").forEach((el, index) => {
|
77
250
|
el.innerHTML = neatJSON(JSON.parse(el.innerText), {
|
78
251
|
wrap: 80,
|
79
252
|
afterComma: 1,
|
80
253
|
afterColon: 1,
|
81
|
-
})
|
82
|
-
|
254
|
+
}).replaceAll("&", "&")
|
255
|
+
.replaceAll("<", "<")
|
256
|
+
.replaceAll(">", ">")
|
257
|
+
.replaceAll('"', """)
|
258
|
+
.replaceAll("'", "'")
|
259
|
+
})
|
83
260
|
|
84
261
|
// Then highlight it.
|
85
|
-
hljs.
|
262
|
+
hljs.configure({cssSelector: "pre code.auto-hljs"})
|
263
|
+
hljs.highlightAll()
|
86
264
|
|
87
|
-
// Replace
|
88
|
-
|
265
|
+
// Replace text node links with anchor tag links.
|
266
|
+
document.querySelectorAll(".rrf-copy code").forEach((el, index) => {
|
89
267
|
el.innerHTML = rrfLinkify(el.innerHTML)
|
90
|
-
})
|
268
|
+
})
|
91
269
|
|
92
270
|
// Insert copy link and callback to copy contents of `<code>` element.
|
93
|
-
|
271
|
+
document.querySelectorAll("rrf-copy").forEach((el, index) => {
|
94
272
|
el.insertAdjacentHTML(
|
95
273
|
"afterbegin",
|
96
274
|
"<a class=\"rrf-copy-link\" onclick=\"return rrfCopyToClipboard(this)\" href=\"#\">Copy to Clipboard</a>",
|
97
275
|
)
|
98
|
-
})
|
276
|
+
})
|
99
277
|
})
|
100
278
|
|
101
279
|
// Convert plain-text links to anchor tag links.
|
@@ -134,10 +312,10 @@ function rrfCopyToClipboard(element) {
|
|
134
312
|
return false
|
135
313
|
}
|
136
314
|
|
137
|
-
// Refresh the window.
|
138
|
-
function
|
315
|
+
// Refresh the window as a `GET` request.
|
316
|
+
function rrfGet(button) {
|
139
317
|
button.disabled = true
|
140
|
-
window.location.
|
318
|
+
window.location.replace(window.location.href)
|
141
319
|
}
|
142
320
|
|
143
321
|
// Call `DELETE` on the current path.
|
@@ -158,11 +336,41 @@ function rrfSubmitRawForm(button) {
|
|
158
336
|
|
159
337
|
// Grab the selected route/method, media type, and the body.
|
160
338
|
const [method, path] = document.getElementById("rawFormRoute").value.split(":")
|
161
|
-
const
|
162
|
-
|
339
|
+
const mediaType = document.getElementById("rawFormMediaType").value
|
340
|
+
let body = document.getElementById("rawFormContent").value
|
341
|
+
|
342
|
+
// If the media type is `multipart/form-data`, then we need to build a FormData object.
|
343
|
+
if (mediaType == "multipart/form-data") {
|
344
|
+
let formData = new FormData()
|
345
|
+
|
346
|
+
// Add body to `formData`.
|
347
|
+
const bodySearchParams = new URLSearchParams(body)
|
348
|
+
bodySearchParams.forEach((value, key) => {
|
349
|
+
formData.append(key, value)
|
350
|
+
})
|
351
|
+
|
352
|
+
// Add file(s) to `formData`.
|
353
|
+
const rawFilesForm = document.getElementById("rawFilesForm")
|
354
|
+
if (rawFilesForm) {
|
355
|
+
rawFilesForm.querySelectorAll("input[type=file]").forEach((el, index) => {
|
356
|
+
const files = el.files
|
357
|
+
for (let i = 0; i < files.length; i++) {
|
358
|
+
formData.append(el.name, files[i])
|
359
|
+
}
|
360
|
+
})
|
361
|
+
}
|
362
|
+
|
363
|
+
// Set body to be the form data.
|
364
|
+
body = formData
|
365
|
+
}
|
163
366
|
|
164
367
|
// Perform the API call.
|
165
|
-
rrfAPICall(path, method, {
|
368
|
+
rrfAPICall(path, method, {
|
369
|
+
body,
|
370
|
+
// If the media type is `multipart/form-data`, then we don't want to set the content type
|
371
|
+
// because it must be set by `fetch` to include boundary.
|
372
|
+
headers: mediaType == "multipart/form-data" ? {} : {"Content-Type": mediaType},
|
373
|
+
})
|
166
374
|
}
|
167
375
|
|
168
376
|
// Make an HTML API call and replace the document with the response.
|
@@ -174,4 +382,17 @@ function rrfAPICall(path, method, kwargs={}) {
|
|
174
382
|
.then((response) => response.text())
|
175
383
|
.then((body) => { rrfReplaceDocument(body) })
|
176
384
|
}
|
385
|
+
|
386
|
+
// Check if `rawFilesFormWrapper` should be displayed when media type is changed.
|
387
|
+
function rrfCheckRawFilesFormDisplay(el) {
|
388
|
+
const rawFilesFormWrapper = document.getElementById("rawFilesFormWrapper")
|
389
|
+
|
390
|
+
if (rawFilesFormWrapper) {
|
391
|
+
if (el.value === "multipart/form-data") {
|
392
|
+
rawFilesFormWrapper.style.display = "block"
|
393
|
+
} else {
|
394
|
+
rawFilesFormWrapper.style.display = "none"
|
395
|
+
}
|
396
|
+
}
|
397
|
+
}
|
177
398
|
</script>
|
@@ -1,7 +1,59 @@
|
|
1
1
|
<div style="max-width: 60em; margin: auto">
|
2
|
-
|
2
|
+
<div class="mb-2">
|
3
|
+
<label class="form-label w-100">Route
|
4
|
+
<select class="form-control form-control-sm" id="htmlFormRoute">
|
5
|
+
<% @_rrf_form_routes.each do |route| %>
|
6
|
+
<% path = @route_props[:with_path_args].call(route[:route]) %>
|
7
|
+
<option value="<%= route[:verb] %>:<%= path %>"><%= route[:verb] %> <%= route[:relative_path] %></option>
|
8
|
+
<% end %>
|
9
|
+
</select>
|
10
|
+
</label>
|
11
|
+
</div>
|
3
12
|
|
4
|
-
<%=
|
5
|
-
|
13
|
+
<%= form_with(
|
14
|
+
model: @record,
|
15
|
+
url: "",
|
16
|
+
method: :patch,
|
17
|
+
id: "htmlForm",
|
18
|
+
scope: "",
|
19
|
+
) do |form| %>
|
20
|
+
<% controller.get_fields.map(&:to_s).each do |f| %>
|
21
|
+
<%
|
22
|
+
# Don't provide form fields for associations or primary keys.
|
23
|
+
metadata = controller.class.fields_metadata[f]
|
24
|
+
next if !metadata || metadata[:kind] == "association" || metadata[:read_only]
|
25
|
+
%>
|
26
|
+
<div class="mb-2">
|
27
|
+
<% if metadata[:kind] == "rich_text" %>
|
28
|
+
<label class="form-label w-100"><%= controller.class.get_label(f) %></label>
|
29
|
+
<%= form.rich_text_area f %>
|
30
|
+
<% elsif metadata[:kind] == "attachment" %>
|
31
|
+
<label class="form-label w-100"><%= controller.class.get_label(f) %>
|
32
|
+
<%= form.file_field f, multiple: metadata.dig(:attachment, :macro) == :has_many_attached %>
|
33
|
+
</label>
|
34
|
+
<% else %>
|
35
|
+
<label class="form-label w-100"><%= controller.class.get_label(f) %>
|
36
|
+
<%= form.text_field f, class: "form-control form-control-sm" %>
|
37
|
+
</label>
|
38
|
+
<% end %>
|
39
|
+
</div>
|
40
|
+
<% end %>
|
41
|
+
|
42
|
+
<%= form.submit "Submit", name: "", class: "btn btn-primary", style: "float: right" %>
|
6
43
|
<% end %>
|
44
|
+
|
45
|
+
<script>
|
46
|
+
// Update form anytime the route changes.
|
47
|
+
document.getElementById("htmlFormRoute").addEventListener("change", (event) => {
|
48
|
+
const [verb, path] = event.target.value.split(":")
|
49
|
+
const form = document.getElementById("htmlForm")
|
50
|
+
form.action = path
|
51
|
+
form.querySelector("input[name='_method']").value = verb
|
52
|
+
})
|
53
|
+
|
54
|
+
document.addEventListener("DOMContentLoaded", (event) => {
|
55
|
+
// Trigger the change event to update the form initially.
|
56
|
+
document.getElementById("htmlFormRoute").dispatchEvent(new Event("change"))
|
57
|
+
})
|
58
|
+
</script>
|
7
59
|
</div>
|
@@ -1,9 +1,18 @@
|
|
1
1
|
<div style="max-width: 60em; margin: auto">
|
2
|
-
|
2
|
+
<div class="mb-2">
|
3
|
+
<label class="form-label w-100">Route
|
4
|
+
<select class="form-control form-control-sm" id="rawFormRoute">
|
5
|
+
<% @_rrf_form_routes.each do |route| %>
|
6
|
+
<% path = @route_props[:with_path_args].call(route[:route]) %>
|
7
|
+
<option value="<%= route[:verb] %>:<%= path %>"><%= route[:verb] %> <%= route[:relative_path] %></option>
|
8
|
+
<% end %>
|
9
|
+
</select>
|
10
|
+
</label>
|
11
|
+
</div>
|
3
12
|
|
4
13
|
<div class="mb-2">
|
5
14
|
<label class="form-label w-100">Media Type
|
6
|
-
<select class="form-control" id="rawFormMediaType">
|
15
|
+
<select class="form-control form-control-sm" id="rawFormMediaType" onchange="rrfCheckRawFilesFormDisplay(this)">
|
7
16
|
<% ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"].each do |t| %>
|
8
17
|
<option value="<%= t %>"><%= t %></option>
|
9
18
|
<% end %>
|
@@ -13,9 +22,28 @@
|
|
13
22
|
|
14
23
|
<div class="mb-2">
|
15
24
|
<label class="form-label w-100">Content
|
16
|
-
<textarea class="form-control" style="font-family: monospace" id="rawFormContent" rows="8" cols="60"></textarea>
|
25
|
+
<textarea class="form-control form-control-sm" style="font-family: monospace" id="rawFormContent" rows="8" cols="60"></textarea>
|
17
26
|
</label>
|
18
27
|
</div>
|
19
28
|
|
29
|
+
<% if @is_model_controller && model = controller.class.get_model %>
|
30
|
+
<% if attachment_reflections = model.attachment_reflections.presence %>
|
31
|
+
<div class="mb-2" style="display: none" id="rawFilesFormWrapper">
|
32
|
+
<%= form_with(
|
33
|
+
model: @record,
|
34
|
+
url: "",
|
35
|
+
id: "rawFilesForm",
|
36
|
+
scope: "",
|
37
|
+
) do |form| %>
|
38
|
+
<% attachment_reflections.each do |field, ref| %>
|
39
|
+
<label class="form-label w-100"><%= controller.class.get_label(field) %>
|
40
|
+
<%= form.file_field field, multiple: ref.macro == :has_many_attached %>
|
41
|
+
</label>
|
42
|
+
<% end %>
|
43
|
+
<% end %>
|
44
|
+
</div>
|
45
|
+
<% end %>
|
46
|
+
<% end %>
|
47
|
+
|
20
48
|
<button type="button" class="btn btn-primary" style="float: right" onclick="rrfSubmitRawForm(this)">Submit</button>
|
21
49
|
</div>
|
data/docs/CNAME
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rails-rest-framework.com
|
data/docs/Gemfile
ADDED