rails_mini_profiler 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/app/controllers/rails_mini_profiler/profiled_requests_controller.rb +13 -10
- data/app/javascript/images/check.svg +3 -0
- data/app/javascript/images/chevron.svg +3 -0
- data/app/javascript/images/filter.svg +1 -0
- data/app/javascript/images/logo_variant.svg +2 -2
- data/app/javascript/images/search.svg +4 -5
- data/app/javascript/js/checklist_controller.js +48 -0
- data/app/javascript/js/enable_controller.js +24 -0
- data/app/javascript/js/filter_controller.js +44 -0
- data/app/javascript/js/search_controller.js +18 -0
- data/app/javascript/js/select_controller.js +47 -0
- data/app/javascript/packs/rails-mini-profiler.js +23 -15
- data/app/javascript/stylesheets/components/page_header/page_header.scss +3 -0
- data/app/javascript/stylesheets/components/pagination.scss +14 -13
- data/app/javascript/stylesheets/components/profiled_request_table/placeholder.scss +33 -0
- data/app/javascript/stylesheets/components/profiled_request_table/profiled_request_table.scss +179 -0
- data/app/javascript/stylesheets/flamegraph.scss +3 -2
- data/app/javascript/stylesheets/flashes.scss +3 -5
- data/app/javascript/stylesheets/navbar.scss +7 -13
- data/app/javascript/stylesheets/profiled_requests.scss +35 -120
- data/app/javascript/stylesheets/rails-mini-profiler.scss +90 -61
- data/app/javascript/stylesheets/traces.scss +17 -17
- data/app/presenters/rails_mini_profiler/profiled_request_presenter.rb +8 -15
- data/app/search/rails_mini_profiler/base_search.rb +67 -0
- data/app/search/rails_mini_profiler/profiled_request_search.rb +34 -0
- data/app/views/rails_mini_profiler/badge.html.erb +2 -2
- data/app/views/rails_mini_profiler/profiled_requests/index.html.erb +8 -58
- data/app/views/rails_mini_profiler/profiled_requests/shared/header/_header.erb +20 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_placeholder.erb +12 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table.erb +14 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_head.erb +125 -0
- data/app/views/rails_mini_profiler/profiled_requests/shared/table/_table_row.erb +21 -0
- data/app/views/rails_mini_profiler/profiled_requests/show.html.erb +1 -1
- data/lib/rails_mini_profiler/configuration.rb +3 -0
- data/lib/rails_mini_profiler/engine.rb +12 -8
- data/lib/rails_mini_profiler/middleware.rb +3 -3
- data/lib/rails_mini_profiler/tracing/controller_tracer.rb +15 -0
- data/lib/rails_mini_profiler/tracing/null_trace.rb +7 -0
- data/lib/rails_mini_profiler/tracing/sequel_tracer.rb +37 -0
- data/lib/rails_mini_profiler/tracing/sequel_tracker.rb +37 -0
- data/lib/rails_mini_profiler/tracing/subscriptions.rb +34 -0
- data/lib/rails_mini_profiler/{models → tracing}/trace.rb +10 -2
- data/lib/rails_mini_profiler/tracing/trace_factory.rb +37 -0
- data/lib/rails_mini_profiler/tracing/tracer.rb +31 -0
- data/lib/rails_mini_profiler/tracing/view_tracer.rb +12 -0
- data/lib/rails_mini_profiler/tracing.rb +11 -0
- data/lib/rails_mini_profiler/version.rb +1 -1
- data/lib/rails_mini_profiler.rb +4 -8
- data/vendor/assets/images/check.svg +3 -0
- data/vendor/assets/images/chevron.svg +3 -0
- data/vendor/assets/images/filter.svg +1 -0
- data/vendor/assets/images/logo_variant.svg +2 -2
- data/vendor/assets/images/search.svg +4 -5
- data/vendor/assets/javascripts/rails-mini-profiler.css +1 -1
- data/vendor/assets/javascripts/rails-mini-profiler.js +1 -1
- metadata +33 -4
- data/lib/rails_mini_profiler/tracers.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7e87dd3ae71d8ef0ab8021c31b037db8b290521782577dad5a7143cbbbd2f8f1
|
4
|
+
data.tar.gz: 1f7060bde727f2dbcdd7f4b6269d2436744cfb625484ed7bf43b5e4659743e6f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e90925e2addb71264c09084a9b4244553b466f40e87f96902e60e9301b3befe3ed898a6578e6dd4a3c5c51cb18974fecda9036e6e5d82f27f3bac5bbce9e3aa
|
7
|
+
data.tar.gz: ed964b334db077211701b956c43f9f0e778bcd260e1097f1eaface96ffacbefe5c6e6bef01e6326f06db318da925a1a0d3a72790bf6c267e0e7d1309844ff95b
|
@@ -7,11 +7,9 @@ module RailsMiniProfiler
|
|
7
7
|
before_action :set_profiled_request, only: %i[show destroy]
|
8
8
|
|
9
9
|
def index
|
10
|
-
@profiled_requests = ProfiledRequest
|
11
|
-
|
12
|
-
|
13
|
-
@profiled_requests = @profiled_requests.where('request_path LIKE ?', "%#{params[:path]}%") if params[:path]
|
14
|
-
@pagy, @profiled_requests = pagy(@profiled_requests, items: configuration.ui.page_size)
|
10
|
+
@profiled_requests = ProfiledRequest.where(user_id: user_id).order(id: :desc)
|
11
|
+
search = ProfiledRequestSearch.new(index_params, scope: @profiled_requests)
|
12
|
+
@pagy, @profiled_requests = pagy(search.results, items: configuration.ui.page_size)
|
15
13
|
@profiled_requests = @profiled_requests.map { |request| present(request) }
|
16
14
|
end
|
17
15
|
|
@@ -31,16 +29,21 @@ module RailsMiniProfiler
|
|
31
29
|
|
32
30
|
def destroy_all
|
33
31
|
ProfiledRequest.transaction do
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
32
|
+
profiled_requests = ProfiledRequest.where(user_id: user_id)
|
33
|
+
profiled_requests = ProfiledRequestSearch.new(index_params, scope: profiled_requests).results
|
34
|
+
Flamegraph.joins(:profiled_request).merge(profiled_requests).delete_all
|
35
|
+
Trace.joins(:profiled_request).merge(profiled_requests).delete_all
|
36
|
+
profiled_requests.delete_all
|
38
37
|
end
|
39
|
-
redirect_to profiled_requests_url, notice: 'Profiled
|
38
|
+
redirect_to profiled_requests_url, notice: 'Profiled requests cleared', status: :see_other
|
40
39
|
end
|
41
40
|
|
42
41
|
private
|
43
42
|
|
43
|
+
def index_params
|
44
|
+
params.permit(:path, :duration, id: [], method: [], media_type: [], status: [])
|
45
|
+
end
|
46
|
+
|
44
47
|
def user_id
|
45
48
|
@user_id ||= User.get(request.env)
|
46
49
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-filter"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
|
@@ -1,5 +1,5 @@
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1070" height="1070" viewBox="0 0 1070 1070" version="1.1">
|
2
|
-
<style
|
1
|
+
<svg id="logo-variant" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1070" height="1070" viewBox="0 0 1070 1070" version="1.1">
|
2
|
+
<style>#logo-variant path{fill:none;stroke-width:64;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4}</style>
|
3
3
|
<defs>
|
4
4
|
<linearGradient id="lightShadow">
|
5
5
|
<stop offset="0" style="stop-color:#c22121;stop-opacity:1"/>
|
@@ -1,10 +1,9 @@
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
2
2
|
<svg width="24px" height="24px" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
3
|
-
<
|
4
|
-
|
5
|
-
<g id="Search" transform="translate(2.000000, 2.000000)" fill="#000000" fill-rule="nonzero">
|
3
|
+
<g id="Iconly/Bulk/Search" stroke="none" stroke-width="1" fill="currentColor" fill-rule="evenodd">
|
4
|
+
<g id="Search" transform="translate(2.000000, 2.000000)" fill="currentColor" fill-rule="nonzero">
|
6
5
|
<ellipse id="Ellipse_746" cx="8.59921927" cy="8.65324385" rx="8.59921927" ry="8.65324385"></ellipse>
|
7
|
-
<path d="M18.674623,19.9552573 C18.3405833,19.9444414 18.0229443,19.8069986 17.7853553,19.5704698 L15.7489321,17.1901566 C15.3123366,16.7908936 15.2766365,16.1123232 15.668898,15.6689038 L15.668898,15.6689038 C15.8525005,15.4831065 16.1021409,15.3786387 16.3625268,15.3786387 C16.6229128,15.3786387 16.8725531,15.4831065 17.0561557,15.6689038 L19.6172468,17.7181208 C19.9861582,18.0957076 20.0999999,18.656254 19.9078887,19.1492153 C19.7157774,19.6421767 19.2536179,19.9754211 18.7279791,20 L18.674623,19.9552573 Z" id="Path_34202" opacity="0.400000006"></path>
|
6
|
+
<path fill="currentColor" d="M18.674623,19.9552573 C18.3405833,19.9444414 18.0229443,19.8069986 17.7853553,19.5704698 L15.7489321,17.1901566 C15.3123366,16.7908936 15.2766365,16.1123232 15.668898,15.6689038 L15.668898,15.6689038 C15.8525005,15.4831065 16.1021409,15.3786387 16.3625268,15.3786387 C16.6229128,15.3786387 16.8725531,15.4831065 17.0561557,15.6689038 L19.6172468,17.7181208 C19.9861582,18.0957076 20.0999999,18.656254 19.9078887,19.1492153 C19.7157774,19.6421767 19.2536179,19.9754211 18.7279791,20 L18.674623,19.9552573 Z" id="Path_34202" opacity="0.400000006"></path>
|
8
7
|
</g>
|
9
8
|
</g>
|
10
|
-
</svg>
|
9
|
+
</svg>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import { Controller } from "stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["count"];
|
5
|
+
|
6
|
+
connect() {
|
7
|
+
this.setCount();
|
8
|
+
}
|
9
|
+
|
10
|
+
checkAll() {
|
11
|
+
this.setAllCheckboxes(true);
|
12
|
+
this.setCount();
|
13
|
+
}
|
14
|
+
|
15
|
+
checkNone() {
|
16
|
+
this.setAllCheckboxes(false);
|
17
|
+
this.setCount();
|
18
|
+
}
|
19
|
+
|
20
|
+
onChecked() {
|
21
|
+
this.setCount();
|
22
|
+
}
|
23
|
+
|
24
|
+
setAllCheckboxes(checked) {
|
25
|
+
this.checkboxes.forEach((el) => {
|
26
|
+
const checkbox = el;
|
27
|
+
|
28
|
+
if (!checkbox.disabled) {
|
29
|
+
checkbox.checked = checked;
|
30
|
+
}
|
31
|
+
});
|
32
|
+
}
|
33
|
+
|
34
|
+
setCount() {
|
35
|
+
if (this.hasCountTarget) {
|
36
|
+
const count = this.selectedCheckboxes.length;
|
37
|
+
this.countTarget.innerHTML = `${count} selected`;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
get selectedCheckboxes() {
|
42
|
+
return this.checkboxes.filter((c) => c.checked);
|
43
|
+
}
|
44
|
+
|
45
|
+
get checkboxes() {
|
46
|
+
return new Array(...this.element.querySelectorAll("input[type=checkbox]"));
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
import { Controller } from 'stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["enable"];
|
5
|
+
|
6
|
+
enable() {
|
7
|
+
this.enableTarget.disabled = false;
|
8
|
+
}
|
9
|
+
|
10
|
+
disable() {
|
11
|
+
this.enableTarget.disabled = true;
|
12
|
+
}
|
13
|
+
|
14
|
+
change(event) {
|
15
|
+
if (event.type.match(/rmp:select:.*/)) {
|
16
|
+
if (event.detail.count > 0) {
|
17
|
+
this.enable();
|
18
|
+
}
|
19
|
+
else {
|
20
|
+
this.disable()
|
21
|
+
}
|
22
|
+
}
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
import { Controller } from "stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["filter"];
|
5
|
+
|
6
|
+
apply() {
|
7
|
+
location.href = `${window.location.pathname}?${this.params}`;
|
8
|
+
}
|
9
|
+
|
10
|
+
post() {
|
11
|
+
const token = document.head.querySelector(
|
12
|
+
'meta[name="csrf-token"]'
|
13
|
+
).content;
|
14
|
+
const path = `${window.location.pathname}/destroy_all?${this.params}`;
|
15
|
+
fetch(path, {
|
16
|
+
method: "DELETE",
|
17
|
+
redirect: "follow",
|
18
|
+
headers: {
|
19
|
+
"Content-Type": "application/json",
|
20
|
+
credentials: "same-origin",
|
21
|
+
},
|
22
|
+
body: JSON.stringify({ authenticity_token: token }),
|
23
|
+
}).then((response) => {
|
24
|
+
if (response.redirected) {
|
25
|
+
window.location.href = response.url;
|
26
|
+
}
|
27
|
+
});
|
28
|
+
}
|
29
|
+
|
30
|
+
get params() {
|
31
|
+
return this.activeFilterTargets()
|
32
|
+
.map((t) => `${t.name}=${t.value}`)
|
33
|
+
.join("&");
|
34
|
+
}
|
35
|
+
|
36
|
+
activeFilterTargets() {
|
37
|
+
return this.filterTargets.filter(function (target) {
|
38
|
+
if (target.type === "checkbox" || target.type === "radio")
|
39
|
+
return target.checked;
|
40
|
+
|
41
|
+
return target.value.length > 0;
|
42
|
+
});
|
43
|
+
}
|
44
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import { Controller } from "stimulus";
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ["field"];
|
5
|
+
|
6
|
+
clear(){
|
7
|
+
this.eventTarget.value = null;
|
8
|
+
window.dispatchEvent(new CustomEvent('search-controller:submit', {}))
|
9
|
+
}
|
10
|
+
|
11
|
+
submit(event) {
|
12
|
+
event.preventDefault();
|
13
|
+
|
14
|
+
if (event.key === 'Enter' || event.type === 'click') {
|
15
|
+
window.dispatchEvent(new CustomEvent('search-controller:submit', {}))
|
16
|
+
}
|
17
|
+
}
|
18
|
+
}
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { Controller } from 'stimulus'
|
2
|
+
|
3
|
+
export default class extends Controller {
|
4
|
+
static targets = ['all', 'selectable']
|
5
|
+
|
6
|
+
selectAll (event) {
|
7
|
+
const checked = event.target.checked
|
8
|
+
this.allTarget.indeterminate = false
|
9
|
+
this._setAllCheckboxes(checked)
|
10
|
+
this._dispatch('change', { count: this.selectedCount })
|
11
|
+
}
|
12
|
+
|
13
|
+
onSelected () {
|
14
|
+
this.allTarget.indeterminate = !!this._indeterminate
|
15
|
+
this._dispatch('change', { count: this.selectedCount })
|
16
|
+
}
|
17
|
+
|
18
|
+
get selectedCount () {
|
19
|
+
return this.selected.length
|
20
|
+
}
|
21
|
+
|
22
|
+
get selected () {
|
23
|
+
return this.selectables.filter((c) => c.checked)
|
24
|
+
}
|
25
|
+
|
26
|
+
get selectables () {
|
27
|
+
return new Array(...this.selectableTargets)
|
28
|
+
}
|
29
|
+
|
30
|
+
_setAllCheckboxes (checked) {
|
31
|
+
this.selectables.forEach((el) => {
|
32
|
+
const checkbox = el
|
33
|
+
|
34
|
+
if (!checkbox.disabled) {
|
35
|
+
checkbox.checked = checked
|
36
|
+
}
|
37
|
+
})
|
38
|
+
}
|
39
|
+
|
40
|
+
get _indeterminate () {
|
41
|
+
return this.selected.length !== this.selectableTargets.length && this.selected.length > 0
|
42
|
+
}
|
43
|
+
|
44
|
+
_dispatch (name, detail) {
|
45
|
+
window.dispatchEvent(new CustomEvent(`rmp:select:${name}`, { bubbles: true, detail }))
|
46
|
+
}
|
47
|
+
}
|
@@ -3,17 +3,23 @@ import '../stylesheets/rails-mini-profiler.scss'
|
|
3
3
|
import tippy from 'tippy.js'
|
4
4
|
import 'tippy.js/dist/tippy.css'
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
import { Application } from 'stimulus'
|
7
|
+
import { Dropdown } from 'tailwindcss-stimulus-components'
|
8
|
+
import Checklist from '../js/checklist_controller'
|
9
|
+
import Selectable from '../js/select_controller'
|
10
|
+
import Filter from '../js/filter_controller'
|
11
|
+
import Search from '../js/search_controller'
|
12
|
+
import Enable from '../js/enable_controller'
|
13
|
+
|
14
|
+
const application = Application.start();
|
15
|
+
|
16
|
+
application.register('dropdown', Dropdown)
|
17
|
+
application.register('checklist', Checklist)
|
18
|
+
application.register('selectable', Selectable)
|
19
|
+
application.register('filters', Filter)
|
20
|
+
application.register('search', Search)
|
21
|
+
application.register('enable', Enable)
|
22
|
+
|
17
23
|
|
18
24
|
function setupTraceSearch() {
|
19
25
|
const traceNameSearch = document.getElementById('trace-search')
|
@@ -30,8 +36,8 @@ function setupTraceSearch() {
|
|
30
36
|
function setupRequestTable() {
|
31
37
|
const profiledRequestTable = document.getElementById('profiled-requests-table');
|
32
38
|
if (profiledRequestTable) {
|
33
|
-
const rows = profiledRequestTable.
|
34
|
-
for (let i =
|
39
|
+
const rows = profiledRequestTable.rows;
|
40
|
+
for (let i = 1; i < rows.length; i++) {
|
35
41
|
const currentRow = profiledRequestTable.rows[i]
|
36
42
|
const link = currentRow.dataset.link
|
37
43
|
const createClickHandler = function () {
|
@@ -39,7 +45,10 @@ function setupRequestTable() {
|
|
39
45
|
window.location.href = link
|
40
46
|
}
|
41
47
|
}
|
42
|
-
|
48
|
+
if (link) {
|
49
|
+
currentRow.onclick = createClickHandler(currentRow)
|
50
|
+
|
51
|
+
}
|
43
52
|
}
|
44
53
|
}
|
45
54
|
}
|
@@ -73,7 +82,6 @@ function setupTraceBars () {
|
|
73
82
|
// Trace Bar Popovers
|
74
83
|
document.addEventListener('DOMContentLoaded', () => {
|
75
84
|
setupRequestTable();
|
76
|
-
setupRequestSearch();
|
77
85
|
setupTraceBars();
|
78
86
|
setupTraceSearch();
|
79
87
|
}, false)
|
@@ -9,18 +9,12 @@
|
|
9
9
|
.pagy-nav > .page.active,
|
10
10
|
.pagy-nav > .page > a {
|
11
11
|
padding: 0.5rem 1rem;
|
12
|
-
|
13
|
-
color: var(--grey-900);
|
14
|
-
|
15
|
-
background: white;
|
16
12
|
border: 1px solid var(--border-color);
|
17
13
|
border-right: none;
|
18
|
-
|
14
|
+
background: white;
|
15
|
+
color: var(--grey-900);
|
19
16
|
cursor: pointer;
|
20
|
-
|
21
|
-
&:hover {
|
22
|
-
background: var(--grey-100);
|
23
|
-
}
|
17
|
+
text-decoration: none;
|
24
18
|
}
|
25
19
|
|
26
20
|
.pagy-nav > .page.prev a,
|
@@ -30,15 +24,15 @@
|
|
30
24
|
|
31
25
|
.pagy-nav > .page.next a,
|
32
26
|
.pagy-nav > .page.next.disabled {
|
33
|
-
border-radius: 0 5px 5px 0;
|
34
27
|
border-right: 1px solid var(--border-color);
|
28
|
+
border-radius: 0 5px 5px 0;
|
35
29
|
}
|
36
30
|
|
37
31
|
.pagy-nav > .page.active {
|
38
|
-
cursor: default;
|
39
|
-
background: var(--red-500);
|
40
32
|
border-color: var(--red-500);
|
33
|
+
background: var(--red-500);
|
41
34
|
color: white;
|
35
|
+
cursor: default;
|
42
36
|
|
43
37
|
&:hover {
|
44
38
|
background: var(--red-500);
|
@@ -46,9 +40,16 @@
|
|
46
40
|
}
|
47
41
|
|
48
42
|
.pagy-nav > .page.disabled {
|
49
|
-
cursor: default;
|
50
43
|
color: var(--grey-500);
|
44
|
+
cursor: default;
|
45
|
+
|
51
46
|
&:hover {
|
52
47
|
background: white;
|
53
48
|
}
|
54
49
|
}
|
50
|
+
|
51
|
+
.pagy-nav > .page.disabled:hover,
|
52
|
+
.pagy-nav > .page.active:hover,
|
53
|
+
.pagy-nav > .page > a:hover {
|
54
|
+
background: var(--grey-100);
|
55
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
.placeholder {
|
3
|
+
display: flex;
|
4
|
+
width: 100%;
|
5
|
+
flex-direction: column;
|
6
|
+
align-items: center;
|
7
|
+
justify-content: center;
|
8
|
+
padding-bottom: 2rem;
|
9
|
+
}
|
10
|
+
|
11
|
+
.placeholder-image {
|
12
|
+
width: 30%;
|
13
|
+
height: 30%;
|
14
|
+
-webkit-filter: grayscale(1) brightness(2.5);
|
15
|
+
}
|
16
|
+
|
17
|
+
.placeholder-text {
|
18
|
+
padding: 1rem 0;
|
19
|
+
color: var(--grey-400);
|
20
|
+
text-align: center;
|
21
|
+
}
|
22
|
+
|
23
|
+
.placeholder-link {
|
24
|
+
color: var(--grey-400);
|
25
|
+
|
26
|
+
&:visited {
|
27
|
+
color: var(--grey-400);
|
28
|
+
}
|
29
|
+
|
30
|
+
&:hover {
|
31
|
+
color: var(--grey-900);
|
32
|
+
}
|
33
|
+
}
|