rest_framework 0.8.16 → 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 -132
- data/app/views/rest_framework/_head.html.erb +166 -15
- data/app/views/rest_framework/_html_form.html.erb +1 -1
- 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/serializers.rb +1 -1
- metadata +19 -2
@@ -0,0 +1,60 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Serializers
|
4
|
+
position: 3
|
5
|
+
slug: serializers
|
6
|
+
---
|
7
|
+
# Serializers
|
8
|
+
|
9
|
+
Serializers allow complex objects to be converted to Ruby primitives (`Array` and `Hash` objects),
|
10
|
+
which can then be converted to JSON or XML.
|
11
|
+
|
12
|
+
## NativeSerializer
|
13
|
+
|
14
|
+
This serializer uses Rails' native `ActiveModel::Serializers.serializable_hash` method to convert
|
15
|
+
records/recordsets to Ruby primitives (`Array` and `Hash`).
|
16
|
+
|
17
|
+
This is the default serializer, you can configure it using the controller class attributes
|
18
|
+
`native_serializer_config` (or `native_serializer_singular_config` /
|
19
|
+
`native_serializer_plural_config`):
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
class Api::MoviesController < ApiController
|
23
|
+
include RESTFramework::ModelControllerMixin
|
24
|
+
|
25
|
+
self.native_serializer_config = {
|
26
|
+
only: [:id, :name],
|
27
|
+
methods: [:active, :some_expensive_computed_property],
|
28
|
+
include: {cast_members: { only: [:id, :name] }},
|
29
|
+
}
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
If you want to re-use a serializer, then you can define it as a standalone class, and you can even
|
34
|
+
nest them. You can also define separate configurations for serializing individual records vs
|
35
|
+
recordsets using `singular_config` and `plural_config`, respectively.
|
36
|
+
|
37
|
+
```ruby
|
38
|
+
class Api::MoviesController < ApiController
|
39
|
+
include RESTFramework::ModelControllerMixin
|
40
|
+
|
41
|
+
class CastMemberSerializer < RESTFramework::NativeSerializer
|
42
|
+
self.config = { only: [:id, :name], methods: [:net_worth] }
|
43
|
+
self.plural_config = { only: [:id, :name] }
|
44
|
+
end
|
45
|
+
|
46
|
+
class MovieSerializer < RESTFramework::NativeSerializer
|
47
|
+
self.config = {
|
48
|
+
only: [:id, :name],
|
49
|
+
include: {cast_members: CastMemberSerializer.new(many: true)},
|
50
|
+
}
|
51
|
+
self.singular_config = {
|
52
|
+
only: [:id, :name],
|
53
|
+
methods: [:active, :some_expensive_computed_property],
|
54
|
+
include: {cast_members: CastMemberSerializer.new(many: true)},
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
self.serializer_class = MovieSerializer
|
59
|
+
end
|
60
|
+
```
|
@@ -0,0 +1,41 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Filtering / Ordering
|
4
|
+
position: 4
|
5
|
+
slug: filtering-and-ordering
|
6
|
+
---
|
7
|
+
# Filtering and Ordering
|
8
|
+
|
9
|
+
While you can control the recordset that the API exposes, sometimes you want the user to control the
|
10
|
+
records they want to see, or the order of those records. Both filtering and ordering are
|
11
|
+
accomplished through what we call filters. To control the filter backends that a controller uses,
|
12
|
+
you can either adjust the `filter_backends` controller attribute or you can override the
|
13
|
+
`get_filter_backends()` method.
|
14
|
+
|
15
|
+
## `ModelFilter`
|
16
|
+
|
17
|
+
This filter provides basic user-controllable filtering of the recordset using query params. For
|
18
|
+
example, a request to `/api/movies?cool=true` could return movies where `cool` is `true`.
|
19
|
+
|
20
|
+
If you include `ModelControllerMixin` into your controller, `ModelFilter` is included in the filter
|
21
|
+
backends by default.
|
22
|
+
|
23
|
+
## `ModelOrderingFilter`
|
24
|
+
|
25
|
+
This filter provides basic user-controllable ordering of the recordset using query params. For
|
26
|
+
example, a request to `/api/movies?ordering=name` could order the movies by `name` rather than `id`.
|
27
|
+
`ordering=-name` would invert the ordering. You can also order with multiple parameters with a comma
|
28
|
+
separated list, like: `ordering=director,-name`.
|
29
|
+
|
30
|
+
If you include `ModelControllerMixin` into your controller, `ModelOrderingFilter` is included in the
|
31
|
+
filter backends by default. You can use `ordering_fields` to controller which fields are allowed to
|
32
|
+
be ordered by. To adjust the parameter that the user passes, adjust `ordering_query_param`; the
|
33
|
+
default is `"ordering"`.
|
34
|
+
|
35
|
+
## `ModelSearchFilter`
|
36
|
+
|
37
|
+
This filter provides basic user-controllable searching of the recordset using the `search` query
|
38
|
+
parameter (adjustable with the `search_query_param`). For example, a request to
|
39
|
+
`/api/movies?search=Star` could return movies where `name` contains the string `Star`. The search is
|
40
|
+
performed against the `search_fields` attribute, but if that is not set, then the search is
|
41
|
+
performed against a configurable default set of fields (`search_columns`).
|
@@ -0,0 +1,21 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
title: Pagination
|
4
|
+
position: 5
|
5
|
+
slug: pagination
|
6
|
+
---
|
7
|
+
# Pagination
|
8
|
+
|
9
|
+
For large result sets, you may need to provide pagination. You can configure the paginator for a
|
10
|
+
controller by setting the `paginator_class` to the paginator you want to use.
|
11
|
+
|
12
|
+
## PageNumberPaginator
|
13
|
+
|
14
|
+
This is a simple paginator which splits a recordset into pages and allows the user to select the
|
15
|
+
desired page using the `page` query parameter (e.g., `/api/movies?page=3`). To adjust this query
|
16
|
+
parameter, set the `page_query_param` controller attribute.
|
17
|
+
|
18
|
+
By default the user can adjust the page size using the `page_size` query param. To adjust this query
|
19
|
+
parameter, you can set the `page_size_query_param` controller attribute, or set it to `nil` to
|
20
|
+
disable this functionality. By default, there is no upper limit to the size of a page a user can
|
21
|
+
request. To enforce an upper limit, set the `max_page_size` controller attribute.
|
@@ -0,0 +1,144 @@
|
|
1
|
+
{% capture headingsWorkspace %}
|
2
|
+
{% comment %}
|
3
|
+
Copyright (c) 2018 Vladimir "allejo" Jimenez
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person
|
6
|
+
obtaining a copy of this software and associated documentation
|
7
|
+
files (the "Software"), to deal in the Software without
|
8
|
+
restriction, including without limitation the rights to use,
|
9
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the
|
11
|
+
Software is furnished to do so, subject to the following
|
12
|
+
conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be
|
15
|
+
included in all copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
18
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
19
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
20
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
21
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
22
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
23
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
24
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
{% endcomment %}
|
26
|
+
{% comment %}
|
27
|
+
Version 1.0.7
|
28
|
+
https://github.com/allejo/jekyll-anchor-headings
|
29
|
+
|
30
|
+
"Be the pull request you wish to see in the world." ~Ben Balter
|
31
|
+
|
32
|
+
Usage:
|
33
|
+
{% include anchor_headings.html html=content anchorBody="#" %}
|
34
|
+
|
35
|
+
Parameters:
|
36
|
+
* html (string) - the HTML of compiled markdown generated by kramdown in Jekyll
|
37
|
+
|
38
|
+
Optional Parameters:
|
39
|
+
* beforeHeading (bool) : false - Set to true if the anchor should be placed _before_ the heading's content
|
40
|
+
* anchorAttrs (string) : '' - Any custom HTML attributes that will be added to the `<a>` tag; you may NOT use `href`, `class` or `title`;
|
41
|
+
the `%heading%` and `%html_id%` placeholders are available
|
42
|
+
* anchorBody (string) : '' - The content that will be placed inside the anchor; the `%heading%` placeholder is available
|
43
|
+
* anchorClass (string) : '' - The class(es) that will be used for each anchor. Separate multiple classes with a space
|
44
|
+
* anchorTitle (string) : '' - The `title` attribute that will be used for anchors
|
45
|
+
* h_min (int) : 1 - The minimum header level to build an anchor for; any header lower than this value will be ignored
|
46
|
+
* h_max (int) : 6 - The maximum header level to build an anchor for; any header greater than this value will be ignored
|
47
|
+
* bodyPrefix (string) : '' - Anything that should be inserted inside of the heading tag _before_ its anchor and content
|
48
|
+
* bodySuffix (string) : '' - Anything that should be inserted inside of the heading tag _after_ its anchor and content
|
49
|
+
|
50
|
+
Output:
|
51
|
+
The original HTML with the addition of anchors inside of all of the h1-h6 headings.
|
52
|
+
{% endcomment %}
|
53
|
+
|
54
|
+
{% assign minHeader = include.h_min | default: 1 %}
|
55
|
+
{% assign maxHeader = include.h_max | default: 6 %}
|
56
|
+
{% assign beforeHeading = include.beforeHeading %}
|
57
|
+
{% assign nodes = include.html | split: '<h' %}
|
58
|
+
|
59
|
+
{% capture edited_headings %}{% endcapture %}
|
60
|
+
|
61
|
+
{% for _node in nodes %}
|
62
|
+
{% capture node %}{{ _node | strip }}{% endcapture %}
|
63
|
+
|
64
|
+
{% if node == "" %}
|
65
|
+
{% continue %}
|
66
|
+
{% endif %}
|
67
|
+
|
68
|
+
{% assign nextChar = node | replace: '"', '' | strip | slice: 0, 1 %}
|
69
|
+
{% assign headerLevel = nextChar | times: 1 %}
|
70
|
+
|
71
|
+
<!-- If the level is cast to 0, it means it's not a h1-h6 tag, so let's see if we need to fix it -->
|
72
|
+
{% if headerLevel == 0 %}
|
73
|
+
<!-- Split up the node based on closing angle brackets and get the first one. -->
|
74
|
+
{% assign firstChunk = node | split: '>' | first %}
|
75
|
+
|
76
|
+
<!-- If the first chunk does NOT contain a '<', that means we've broken another HTML tag that starts with 'h' -->
|
77
|
+
{% unless firstChunk contains '<' %}
|
78
|
+
{% capture node %}<h{{ node }}{% endcapture %}
|
79
|
+
{% endunless %}
|
80
|
+
|
81
|
+
{% capture edited_headings %}{{ edited_headings }}{{ node }}{% endcapture %}
|
82
|
+
{% continue %}
|
83
|
+
{% endif %}
|
84
|
+
|
85
|
+
{% capture _closingTag %}</h{{ headerLevel }}>{% endcapture %}
|
86
|
+
{% assign _workspace = node | split: _closingTag %}
|
87
|
+
{% assign _idWorkspace = _workspace[0] | split: 'id="' %}
|
88
|
+
{% assign _idWorkspace = _idWorkspace[1] | split: '"' %}
|
89
|
+
{% assign html_id = _idWorkspace[0] %}
|
90
|
+
|
91
|
+
{% capture _hAttrToStrip %}{{ _workspace[0] | split: '>' | first }}>{% endcapture %}
|
92
|
+
{% assign header = _workspace[0] | replace: _hAttrToStrip, '' %}
|
93
|
+
|
94
|
+
<!-- Build the anchor to inject for our heading -->
|
95
|
+
{% capture anchor %}{% endcapture %}
|
96
|
+
|
97
|
+
{% if html_id and headerLevel >= minHeader and headerLevel <= maxHeader %}
|
98
|
+
{% capture anchor %}href="#{{ html_id }}"{% endcapture %}
|
99
|
+
|
100
|
+
{% if include.anchorClass %}
|
101
|
+
{% capture anchor %}{{ anchor }} class="{{ include.anchorClass }}"{% endcapture %}
|
102
|
+
{% endif %}
|
103
|
+
|
104
|
+
{% if include.anchorTitle %}
|
105
|
+
{% capture anchor %}{{ anchor }} title="{{ include.anchorTitle | replace: '%heading%', header }}"{% endcapture %}
|
106
|
+
{% endif %}
|
107
|
+
|
108
|
+
{% if include.anchorAttrs %}
|
109
|
+
{% capture anchor %}{{ anchor }} {{ include.anchorAttrs | replace: '%heading%', header | replace: '%html_id%', html_id }}{% endcapture %}
|
110
|
+
{% endif %}
|
111
|
+
|
112
|
+
{% capture anchor %}<a {{ anchor }}>{{ include.anchorBody | replace: '%heading%', header | default: '' }}</a>{% endcapture %}
|
113
|
+
|
114
|
+
<!-- In order to prevent adding extra space after a heading, we'll let the 'anchor' value contain it -->
|
115
|
+
{% if beforeHeading %}
|
116
|
+
{% capture anchor %}{{ anchor }} {% endcapture %}
|
117
|
+
{% else %}
|
118
|
+
{% capture anchor %} {{ anchor }}{% endcapture %}
|
119
|
+
{% endif %}
|
120
|
+
{% endif %}
|
121
|
+
|
122
|
+
{% capture new_heading %}
|
123
|
+
<h{{ _hAttrToStrip }}
|
124
|
+
{{ include.bodyPrefix }}
|
125
|
+
{% if beforeHeading %}
|
126
|
+
{{ anchor }}{{ header }}
|
127
|
+
{% else %}
|
128
|
+
{{ header }}{{ anchor }}
|
129
|
+
{% endif %}
|
130
|
+
{{ include.bodySuffix }}
|
131
|
+
</h{{ headerLevel }}>
|
132
|
+
{% endcapture %}
|
133
|
+
|
134
|
+
<!--
|
135
|
+
If we have content after the `</hX>` tag, then we'll want to append that here so we don't lost any content.
|
136
|
+
-->
|
137
|
+
{% assign chunkCount = _workspace | size %}
|
138
|
+
{% if chunkCount > 1 %}
|
139
|
+
{% capture new_heading %}{{ new_heading }}{{ _workspace | last }}{% endcapture %}
|
140
|
+
{% endif %}
|
141
|
+
|
142
|
+
{% capture edited_headings %}{{ edited_headings }}{{ new_heading }}{% endcapture %}
|
143
|
+
{% endfor %}
|
144
|
+
{% endcapture %}{% assign headingsWorkspace = '' %}{{ edited_headings | strip }}
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<head>
|
2
|
+
<meta charset="utf-8">
|
3
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
4
|
+
<link rel='icon' type='image/x-icon' href='/assets/images/favicon.ico' />
|
5
|
+
|
6
|
+
<title>{% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}</title>
|
7
|
+
|
8
|
+
<!-- Bootstrap -->
|
9
|
+
<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">
|
10
|
+
<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>
|
11
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.4/font/bootstrap-icons.css">
|
12
|
+
|
13
|
+
<!-- Highlight.js -->
|
14
|
+
<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" />
|
15
|
+
<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" />
|
16
|
+
<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>
|
17
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/shell.min.js" integrity="sha512-X2JngetHwVsp0j3n6lo8HGdXQKLpz2hwFfQkG996OfanpFaQJFgjKJlmzsdefWsHTQIwY539tD09JF48kCPMXw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
18
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/erb.min.js" integrity="sha512-flbEiCcectGeyRXyuMZW5jlAGIQ1/qrTZS6DsZDTqObM0JG/isYHvUyehOyt14ssmY85gZRYra+IJR9+azRuqw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
19
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/ruby.min.js" integrity="sha512-xRUQANk9Iw3wtAp0cBOa1Ghr7yIFrMiJiEujrMGf04qOau23exxj4R7DLUeLGfLiDbVSK0FyN8v2ns4m/6iNmQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
20
|
+
<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>
|
21
|
+
<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>
|
22
|
+
|
23
|
+
<link rel="stylesheet" href="{{ "/assets/css/rest_framework.css" }}">
|
24
|
+
<script src="{{ "/assets/js/rest_framework.js" }}"></script>
|
25
|
+
|
26
|
+
<!-- Global site tag (gtag.js) - Google Analytics -->
|
27
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-P2KRPNXQMT"></script>
|
28
|
+
<script>
|
29
|
+
window.dataLayer = window.dataLayer || [];
|
30
|
+
function gtag(){dataLayer.push(arguments);}
|
31
|
+
gtag('js', new Date());
|
32
|
+
|
33
|
+
gtag('config', 'G-P2KRPNXQMT');
|
34
|
+
</script>
|
35
|
+
</head>
|
@@ -0,0 +1,58 @@
|
|
1
|
+
<header>
|
2
|
+
<div class="w-100 m-0 p-0" id="rrfAccentBar"></div>
|
3
|
+
<nav class="navbar py-0 navbar-expand-md" data-bs-theme="dark">
|
4
|
+
<div class="container">
|
5
|
+
|
6
|
+
<span class="navbar-brand p-0">
|
7
|
+
<a href="/">
|
8
|
+
<h1 class="text-light font-weight-light m-0 p-0" style="font-size: 1em; ">{{ site.title }}</h1>
|
9
|
+
</a>
|
10
|
+
</span>
|
11
|
+
|
12
|
+
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
|
13
|
+
<span class="navbar-toggler-icon"></span>
|
14
|
+
</button>
|
15
|
+
|
16
|
+
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
17
|
+
<ul class="navbar-nav ms-auto">
|
18
|
+
<li class="nav-item">
|
19
|
+
<a class="nav-link{% if page.url == '/' %} active{% endif %}" href="/">Home</a>
|
20
|
+
</li>
|
21
|
+
|
22
|
+
<li class="nav-item dropdown">
|
23
|
+
<a class="nav-link dropdown-toggle {% if page.url contains 'guide' %} active{% endif %}" href="#" role="button" data-bs-toggle="dropdown">
|
24
|
+
Guide
|
25
|
+
</a>
|
26
|
+
<div class="rrf-mode dropdown-menu">
|
27
|
+
{% for section in site.guide %}
|
28
|
+
<a class="dropdown-item" href="{{ section.url }}">{{ section.title }}</a>
|
29
|
+
{% endfor %}
|
30
|
+
</div>
|
31
|
+
</li>
|
32
|
+
|
33
|
+
<li class="nav-item ps-2" id="rrfGithubAndModeWrapper">
|
34
|
+
<span id="rrfGithubComponent">
|
35
|
+
<script async defer src="https://buttons.github.io/buttons.js"></script>
|
36
|
+
<a class="github-button" href="https://github.com/gregschmit/rails-rest-framework" data-show-count="true" aria-label="Star gregschmit/rails-rest-framework on GitHub">Star</a>
|
37
|
+
</span>
|
38
|
+
<div class="dropdown ms-auto float-end" id="rrfModeComponent">
|
39
|
+
<button class="btn btn-dark dropdown-toggle rounded-0 bg-black" style="border-color: black" data-bs-toggle="dropdown"></button>
|
40
|
+
<div class="rrf-mode dropdown-menu dropdown-menu-end py-0 rounded-0" style="font-size: .8em; min-width: 0">
|
41
|
+
<button class="dropdown-item text-end" data-rrf-mode-value="system">
|
42
|
+
System<i class="bi bi-circle-half ms-2"></i>
|
43
|
+
</button>
|
44
|
+
<button class="dropdown-item text-end" data-rrf-mode-value="light">
|
45
|
+
Light<i class="bi bi-sun-fill ms-2"></i>
|
46
|
+
</button>
|
47
|
+
<button class="dropdown-item text-end" data-rrf-mode-value="dark">
|
48
|
+
Dark<i class="bi bi-moon-stars-fill ms-2"></i>
|
49
|
+
</button>
|
50
|
+
</div>
|
51
|
+
</div>
|
52
|
+
</li>
|
53
|
+
</ul>
|
54
|
+
</div>
|
55
|
+
|
56
|
+
</div>
|
57
|
+
</nav>
|
58
|
+
</header>
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html class="rrf-mode">
|
3
|
+
{% include head.html %}
|
4
|
+
<body>
|
5
|
+
{% include header.html %}
|
6
|
+
<div class="container pb-3">
|
7
|
+
<div id="headersTable" class="headers-table float-md-end m-2"></div>
|
8
|
+
{% include anchor_headings.html html=content anchorBody="#" %}
|
9
|
+
</div>
|
10
|
+
</body>
|
11
|
+
</html>
|
@@ -0,0 +1,159 @@
|
|
1
|
+
/********************************
|
2
|
+
* START OF LIB/DOCS COMMON CSS *
|
3
|
+
********************************/
|
4
|
+
|
5
|
+
:root {
|
6
|
+
--rrf-red: #900;
|
7
|
+
--rrf-red-hover: #5f0c0c;
|
8
|
+
--rrf-light-red: #db2525;
|
9
|
+
--rrf-light-red-hover: #b80404;
|
10
|
+
}
|
11
|
+
#rrfAccentBar {
|
12
|
+
background-color: var(--rrf-red);
|
13
|
+
height: .3em;
|
14
|
+
}
|
15
|
+
header nav { background-color: black; }
|
16
|
+
|
17
|
+
/* Header adjustments. */
|
18
|
+
h1 { font-size: 2rem; }
|
19
|
+
h2 { font-size: 1.7rem; }
|
20
|
+
h3 { font-size: 1.5rem; }
|
21
|
+
h4 { font-size: 1.3rem; }
|
22
|
+
h5 { font-size: 1.1rem; }
|
23
|
+
h6 { font-size: 1rem; }
|
24
|
+
h1, h2, h3, h4, h5, h6 {
|
25
|
+
color: var(--rrf-red);
|
26
|
+
}
|
27
|
+
html[data-bs-theme="dark"] h1,
|
28
|
+
html[data-bs-theme="dark"] h2,
|
29
|
+
html[data-bs-theme="dark"] h3,
|
30
|
+
html[data-bs-theme="dark"] h4,
|
31
|
+
html[data-bs-theme="dark"] h5,
|
32
|
+
html[data-bs-theme="dark"] h6 {
|
33
|
+
color: var(--rrf-light-red);
|
34
|
+
}
|
35
|
+
|
36
|
+
/* Improve code and code blocks. */
|
37
|
+
pre code {
|
38
|
+
display: block;
|
39
|
+
overflow-x: auto;
|
40
|
+
}
|
41
|
+
code, .trix-content pre {
|
42
|
+
padding: .5em !important;
|
43
|
+
background-color: #eee !important;
|
44
|
+
border: 1px solid #aaa;
|
45
|
+
border-radius: 3px;
|
46
|
+
}
|
47
|
+
p code {
|
48
|
+
padding: .1em .3em !important;
|
49
|
+
}
|
50
|
+
html[data-bs-theme="dark"] code, html[data-bs-theme="dark"] .trix-content pre {
|
51
|
+
background-color: #2b2b2b !important;
|
52
|
+
}
|
53
|
+
|
54
|
+
/* Anchors */
|
55
|
+
a:not(.nav-link) {
|
56
|
+
text-decoration: none;
|
57
|
+
color: var(--rrf-red);
|
58
|
+
}
|
59
|
+
a:hover:not(.nav-link) {
|
60
|
+
text-decoration: underline;
|
61
|
+
color: var(--rrf-red-hover);
|
62
|
+
}
|
63
|
+
html[data-bs-theme="dark"] a:not(.nav-link) { color: var(--rrf-light-red); }
|
64
|
+
html[data-bs-theme="dark"] a:hover:not(.nav-link) { color: var(--rrf-light-red-hover); }
|
65
|
+
|
66
|
+
/******************************
|
67
|
+
* END OF LIB/DOCS COMMON CSS *
|
68
|
+
******************************/
|
69
|
+
|
70
|
+
/* Header adjustments. */
|
71
|
+
h1, h2, h3, h4, h5, h6 {
|
72
|
+
width: 100%;
|
73
|
+
font-weight: normal;
|
74
|
+
margin-top: 1.8rem;
|
75
|
+
margin-bottom: 1.2rem;
|
76
|
+
}
|
77
|
+
h1 a:not(:hover),
|
78
|
+
h2 a:not(:hover),
|
79
|
+
h3 a:not(:hover),
|
80
|
+
h4 a:not(:hover),
|
81
|
+
h5 a:not(:hover),
|
82
|
+
h6 a:not(:hover) {
|
83
|
+
color: #ddd;
|
84
|
+
}
|
85
|
+
html[data-bs-theme="dark"] h1 a:not(:hover),
|
86
|
+
html[data-bs-theme="dark"] h2 a:not(:hover),
|
87
|
+
html[data-bs-theme="dark"] h3 a:not(:hover),
|
88
|
+
html[data-bs-theme="dark"] h4 a:not(:hover),
|
89
|
+
html[data-bs-theme="dark"] h5 a:not(:hover),
|
90
|
+
html[data-bs-theme="dark"] h6 a:not(:hover) {
|
91
|
+
color: #444;
|
92
|
+
}
|
93
|
+
h1 a:hover, h2 a:hover, h3 a:hover, h4 a:hover, h5 a:hover, h6 a:hover {
|
94
|
+
text-decoration: none !important;
|
95
|
+
}
|
96
|
+
|
97
|
+
/* Navbar */
|
98
|
+
.navbar .navbar-toggler {
|
99
|
+
margin: .2em 0;
|
100
|
+
padding: .2em .3em;
|
101
|
+
border: none;
|
102
|
+
}
|
103
|
+
.navbar .navbar-toggler .navbar-toggler-icon {
|
104
|
+
height: 1.1em;
|
105
|
+
width: 1.1em;
|
106
|
+
}
|
107
|
+
.navbar .navbar-nav .nav-item .nav-link {
|
108
|
+
padding: .45em .6em;
|
109
|
+
}
|
110
|
+
.navbar .navbar-nav .nav-item .nav-link:hover {
|
111
|
+
background-color: #262a2f;
|
112
|
+
}
|
113
|
+
.navbar .dropdown-menu a.dropdown-item {
|
114
|
+
font-size: .9em;
|
115
|
+
padding: .2em .8em;
|
116
|
+
}
|
117
|
+
|
118
|
+
/* Headers table. */
|
119
|
+
.headers-table {
|
120
|
+
padding: .5em 1em;
|
121
|
+
background-color: #eee;
|
122
|
+
border: 1px solid #aaa;
|
123
|
+
border-radius: .3em;
|
124
|
+
font-size: .9em;
|
125
|
+
}
|
126
|
+
html[data-bs-theme="dark"] .headers-table {
|
127
|
+
background-color: #2b2b2b;
|
128
|
+
}
|
129
|
+
.headers-table:empty { display: none; }
|
130
|
+
.headers-table ul {
|
131
|
+
list-style-type: none;
|
132
|
+
margin: 0;
|
133
|
+
padding-left: 0;
|
134
|
+
padding-right: .6em;
|
135
|
+
}
|
136
|
+
.headers-table ul li { margin: .3em 0; }
|
137
|
+
.headers-table ul ul { padding-left: .8em; padding-right: 0; }
|
138
|
+
.headers-table > ul > li {
|
139
|
+
font-weight: bold;
|
140
|
+
}
|
141
|
+
|
142
|
+
/* Style the github and mode component. */
|
143
|
+
#rrfGithubAndModeWrapper {
|
144
|
+
height: 2.4em;
|
145
|
+
}
|
146
|
+
#rrfGithubComponent {
|
147
|
+
display: inline-block;
|
148
|
+
position: relative;
|
149
|
+
top: .6em;
|
150
|
+
}
|
151
|
+
#rrfModeComponent .dropdown-toggle {
|
152
|
+
float: right;
|
153
|
+
}
|
154
|
+
#rrfModeComponent .dropdown-menu {
|
155
|
+
position: absolute;
|
156
|
+
right: 0;
|
157
|
+
left: auto;
|
158
|
+
top: 100%;
|
159
|
+
}
|
Binary file
|
@@ -0,0 +1,132 @@
|
|
1
|
+
/*******************************
|
2
|
+
* START OF LIB/DOCS COMMON JS *
|
3
|
+
*******************************/
|
4
|
+
|
5
|
+
;(() => {
|
6
|
+
// Get the real mode from a selected mode. Anything other than "light" or "dark" is treated as
|
7
|
+
// "system" mode.
|
8
|
+
const rrfGetRealMode = (selectedMode) => {
|
9
|
+
if (selectedMode === "light" || selectedMode === "dark") {
|
10
|
+
return selectedMode
|
11
|
+
}
|
12
|
+
|
13
|
+
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
14
|
+
return "dark"
|
15
|
+
}
|
16
|
+
|
17
|
+
return "light"
|
18
|
+
}
|
19
|
+
|
20
|
+
// Set the mode, given a "selected" mode.
|
21
|
+
const rrfSetSelectedMode = (selectedMode) => {
|
22
|
+
const modeComponent = document.getElementById("rrfModeComponent")
|
23
|
+
|
24
|
+
// Anything except "light" or "dark" is casted to "system".
|
25
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
26
|
+
selectedMode = "system"
|
27
|
+
}
|
28
|
+
|
29
|
+
// Store selected mode in `localStorage`.
|
30
|
+
localStorage.setItem("rrfMode", selectedMode)
|
31
|
+
|
32
|
+
// Set the mode selector to the selected mode.
|
33
|
+
let labelHTML
|
34
|
+
modeComponent.querySelectorAll("button[data-rrf-mode-value]").forEach((el) => {
|
35
|
+
if (el.getAttribute("data-rrf-mode-value") === selectedMode) {
|
36
|
+
el.classList.add("active")
|
37
|
+
labelHTML = el.querySelector("i").outerHTML.replace("ms-2", "me-1")
|
38
|
+
} else {
|
39
|
+
el.classList.remove("active")
|
40
|
+
}
|
41
|
+
})
|
42
|
+
modeComponent.querySelector("button[data-bs-toggle]").innerHTML = labelHTML
|
43
|
+
|
44
|
+
// Get the real mode to use.
|
45
|
+
realMode = rrfGetRealMode(selectedMode)
|
46
|
+
|
47
|
+
// Set the `realMode` effects.
|
48
|
+
if (realMode === "light") {
|
49
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
50
|
+
el.disabled = false
|
51
|
+
})
|
52
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
53
|
+
el.disabled = true
|
54
|
+
})
|
55
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
56
|
+
el.setAttribute("data-bs-theme", "light")
|
57
|
+
})
|
58
|
+
} else if (realMode === "dark") {
|
59
|
+
document.querySelectorAll(".rrf-light-mode").forEach((el) => {
|
60
|
+
el.disabled = true
|
61
|
+
})
|
62
|
+
document.querySelectorAll(".rrf-dark-mode").forEach((el) => {
|
63
|
+
el.disabled = false
|
64
|
+
})
|
65
|
+
document.querySelectorAll(".rrf-mode").forEach((el) => {
|
66
|
+
el.setAttribute("data-bs-theme", "dark")
|
67
|
+
})
|
68
|
+
} else {
|
69
|
+
console.log(`RRF: Unknown mode: ${mode}`)
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
// Initialize dark/light mode.
|
74
|
+
document.addEventListener("DOMContentLoaded", (event) => {
|
75
|
+
const selectedMode = localStorage.getItem("rrfMode")
|
76
|
+
rrfSetSelectedMode(selectedMode)
|
77
|
+
document.querySelectorAll("#rrfModeComponent button[data-rrf-mode-value]").forEach((el) => {
|
78
|
+
el.addEventListener("click", (event) => {
|
79
|
+
rrfSetSelectedMode(event.target.getAttribute("data-rrf-mode-value"))
|
80
|
+
})
|
81
|
+
})
|
82
|
+
})
|
83
|
+
|
84
|
+
// Handle case where user changes system theme.
|
85
|
+
if (window.matchMedia) {
|
86
|
+
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
|
87
|
+
const selectedMode = localStorage.getItem("rrfMode")
|
88
|
+
if (selectedMode !== "light" && selectedMode !== "dark") {
|
89
|
+
rrfSetSelectedMode("system")
|
90
|
+
}
|
91
|
+
})
|
92
|
+
}
|
93
|
+
})()
|
94
|
+
|
95
|
+
/*****************************
|
96
|
+
* END OF LIB/DOCS COMMON JS *
|
97
|
+
*****************************/
|
98
|
+
|
99
|
+
document.addEventListener("DOMContentLoaded", () => {
|
100
|
+
// Initialize `Highlight.js`.
|
101
|
+
hljs.configure({ ignoreUnescapedHTML: true })
|
102
|
+
hljs.highlightAll()
|
103
|
+
|
104
|
+
// Setup the floating table of contents.
|
105
|
+
let table = "<ul>"
|
106
|
+
let hlevel = 2
|
107
|
+
let hprevlevel = 2
|
108
|
+
document.querySelectorAll("h2, h3, h4").forEach((header) => {
|
109
|
+
hlevel = parseInt(header.tagName[1])
|
110
|
+
|
111
|
+
if (hlevel > hprevlevel) {
|
112
|
+
table += "<ul>"
|
113
|
+
} else if (hlevel < hprevlevel) {
|
114
|
+
Array(hprevlevel - hlevel)
|
115
|
+
.fill(0)
|
116
|
+
.forEach(function () {
|
117
|
+
table += "</ul>"
|
118
|
+
})
|
119
|
+
}
|
120
|
+
table += `<li><a href="${
|
121
|
+
header.querySelectorAll("a")[0].href
|
122
|
+
}">${header.childNodes[0].nodeValue.trim()}</a></li>`
|
123
|
+
hprevlevel = hlevel
|
124
|
+
})
|
125
|
+
if (hlevel > hprevlevel) {
|
126
|
+
table += "</ul>"
|
127
|
+
}
|
128
|
+
table += "</ul>"
|
129
|
+
if (table != "<ul></ul>") {
|
130
|
+
document.getElementById("headersTable").innerHTML = table
|
131
|
+
}
|
132
|
+
})
|